66#include "llvm/ADT/DenseSet.h"
67#include "llvm/ADT/STLExtras.h"
68#include "llvm/ADT/TypeSwitch.h"
69#include "llvm/Support/Debug.h"
70#include "llvm/Support/ErrorHandling.h"
74#define GEN_PASS_DEF_ACCRECIPEMATERIALIZATION
75#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
79#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;
176 void runOnOperation()
override;
192 template <
typename OpTy>
193 void handleInitialValueMapping(OpTy op)
const;
194 template <
typename OpTy>
196 OpTy op, ModuleOp moduleOp,
198 template <
typename OpTy,
typename RecipeOpTy,
typename AccOpTy>
199 LogicalResult materialize(OpTy op, RecipeOpTy recipe, AccOpTy accOp,
202 Value materializationVar = {})
const;
203 template <
typename OpTy>
208template <
typename OpTy>
209void ACCRecipeMaterialization::handleInitialValueMapping(OpTy op)
const {
211 auto mapInitialOp = acc::FirstprivateMapInitialOp::create(
212 builder, op.getLoc(), op.getVar(), op.getStructured(), op.getImplicit(),
214 mapInitialOp.setName(op.getName());
215 op.getVarMutable().assign(mapInitialOp.getAccVar());
221static bool readsVar(
Region ®ion) {
227template <
typename OpTy>
228void ACCRecipeMaterialization::removeRecipe(
229 OpTy op, ModuleOp moduleOp,
231 auto recipeName = op.getNameAttr();
234 bool useEmpty = usedSymbols
235 ? !usedSymbols->contains(recipeName)
238 LLVM_DEBUG(llvm::dbgs() <<
"erasing recipe: " << recipeName <<
"\n");
242 std::optional<SymbolTable::UseRange> symbolUses =
243 op.getSymbolUses(moduleOp);
244 if (symbolUses.has_value()) {
246 llvm::dbgs() <<
"symbol use: ";
247 symbolUse.getUser()->dump();
251 llvm_unreachable(
"expected no use of recipe symbol");
255template <
typename OpTy,
typename RecipeOpTy,
typename AccOpTy>
256LogicalResult ACCRecipeMaterialization::materialize(
259 Region ®ion = accOp.getRegion();
260 Value origPtr = materializationVar ? materializationVar : op.getVar();
261 Value accPtr = op.getAccVar();
262 assert(accPtr &&
"invalid op: null acc var");
268 Region &initRegion = recipe.getInitRegion();
269 unsigned initNumArguments =
270 initRegion.
getBlocks().front().getArguments().size();
271 if (initNumArguments > 1) {
274 if ((initNumArguments - 1) % 3 != 0) {
276 "privatization of array section with extents");
287 initRegion.
getBlocks().front().getArgument(argIdx++).getType(),
291 auto dataBound = bound.getDefiningOp<acc::DataBoundsOp>();
293 "acc.reduction's bound must be defined by acc.bounds");
298 castValueToArgType(dataBound.getLoc(), dataBound.getLowerbound());
300 castValueToArgType(dataBound.getLoc(), dataBound.getUpperbound());
302 castValueToArgType(dataBound.getLoc(), dataBound.getStride());
303 triples.append({lb,
ub, step});
305 assert(triples.size() + 1 == initNumArguments &&
306 "mismatch between number bounds and number of recipe init block "
312 initArgs.append(triples);
313 mapping.
map(initRegion.
getBlocks().front().getArguments(), initArgs);
316 setLocation(initRegion, loc);
318 if constexpr (std::is_same_v<OpTy, acc::PrivateOp>) {
322 &initRegion, block, block->
begin(), mapping, {accPtr});
323 assert(!results.empty() &&
"expected a result from init region");
324 saveVarName(op.getAccVar(), results[0]);
327 if (!recipe.getDestroyRegion().empty()) {
328 results.insert(results.begin(), origPtr);
329 results.append(triples);
330 cloneDestroy(loc, recipe, block, std::prev(block->
end()), results);
332 }
else if constexpr (std::is_same_v<OpTy, acc::FirstprivateOp>) {
336 &initRegion, block, block->
begin(), mapping, {accPtr});
337 assert(!results.empty() &&
"expected a result from init region");
338 saveVarName(op.getAccVar(), results[0]);
342 copyArgs.append(triples);
345 destroyArgs.append(results);
346 destroyArgs.append(triples);
350 mapping.
map(recipe.getCopyRegion().front().getArguments(), copyArgs);
352 Region ©Region = recipe.getCopyRegion();
353 setLocation(copyRegion, loc);
355 if (!recipe.getDestroyRegion().empty()) {
356 cloneDestroy(loc, recipe, block, std::prev(block->
end()), destroyArgs);
358 }
else if constexpr (std::is_same_v<OpTy, acc::ReductionOp>) {
359 auto cloneRegionIntoAccRegion = [&](
Region *src,
Region *dest,
364 b.setInsertionPoint(terminator);
366 acc::YieldOp::create(
b, op.getLoc(), terminator->
getOperands());
368 acc::YieldOp::create(
b, op.getLoc(),
ValueRange{});
373 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>)
374 b.setInsertionPointToStart(®ion.
front());
375 else if constexpr (std::is_same_v<AccOpTy, acc::LoopOp>)
376 b.setInsertionPoint(op);
378 llvm_unreachable(
"unexpected acc op with reduction recipe");
382 acc::ReductionInitOp::create(
b, op.getLoc(), origPtr, reductionBounds,
383 recipe.getReductionOperatorAttr());
384 saveVarName(op.getAccVar(), reductionOp.getResult());
385 cloneRegionIntoAccRegion(&initRegion, &reductionOp.getRegion(),
387 Block *initBlock = &reductionOp.getRegion().
front();
388 resolveVarNamePlaceholders(initBlock, std::prev(initBlock->
end()),
395 Region &combinerRegion = recipe.getCombinerRegion();
396 setLocation(combinerRegion, loc);
400 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>)
402 else if constexpr (std::is_same_v<AccOpTy, acc::LoopOp>)
403 b.setInsertionPointAfter(accOp);
405 llvm_unreachable(
"unexpected acc op with reduction recipe");
413 argsRemapping.append(triples);
416 auto combineRegionOp = acc::ReductionCombineRegionOp::create(
417 b, op.getLoc(), origPtr, reductionOp.getResult());
418 cloneRegionIntoAccRegion(&combinerRegion, &combineRegionOp.getRegion(),
421 auto *ctx =
b.getContext();
427 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>) {
428 acc::GPUParallelDimsAttr parDimsAttr;
429 if (accOp.isEffectivelySerial()) {
441 auto setSeqParDimsForRecipeLoops = [&](
Region *r) {
442 r->walk([&](LoopLikeOpInterface loopLike) {
446 setSeqParDimsForRecipeLoops(&reductionOp.getRegion());
447 setSeqParDimsForRecipeLoops(&combineRegionOp.getRegion());
449 if (!recipe.getDestroyRegion().empty()) {
451 results.append(triples);
453 cloneDestroy(loc, recipe, combineRegionOp->getBlock(), ip, results);
456 llvm_unreachable(
"unexpected op type");
463template <
typename OpTy>
464LogicalResult ACCRecipeMaterialization::materializeForACCOp(
467 assert(isa<ACC_COMPUTE_CONSTRUCT_AND_LOOP_OPS>(accOp));
477 struct ReductionMapping {
482 if constexpr (!std::is_same_v<OpTy, acc::LoopOp>) {
483 for (
Value dataOperand : accOp.getDataClauseOperands()) {
484 Operation *dataOp = dataOperand.getDefiningOp();
485 if (dataOp && isa<ACC_DATA_ENTRY_OPS>(dataOp))
486 mappedReductionVars.push_back({
acc::getVar(dataOp), dataOperand});
490 auto getMappedReductionVar = [&](acc::ReductionOp reductionOp) ->
Value {
491 if constexpr (std::is_same_v<OpTy, acc::LoopOp>) {
492 return reductionOp.getVar();
494 Value originalVar = reductionOp.getVar();
495 if (isa_and_nonnull<ACC_DATA_ENTRY_OPS>(originalVar.
getDefiningOp()))
502 auto existing = llvm::find_if(mappedReductionVars,
503 [&](
const ReductionMapping &mapping) {
504 return mapping.originalVar == originalVar;
506 if (existing != mappedReductionVars.end())
507 return existing->mappedVar;
510 acc::CopyinOp copyinOp;
511 if (std::optional<StringRef> name = reductionOp.getName())
513 acc::CopyinOp::create(builder, reductionOp.getLoc(), originalVar,
515 reductionOp.getBounds());
517 copyinOp = acc::CopyinOp::create(
518 builder, reductionOp.getLoc(), originalVar,
519 true,
true, reductionOp.getBounds());
520 copyinOp.setDataClause(acc::DataClause::acc_reduction);
521 accOp.getDataClauseOperandsMutable().append(copyinOp.getAccVar());
524 acc::CopyoutOp copyoutOp;
525 if (std::optional<StringRef> name = reductionOp.getName())
526 copyoutOp = acc::CopyoutOp::create(
527 builder, reductionOp.getLoc(), copyinOp.getAccVar(), originalVar,
529 reductionOp.getBounds());
531 copyoutOp = acc::CopyoutOp::create(
532 builder, reductionOp.getLoc(), copyinOp.getAccVar(), originalVar,
533 true,
true, reductionOp.getBounds());
534 copyoutOp.setDataClause(acc::DataClause::acc_reduction);
536 mappedReductionVars.push_back({originalVar, copyinOp.getAccVar()});
537 return copyinOp.getAccVar();
541 if (!accOp.getFirstprivateOperands().empty()) {
545 accOp.getFirstprivateOperandsMutable().clear();
546 for (
Value operand : operands) {
547 auto firstprivateOp = cast<acc::FirstprivateOp>(operand.getDefiningOp());
548 auto symbolRef = cast<SymbolRefAttr>(firstprivateOp.getRecipeAttr());
550 auto recipeOp = cast<acc::FirstprivateRecipeOp>(decl);
551 LLVM_DEBUG(llvm::dbgs() <<
"materializing: " << firstprivateOp <<
"\n"
552 << symbolRef <<
"\n");
553 handleInitialValueMapping(firstprivateOp);
555 materialize(firstprivateOp, recipeOp, accOp, accSupport, policy)))
560 if (!accOp.getPrivateOperands().empty()) {
564 accOp.getPrivateOperandsMutable().clear();
565 for (
Value operand : operands) {
566 auto privateOp = cast<acc::PrivateOp>(operand.getDefiningOp());
567 auto symbolRef = cast<SymbolRefAttr>(privateOp.getRecipeAttr());
569 auto recipeOp = cast<acc::PrivateRecipeOp>(decl);
570 LLVM_DEBUG(llvm::dbgs() <<
"materializing: " << privateOp <<
"\n"
571 << symbolRef <<
"\n");
572 if (readsVar(recipeOp.getInitRegion()) ||
573 readsVar(recipeOp.getDestroyRegion()))
574 handleInitialValueMapping(privateOp);
575 if (failed(materialize(privateOp, recipeOp, accOp, accSupport, policy)))
580 if (!accOp.getReductionOperands().empty()) {
584 accOp.getReductionOperandsMutable().clear();
585 for (
Value operand : operands) {
586 auto reductionOp = cast<acc::ReductionOp>(operand.getDefiningOp());
587 auto symbolRef = cast<SymbolRefAttr>(reductionOp.getRecipeAttr());
589 auto recipeOp = cast<acc::ReductionRecipeOp>(decl);
590 LLVM_DEBUG(llvm::dbgs() <<
"materializing: " << reductionOp <<
"\n"
591 << symbolRef <<
"\n");
592 Value mappedVar = getMappedReductionVar(reductionOp);
593 if (failed(materialize(reductionOp, recipeOp, accOp, accSupport, policy,
601void ACCRecipeMaterialization::runOnOperation() {
602 ModuleOp moduleOp = getOperation();
608 bool anyFailed =
false;
613 [&](
auto constructOp) {
614 if (failed(materializeForACCOp(constructOp, accSupport, policy)))
627 std::optional<llvm::DenseSet<StringAttr>> usedSymbols;
630 if (std::optional<SymbolTable::UseRange> uses =
632 usedSymbols.emplace();
634 usedSymbols->insert(use.getSymbolRef().getLeafReference());
638 if (
auto recipe = dyn_cast<acc::ReductionRecipeOp>(op))
639 removeRecipe(recipe, moduleOp, usedSymbols);
640 else if (
auto recipe = dyn_cast<acc::PrivateRecipeOp>(op))
641 removeRecipe(recipe, moduleOp, usedSymbols);
642 else if (
auto recipe = dyn_cast<acc::FirstprivateRecipeOp>(op))
643 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.
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 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