67#include "llvm/ADT/DenseSet.h"
68#include "llvm/ADT/STLExtras.h"
69#include "llvm/ADT/TypeSwitch.h"
70#include "llvm/Support/Debug.h"
71#include "llvm/Support/ErrorHandling.h"
75#define GEN_PASS_DEF_ACCRECIPEMATERIALIZATION
76#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
80#define DEBUG_TYPE "acc-recipe-materialization"
94static void saveVarName(StringRef name,
Value dst) {
98 if (dstOp->getDiscardableAttrOfType<acc::VarNameAttr>(
101 if (isa<ACC_DATA_ENTRY_OPS>(dstOp))
104 acc::VarNameAttr::get(dstOp->getContext(), name));
107 auto blockArg = dyn_cast<BlockArgument>(dst);
110 Block *block = blockArg.getOwner();
117 auto funcOp = dyn_cast<FunctionOpInterface>(parent);
120 unsigned argIdx = blockArg.getArgNumber();
121 if (argIdx >= funcOp.getNumArguments())
126 acc::VarNameAttr::get(parent->
getContext(), name));
129static void saveVarName(
Value src,
Value dst) {
136 for (
auto it = block->
begin(); it != std::next(ip); ++it) {
140 if (!attr || attr.getName() != placeholder)
146 acc::VarNameAttr::get(op->
getContext(), name));
154template <
typename RecipeOpTy>
159 Region &destroyRegion = recipe.getDestroyRegion();
160 assert(destroyRegion.
getBlocks().front().getNumArguments() ==
162 "unexpected acc recipe destroy block arguments");
164 setLocation(destroyRegion, loc);
166 mapping.
map(destroyRegion.
getBlocks().front().getArguments(), arguments);
171class ACCRecipeMaterialization
175 ACCRecipeMaterialization>::ACCRecipeMaterializationBase;
179 ACCRecipeMaterialization>::getDependentDialects(registry);
182 void runOnOperation()
override;
198 template <
typename OpTy>
199 void handleInitialValueMapping(OpTy op)
const;
200 template <
typename OpTy>
202 OpTy op, ModuleOp moduleOp,
204 template <
typename OpTy,
typename RecipeOpTy,
typename AccOpTy>
205 LogicalResult materialize(OpTy op, RecipeOpTy recipe, AccOpTy accOp,
208 Value materializationVar = {})
const;
209 template <
typename OpTy>
214template <
typename OpTy>
215void ACCRecipeMaterialization::handleInitialValueMapping(OpTy op)
const {
217 auto mapInitialOp = acc::FirstprivateMapInitialOp::create(
218 builder, op.getLoc(), op.getVar(), op.getStructured(), op.getImplicit(),
220 mapInitialOp.setName(op.getName());
221 op.getVarMutable().assign(mapInitialOp.getAccVar());
227static bool readsVar(
Region ®ion) {
233template <
typename OpTy>
234void ACCRecipeMaterialization::removeRecipe(
235 OpTy op, ModuleOp moduleOp,
237 auto recipeName = op.getNameAttr();
240 bool useEmpty = usedSymbols
241 ? !usedSymbols->contains(recipeName)
244 LLVM_DEBUG(llvm::dbgs() <<
"erasing recipe: " << recipeName <<
"\n");
248 std::optional<SymbolTable::UseRange> symbolUses =
249 op.getSymbolUses(moduleOp);
250 if (symbolUses.has_value()) {
252 llvm::dbgs() <<
"symbol use: ";
253 symbolUse.getUser()->dump();
257 llvm_unreachable(
"expected no use of recipe symbol");
261template <
typename OpTy,
typename RecipeOpTy,
typename AccOpTy>
262LogicalResult ACCRecipeMaterialization::materialize(
265 Region ®ion = accOp.getRegion();
266 Value origPtr = materializationVar ? materializationVar : op.getVar();
267 Value accPtr = op.getAccVar();
268 assert(accPtr &&
"invalid op: null acc var");
274 Region &initRegion = recipe.getInitRegion();
275 unsigned initNumArguments =
276 initRegion.
getBlocks().front().getArguments().size();
277 if (initNumArguments > 1) {
280 if ((initNumArguments - 1) % 3 != 0) {
282 "privatization of array section with extents");
293 initRegion.
getBlocks().front().getArgument(argIdx++).getType(),
297 auto dataBound = bound.getDefiningOp<acc::DataBoundsOp>();
299 "acc.reduction's bound must be defined by acc.bounds");
304 castValueToArgType(dataBound.getLoc(), dataBound.getLowerbound());
306 castValueToArgType(dataBound.getLoc(), dataBound.getUpperbound());
308 castValueToArgType(dataBound.getLoc(), dataBound.getStride());
309 triples.append({lb,
ub, step});
311 assert(triples.size() + 1 == initNumArguments &&
312 "mismatch between number bounds and number of recipe init block "
318 initArgs.append(triples);
319 mapping.
map(initRegion.
getBlocks().front().getArguments(), initArgs);
322 setLocation(initRegion, loc);
324 if constexpr (std::is_same_v<OpTy, acc::PrivateOp>) {
328 &initRegion, block, block->
begin(), mapping, {accPtr});
329 assert(!results.empty() &&
"expected a result from init region");
330 saveVarName(op.getAccVar(), results[0]);
333 if (!recipe.getDestroyRegion().empty()) {
334 results.insert(results.begin(), origPtr);
335 results.append(triples);
336 cloneDestroy(loc, recipe, block, std::prev(block->
end()), results);
338 }
else if constexpr (std::is_same_v<OpTy, acc::FirstprivateOp>) {
342 &initRegion, block, block->
begin(), mapping, {accPtr});
343 assert(!results.empty() &&
"expected a result from init region");
344 saveVarName(op.getAccVar(), results[0]);
348 copyArgs.append(triples);
351 destroyArgs.append(results);
352 destroyArgs.append(triples);
356 mapping.
map(recipe.getCopyRegion().front().getArguments(), copyArgs);
358 Region ©Region = recipe.getCopyRegion();
359 setLocation(copyRegion, loc);
361 if (!recipe.getDestroyRegion().empty()) {
362 cloneDestroy(loc, recipe, block, std::prev(block->
end()), destroyArgs);
364 }
else if constexpr (std::is_same_v<OpTy, acc::ReductionOp>) {
365 auto cloneRegionIntoAccRegion = [&](
Region *src,
Region *dest,
370 b.setInsertionPoint(terminator);
372 acc::YieldOp::create(
b, op.getLoc(), terminator->
getOperands());
374 acc::YieldOp::create(
b, op.getLoc(),
ValueRange{});
379 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>)
380 b.setInsertionPointToStart(®ion.
front());
381 else if constexpr (std::is_same_v<AccOpTy, acc::LoopOp>)
382 b.setInsertionPoint(op);
384 llvm_unreachable(
"unexpected acc op with reduction recipe");
388 acc::ReductionInitOp::create(
b, op.getLoc(), origPtr, reductionBounds,
389 recipe.getReductionOperatorAttr());
390 saveVarName(op.getAccVar(), reductionOp.getResult());
391 cloneRegionIntoAccRegion(&initRegion, &reductionOp.getRegion(),
393 Block *initBlock = &reductionOp.getRegion().
front();
394 resolveVarNamePlaceholders(initBlock, std::prev(initBlock->
end()),
401 Region &combinerRegion = recipe.getCombinerRegion();
402 setLocation(combinerRegion, loc);
406 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>)
408 else if constexpr (std::is_same_v<AccOpTy, acc::LoopOp>)
409 b.setInsertionPointAfter(accOp);
411 llvm_unreachable(
"unexpected acc op with reduction recipe");
419 argsRemapping.append(triples);
422 auto combineRegionOp = acc::ReductionCombineRegionOp::create(
423 b, op.getLoc(), origPtr, reductionOp.getResult());
424 cloneRegionIntoAccRegion(&combinerRegion, &combineRegionOp.getRegion(),
427 auto *ctx =
b.getContext();
433 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>) {
434 acc::GPUParallelDimsAttr parDimsAttr;
435 if (accOp.isEffectivelySerial()) {
447 auto setSeqParDimsForRecipeLoops = [&](
Region *r) {
448 r->walk([&](LoopLikeOpInterface loopLike) {
452 setSeqParDimsForRecipeLoops(&reductionOp.getRegion());
453 setSeqParDimsForRecipeLoops(&combineRegionOp.getRegion());
455 if (!recipe.getDestroyRegion().empty()) {
457 results.append(triples);
459 cloneDestroy(loc, recipe, combineRegionOp->getBlock(), ip, results);
462 llvm_unreachable(
"unexpected op type");
469template <
typename OpTy>
470LogicalResult ACCRecipeMaterialization::materializeForACCOp(
473 assert(isa<ACC_COMPUTE_CONSTRUCT_AND_LOOP_OPS>(accOp));
483 struct ReductionMapping {
488 if constexpr (!std::is_same_v<OpTy, acc::LoopOp>) {
489 for (
Value dataOperand : accOp.getDataClauseOperands()) {
490 Operation *dataOp = dataOperand.getDefiningOp();
491 if (dataOp && isa<ACC_DATA_ENTRY_OPS>(dataOp))
492 mappedReductionVars.push_back({
acc::getVar(dataOp), dataOperand});
496 auto getMappedReductionVar = [&](acc::ReductionOp reductionOp) ->
Value {
497 if constexpr (std::is_same_v<OpTy, acc::LoopOp>) {
498 return reductionOp.getVar();
500 Value originalVar = reductionOp.getVar();
501 if (isa_and_nonnull<ACC_DATA_ENTRY_OPS>(originalVar.
getDefiningOp()))
508 auto existing = llvm::find_if(mappedReductionVars,
509 [&](
const ReductionMapping &mapping) {
510 return mapping.originalVar == originalVar;
512 if (existing != mappedReductionVars.end())
513 return existing->mappedVar;
516 acc::CopyinOp copyinOp;
517 if (std::optional<StringRef> name = reductionOp.getName())
519 acc::CopyinOp::create(builder, reductionOp.getLoc(), originalVar,
521 reductionOp.getBounds());
523 copyinOp = acc::CopyinOp::create(
524 builder, reductionOp.getLoc(), originalVar,
525 true,
true, reductionOp.getBounds());
526 copyinOp.setDataClause(acc::DataClause::acc_reduction);
527 accOp.getDataClauseOperandsMutable().append(copyinOp.getAccVar());
530 acc::CopyoutOp copyoutOp;
531 if (std::optional<StringRef> name = reductionOp.getName())
532 copyoutOp = acc::CopyoutOp::create(
533 builder, reductionOp.getLoc(), copyinOp.getAccVar(), originalVar,
535 reductionOp.getBounds());
537 copyoutOp = acc::CopyoutOp::create(
538 builder, reductionOp.getLoc(), copyinOp.getAccVar(), originalVar,
539 true,
true, reductionOp.getBounds());
540 copyoutOp.setDataClause(acc::DataClause::acc_reduction);
542 mappedReductionVars.push_back({originalVar, copyinOp.getAccVar()});
543 return copyinOp.getAccVar();
547 if (!accOp.getFirstprivateOperands().empty()) {
551 accOp.getFirstprivateOperandsMutable().clear();
552 for (
Value operand : operands) {
553 auto firstprivateOp = cast<acc::FirstprivateOp>(operand.getDefiningOp());
554 auto symbolRef = cast<SymbolRefAttr>(firstprivateOp.getRecipeAttr());
556 auto recipeOp = cast<acc::FirstprivateRecipeOp>(decl);
557 LLVM_DEBUG(llvm::dbgs() <<
"materializing: " << firstprivateOp <<
"\n"
558 << symbolRef <<
"\n");
559 handleInitialValueMapping(firstprivateOp);
561 materialize(firstprivateOp, recipeOp, accOp, accSupport, policy)))
566 if (!accOp.getPrivateOperands().empty()) {
570 accOp.getPrivateOperandsMutable().clear();
571 for (
Value operand : operands) {
572 auto privateOp = cast<acc::PrivateOp>(operand.getDefiningOp());
573 auto symbolRef = cast<SymbolRefAttr>(privateOp.getRecipeAttr());
575 auto recipeOp = cast<acc::PrivateRecipeOp>(decl);
576 LLVM_DEBUG(llvm::dbgs() <<
"materializing: " << privateOp <<
"\n"
577 << symbolRef <<
"\n");
578 if (readsVar(recipeOp.getInitRegion()) ||
579 readsVar(recipeOp.getDestroyRegion()))
580 handleInitialValueMapping(privateOp);
581 if (failed(materialize(privateOp, recipeOp, accOp, accSupport, policy)))
586 if (!accOp.getReductionOperands().empty()) {
590 accOp.getReductionOperandsMutable().clear();
591 for (
Value operand : operands) {
592 auto reductionOp = cast<acc::ReductionOp>(operand.getDefiningOp());
593 auto symbolRef = cast<SymbolRefAttr>(reductionOp.getRecipeAttr());
595 auto recipeOp = cast<acc::ReductionRecipeOp>(decl);
596 LLVM_DEBUG(llvm::dbgs() <<
"materializing: " << reductionOp <<
"\n"
597 << symbolRef <<
"\n");
598 Value mappedVar = getMappedReductionVar(reductionOp);
599 if (failed(materialize(reductionOp, recipeOp, accOp, accSupport, policy,
607void ACCRecipeMaterialization::runOnOperation() {
608 ModuleOp moduleOp = getOperation();
614 bool anyFailed =
false;
619 [&](
auto constructOp) {
620 if (failed(materializeForACCOp(constructOp, accSupport, policy)))
633 std::optional<llvm::DenseSet<StringAttr>> usedSymbols;
636 if (std::optional<SymbolTable::UseRange> uses =
638 usedSymbols.emplace();
640 usedSymbols->insert(use.getSymbolRef().getLeafReference());
644 if (
auto recipe = dyn_cast<acc::ReductionRecipeOp>(op))
645 removeRecipe(recipe, moduleOp, usedSymbols);
646 else if (
auto recipe = dyn_cast<acc::PrivateRecipeOp>(op))
647 removeRecipe(recipe, moduleOp, usedSymbols);
648 else if (
auto recipe = dyn_cast<acc::FirstprivateRecipeOp>(op))
649 removeRecipe(recipe, moduleOp, usedSymbols);
Block represents an ordered list of Operations.
OpListType::iterator iterator
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Operation * getTerminator()
Get the terminator operation of this block.
BlockArgListType getArguments()
bool isEntryBlock()
Return if this block is the entry block in the parent region.
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
This is a utility class for mapping one set of IR entities to another.
void clear()
Clears all mappings held by the mapper.
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
This class helps build Operations.
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Operation is the basic unit of execution within MLIR.
void setLoc(Location loc)
Set the source location the operation was defined or derived from.
void setDiscardableAttr(StringAttr name, Attribute value)
Set a discardable attribute by name.
Attribute removeDiscardableAttr(StringAttr name)
Remove the discardable attribute with the specified name if it exists.
operand_range getOperands()
Returns an iterator on the underlying Value's.
AttrClass getDiscardableAttrOfType(StringRef name)
Access a discardable attribute by name and cast it to AttrClass.
MLIRContext * getContext()
Return the context this operation is associated with.
void erase()
Remove this operation from its parent block and delete it.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
void cloneInto(Region *dest, IRMapping &mapper)
Clone the internal blocks from this region into dest.
unsigned getNumArguments()
BlockArgument getArgument(unsigned i)
Operation * getParentOp()
Return the parent operation this region is attached to.
BlockListType & getBlocks()
RetT walk(FnT &&callback)
Walk all nested operations, blocks or regions (including this region), depending on the type of callb...
This class represents a specific symbol use.
static Operation * lookupNearestSymbolFrom(Operation *from, StringAttr symbol)
Returns the operation registered with the given symbol name within the closest parent operation of,...
static bool symbolKnownUseEmpty(StringAttr symbol, Operation *from)
Return if the given symbol is known to have no uses that are nested within the given operation 'from'...
static std::optional< UseRange > getSymbolUses(Operation *from)
Get an iterator range for all of the uses, for any symbol, that are nested within the given operation...
This class provides an abstraction over the different types of ranges over Values.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
bool use_empty() const
Returns true if this value has no uses.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Default policy that provides the standard GPU mapping: gang(dim:1) -> BlockX (gridDim....
InFlightDiagnostic emitNYI(Location loc, const Twine &message)
Report a case that is not yet supported by the implementation.
#define ACC_COMPUTE_CONSTRUCT_AND_LOOP_OPS
std::string getVariableName(mlir::Value v)
Attempts to extract the variable name from a value by walking through view-like operations until an a...
mlir::Value getVar(mlir::Operation *accDataClauseOp)
Used to obtain the var from a data clause operation.
GPUParallelDimsAttr getGangDim1ParDimsAttr(MLIRContext *ctx, ACCToGPUMappingPolicy &policy)
Create a gang dim 1 GPUParallelDimsAttr based on the mapping policy.
mlir::SmallVector< mlir::Value > getBounds(mlir::Operation *accDataClauseOp)
Used to obtain bounds from an acc data clause operation.
GPUParallelDimsAttr getSeqParDimsAttr(MLIRContext *ctx, ACCToGPUMappingPolicy &policy)
Create a sequential GPUParallelDimsAttr based on the mapping policy.
llvm::StringLiteral getVarNamePlaceholder()
Returns a placeholder string for use as an acc.var_name attribute value when the actual variable name...
static constexpr StringLiteral getVarNameAttrName()
void registerAllExtensions(DialectRegistry ®istry)
Register the standard OpenACC models for GPU, LLVM and MemRef IR.
void setParDimsAttr(Operation *op, GPUParallelDimsAttr attr)
Set parallel dimensions on op.
std::pair< llvm::SmallVector< Value >, Block::iterator > cloneACCRegionInto(Region *src, Block *dest, Block::iterator inlinePoint, IRMapping &mapping, ValueRange resultsToReplace)
Clone an ACC region into a destination block at the given insertion point.
ACCParMappingPolicy< mlir::acc::GPUParallelDimAttr > ACCToGPUMappingPolicy
Type alias for the GPU-specific mapping policy.
Include the generated interface declarations.
Value convertScalarToDtype(OpBuilder &b, Location loc, Value operand, Type toType, bool isUnsignedCast)
Converts a scalar value operand to type toType.
void replaceAllUsesInRegionWith(Value orig, Value replacement, Region ®ion)
Replace all uses of orig within the given region with replacement.
llvm::TypeSwitch< T, ResultT > TypeSwitch