MLIR 24.0.0git
ACCRecipeMaterialization.cpp
Go to the documentation of this file.
1//===- ACCRecipeMaterialization.cpp - Materialize ACC recipes -------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Overview:
10// ---------
11// OpenACC compute constructs (acc.parallel, acc.serial, acc.kernels) and
12// acc.loop can carry data clauses (acc.private, acc.firstprivate,
13// acc.reduction) that refer to recipes (acc.private.recipe,
14// acc.firstprivate.recipe, acc.reduction.recipe). Recipes define how to
15// initialize, copy, combine, or destroy a particular variable. This pass clones
16// those regions into the construct and ensures the materialized SSA values are
17// used instead.
18//
19// Transforms:
20// -----------
21// 1. Firstprivate: Inserts acc.firstprivate_map so the initial value is
22// available on the device, then clones the recipe init and copy regions
23// into the construct and replaces uses with the materialized alloca.
24// Optional destroy region is cloned before the region terminator.
25//
26// 2. Private: Clones the recipe init region into the construct (at the
27// region entry or at the loop op for acc.loop private). Replaces uses
28// of the recipe result with the materialized alloca. Optional destroy
29// region is cloned before the region terminator.
30//
31// 3. Reduction: Creates acc.reduction_init (init region inlined) and
32// acc.reduction_combine_region (combiner region inlined). Uses within
33// the region are updated to the reduction init result.
34// In addition, creates appropriate acc.copyin/copyout around
35// the compute region; the reduction's initial value is taken
36// from the copied in variable, and the final reduction value
37// is copied out to the variable. If there are existing
38// data operations for the reduction variable, no new data
39// operations are added.
40//
41// Requirements:
42// -------------
43// 1. OpenACCSupport: The pass uses the `acc::OpenACCSupport` analysis
44// including emitNYI for unsupported cases.
45//
46//===----------------------------------------------------------------------===//
47
58#include "mlir/IR/Block.h"
59#include "mlir/IR/Builders.h"
60#include "mlir/IR/IRMapping.h"
61#include "mlir/IR/SymbolTable.h"
62#include "mlir/IR/Value.h"
63#include "mlir/IR/ValueRange.h"
65#include "mlir/Support/LLVM.h"
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"
72
73namespace mlir {
74namespace acc {
75#define GEN_PASS_DEF_ACCRECIPEMATERIALIZATION
76#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
77} // namespace acc
78} // namespace mlir
79
80#define DEBUG_TYPE "acc-recipe-materialization"
81
82namespace {
83using namespace mlir;
84
85static void setLocation(Region &region, Location loc) {
86 // Since recipes are generated per type and not per variable, the location
87 // of the recipe operations which get inlined will not necessarily be the
88 // same as the location of the op that is being materialized. Force an update
89 // of the location of the recipe operations to the location of the op that is
90 // being materialized.
91 region.walk([&](Operation *op) { op->setLoc(loc); });
92}
93
94static void saveVarName(StringRef name, Value dst) {
95 if (name.empty())
96 return;
97 if (Operation *dstOp = dst.getDefiningOp()) {
98 if (dstOp->getDiscardableAttrOfType<acc::VarNameAttr>(
100 return;
101 if (isa<ACC_DATA_ENTRY_OPS>(dstOp))
102 return;
103 dstOp->setDiscardableAttr(acc::getVarNameAttrName(),
104 acc::VarNameAttr::get(dstOp->getContext(), name));
105 return;
106 }
107 auto blockArg = dyn_cast<BlockArgument>(dst);
108 if (!blockArg)
109 return;
110 Block *block = blockArg.getOwner();
111 Region *region = block ? block->getParent() : nullptr;
112 if (!region || !block->isEntryBlock())
113 return;
114 Operation *parent = region->getParentOp();
115 if (!parent)
116 return;
117 auto funcOp = dyn_cast<FunctionOpInterface>(parent);
118 if (!funcOp)
119 return;
120 unsigned argIdx = blockArg.getArgNumber();
121 if (argIdx >= funcOp.getNumArguments())
122 return;
123 if (funcOp.getArgAttr(argIdx, acc::getVarNameAttrName()))
124 return;
125 funcOp.setArgAttr(argIdx, acc::getVarNameAttrName(),
126 acc::VarNameAttr::get(parent->getContext(), name));
127}
128
129static void saveVarName(Value src, Value dst) {
130 saveVarName(acc::getVariableName(src), dst);
131}
132
133static void resolveVarNamePlaceholders(Block *block, Block::iterator ip,
134 StringRef name) {
135 StringRef placeholder = acc::getVarNamePlaceholder();
136 for (auto it = block->begin(); it != std::next(ip); ++it) {
137 it->walk([&](Operation *op) {
138 auto attr = op->getDiscardableAttrOfType<acc::VarNameAttr>(
140 if (!attr || attr.getName() != placeholder)
141 return;
142 if (name.empty())
144 else
146 acc::VarNameAttr::get(op->getContext(), name));
147 });
148 }
149}
150
151// Clone the destroy region of the recipe before the terminator of the provided
152// block. Values must be provided for the destroy region block arguments
153// according to the recipe specifications.
154template <typename RecipeOpTy>
155static void cloneDestroy(Location loc, RecipeOpTy recipe, mlir::Block *block,
157 const llvm::SmallVector<mlir::Value> &arguments) {
158 IRMapping mapping{};
159 Region &destroyRegion = recipe.getDestroyRegion();
160 assert(destroyRegion.getBlocks().front().getNumArguments() ==
161 arguments.size() &&
162 "unexpected acc recipe destroy block arguments");
163
164 setLocation(destroyRegion, loc);
165
166 mapping.map(destroyRegion.getBlocks().front().getArguments(), arguments);
167 acc::cloneACCRegionInto(&destroyRegion, block, ip, mapping,
168 /*resultsToReplace=*/{});
169}
170
171class ACCRecipeMaterialization
172 : public acc::impl::ACCRecipeMaterializationBase<ACCRecipeMaterialization> {
173public:
175 ACCRecipeMaterialization>::ACCRecipeMaterializationBase;
176
177 void getDependentDialects(DialectRegistry &registry) const override {
179 ACCRecipeMaterialization>::getDependentDialects(registry);
181 }
182 void runOnOperation() override;
183
184private:
185 // When the recipe reads the original variable, its initial value needs to be
186 // available on the GPU. One way to get that value there is to map the
187 // variable through global memory.
188 // Thus, when we materialize a firstprivate, we materialize it into
189 // a mapping action first. This function ends up with doing the following:
190 // %dev = acc.firstprivate var(%var)
191 // =>
192 // %copy = acc.firstprivate_map var(%var)
193 // %dev = acc.firstprivate var(%copy)
194 // When the recipe materialization happens, the `acc.firstprivate` ends up
195 // being removed. But because of the way we chain it to the
196 // `acc.firstprivate_map`, then its result becomes live-in to the
197 // compute region and used as the variable the initial value is loaded from.
198 template <typename OpTy>
199 void handleInitialValueMapping(OpTy op) const;
200 template <typename OpTy>
201 void removeRecipe(
202 OpTy op, ModuleOp moduleOp,
203 const std::optional<llvm::DenseSet<StringAttr>> &usedSymbols) const;
204 template <typename OpTy, typename RecipeOpTy, typename AccOpTy>
205 LogicalResult materialize(OpTy op, RecipeOpTy recipe, AccOpTy accOp,
206 acc::OpenACCSupport &accSupport,
208 Value materializationVar = {}) const;
209 template <typename OpTy>
210 LogicalResult materializeForACCOp(OpTy accOp, acc::OpenACCSupport &accSupport,
211 acc::ACCToGPUMappingPolicy &policy) const;
212};
213
214template <typename OpTy>
215void ACCRecipeMaterialization::handleInitialValueMapping(OpTy op) const {
216 OpBuilder builder(op);
217 auto mapInitialOp = acc::FirstprivateMapInitialOp::create(
218 builder, op.getLoc(), op.getVar(), op.getStructured(), op.getImplicit(),
219 op.getBounds());
220 mapInitialOp.setName(op.getName());
221 op.getVarMutable().assign(mapInitialOp.getAccVar());
222}
223
224// Whether a recipe region reads the variable it privatizes - a descriptor
225// recipe loads it for the bounds, while a scalar one ignores it. Both init and
226// destroy receive it as their first argument.
227static bool readsVar(Region &region) {
228 if (region.empty() || region.getNumArguments() == 0)
229 return false;
230 return !region.getArgument(0).use_empty();
231}
232
233template <typename OpTy>
234void ACCRecipeMaterialization::removeRecipe(
235 OpTy op, ModuleOp moduleOp,
236 const std::optional<llvm::DenseSet<StringAttr>> &usedSymbols) const {
237 auto recipeName = op.getNameAttr();
238 // Fall back to scanning the module when the symbol uses could not be
239 // gathered up front.
240 bool useEmpty = usedSymbols
241 ? !usedSymbols->contains(recipeName)
242 : SymbolTable::symbolKnownUseEmpty(recipeName, moduleOp);
243 if (useEmpty) {
244 LLVM_DEBUG(llvm::dbgs() << "erasing recipe: " << recipeName << "\n");
245 op.erase();
246 } else {
247 LLVM_DEBUG({
248 std::optional<SymbolTable::UseRange> symbolUses =
249 op.getSymbolUses(moduleOp);
250 if (symbolUses.has_value()) {
251 for (SymbolTable::SymbolUse symbolUse : *symbolUses) {
252 llvm::dbgs() << "symbol use: ";
253 symbolUse.getUser()->dump();
254 }
255 }
256 });
257 llvm_unreachable("expected no use of recipe symbol");
258 }
259}
260
261template <typename OpTy, typename RecipeOpTy, typename AccOpTy>
262LogicalResult ACCRecipeMaterialization::materialize(
263 OpTy op, RecipeOpTy recipe, AccOpTy accOp, acc::OpenACCSupport &accSupport,
264 acc::ACCToGPUMappingPolicy &policy, Value materializationVar) const {
265 Region &region = accOp.getRegion();
266 Value origPtr = materializationVar ? materializationVar : op.getVar();
267 Value accPtr = op.getAccVar();
268 assert(accPtr && "invalid op: null acc var");
269
270 OpBuilder b(op);
271 SmallVector<Value> triples;
272
273 // Clone init block into the region at the insertion point specified.
274 Region &initRegion = recipe.getInitRegion();
275 unsigned initNumArguments =
276 initRegion.getBlocks().front().getArguments().size();
277 if (initNumArguments > 1) {
278 // Code from C/C++ will most likely only provide extent arguments to the
279 // recipe arguments.
280 if ((initNumArguments - 1) % 3 != 0) {
281 (void)accSupport.emitNYI(recipe.getLoc(),
282 "privatization of array section with extents");
283 return failure();
284 }
285 // The remaining arguments must be the bounds triples
286 // (lower-bound, upper-bound, step), ...
287 unsigned argIdx = 1;
288 // Cast the given value to the type of the combiner region's argument
289 // at position argIdx, and increment argIdx.
290 auto castValueToArgType = [&](Location loc, Value v) {
292 b, loc, v,
293 initRegion.getBlocks().front().getArgument(argIdx++).getType(),
294 /*isUnsignedCast=*/false);
295 };
296 for (Value bound : acc::getBounds(op)) {
297 auto dataBound = bound.getDefiningOp<acc::DataBoundsOp>();
298 assert(dataBound &&
299 "acc.reduction's bound must be defined by acc.bounds");
300 // NOTE: we should probably generate get_lowerbound, get_upperbound
301 // and get_stride here, so that we can stop looking for the acc.bounds
302 // operation above, and just use the `bound` value.
303 Value lb =
304 castValueToArgType(dataBound.getLoc(), dataBound.getLowerbound());
305 Value ub =
306 castValueToArgType(dataBound.getLoc(), dataBound.getUpperbound());
307 Value step =
308 castValueToArgType(dataBound.getLoc(), dataBound.getStride());
309 triples.append({lb, ub, step});
310 }
311 assert(triples.size() + 1 == initNumArguments &&
312 "mismatch between number bounds and number of recipe init block "
313 "arguments");
314 }
315
316 IRMapping mapping;
317 SmallVector<Value> initArgs{origPtr};
318 initArgs.append(triples);
319 mapping.map(initRegion.getBlocks().front().getArguments(), initArgs);
320
321 Location loc = op.getLoc();
322 setLocation(initRegion, loc);
323
324 if constexpr (std::is_same_v<OpTy, acc::PrivateOp>) {
325 // Clone the init region for a private.
326 Block *block = &region.front();
327 auto [results, ip] = acc::cloneACCRegionInto(
328 &initRegion, block, block->begin(), mapping, {accPtr});
329 assert(!results.empty() && "expected a result from init region");
330 saveVarName(op.getAccVar(), results[0]);
331 resolveVarNamePlaceholders(block, ip, acc::getVariableName(op.getAccVar()));
332 // Clone the destroy region for a private, if it exists.
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);
337 }
338 } else if constexpr (std::is_same_v<OpTy, acc::FirstprivateOp>) {
339 // Clone the init region for a firstprivate.
340 Block *block = &region.front();
341 auto [results, ip] = acc::cloneACCRegionInto(
342 &initRegion, block, block->begin(), mapping, {accPtr});
343 assert(!results.empty() && "expected a result from init region");
344 saveVarName(op.getAccVar(), results[0]);
345 resolveVarNamePlaceholders(block, ip, acc::getVariableName(op.getAccVar()));
346 // The copy only consumes the original and user-visible private value.
347 SmallVector<Value> copyArgs{origPtr, results.front()};
348 copyArgs.append(triples);
349 // Destruction also consumes any cleanup values yielded by init.
350 SmallVector<Value> destroyArgs{origPtr};
351 destroyArgs.append(results);
352 destroyArgs.append(triples);
353
354 // Clone the copy region for a firstprivate
355 mapping.clear();
356 mapping.map(recipe.getCopyRegion().front().getArguments(), copyArgs);
357 // Clone the copy region for a firstprivate.
358 Region &copyRegion = recipe.getCopyRegion();
359 setLocation(copyRegion, loc);
360 acc::cloneACCRegionInto(&copyRegion, block, std::next(ip), mapping, {});
361 if (!recipe.getDestroyRegion().empty()) {
362 cloneDestroy(loc, recipe, block, std::prev(block->end()), destroyArgs);
363 }
364 } else if constexpr (std::is_same_v<OpTy, acc::ReductionOp>) {
365 auto cloneRegionIntoAccRegion = [&](Region *src, Region *dest,
366 bool hasResult) {
367 src->cloneInto(dest, mapping);
368 Block *block = &dest->front();
369 Operation *terminator = block->getTerminator();
370 b.setInsertionPoint(terminator);
371 if (hasResult)
372 acc::YieldOp::create(b, op.getLoc(), terminator->getOperands());
373 else
374 acc::YieldOp::create(b, op.getLoc(), ValueRange{});
375 terminator->erase();
376 };
377
378 // Clone the init region into acc.reduction_init.
379 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>)
380 b.setInsertionPointToStart(&region.front());
381 else if constexpr (std::is_same_v<AccOpTy, acc::LoopOp>)
382 b.setInsertionPoint(op);
383 else
384 llvm_unreachable("unexpected acc op with reduction recipe");
385
386 SmallVector<Value> reductionBounds(acc::getBounds(op));
387 auto reductionOp =
388 acc::ReductionInitOp::create(b, op.getLoc(), origPtr, reductionBounds,
389 recipe.getReductionOperatorAttr());
390 saveVarName(op.getAccVar(), reductionOp.getResult());
391 cloneRegionIntoAccRegion(&initRegion, &reductionOp.getRegion(),
392 /*hasResult=*/true);
393 Block *initBlock = &reductionOp.getRegion().front();
394 resolveVarNamePlaceholders(initBlock, std::prev(initBlock->end()),
395 acc::getVariableName(op.getAccVar()));
396
397 // Update the uses within the loop to use the reduction op result.
398 replaceAllUsesInRegionWith(accPtr, reductionOp.getResult(), region);
399
400 // Clone the combiner region into acc.reduction_combine_region.
401 Region &combinerRegion = recipe.getCombinerRegion();
402 setLocation(combinerRegion, loc);
403
404 Block *entryBlock = &combinerRegion.front();
405
406 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>)
407 b.setInsertionPoint(region.back().getTerminator());
408 else if constexpr (std::is_same_v<AccOpTy, acc::LoopOp>)
409 b.setInsertionPointAfter(accOp);
410 else
411 llvm_unreachable("unexpected acc op with reduction recipe");
412
413 // Map the first two block arguments to the original and private
414 // reduction variables. If the recipe's combiner region has the bounds
415 // arguments, we have to map them to the corresponding operands of
416 // acc.reduction operation.
417 mapping.clear();
418 SmallVector<Value, 2> argsRemapping{origPtr, reductionOp.getResult()};
419 argsRemapping.append(triples);
420 mapping.map(entryBlock->getArguments(), argsRemapping);
421
422 auto combineRegionOp = acc::ReductionCombineRegionOp::create(
423 b, op.getLoc(), origPtr, reductionOp.getResult());
424 cloneRegionIntoAccRegion(&combinerRegion, &combineRegionOp.getRegion(),
425 /*hasResult=*/false);
426
427 auto *ctx = b.getContext();
428
429 // For reductions that come from parallel constructs, explicitly set the
430 // GPU parallel dimensions attribute to blockXDim since they will always be
431 // gang private. GPU parallel dimensions cannot be determined for acc.loop
432 // at this point.
433 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>) {
434 acc::GPUParallelDimsAttr parDimsAttr;
435 if (accOp.isEffectivelySerial()) {
436 // If acc.serial has been lowered to a parallel op that is effectively
437 // sequential
438 parDimsAttr = acc::getSeqParDimsAttr(ctx, policy);
439 } else {
440 parDimsAttr = acc::getGangDim1ParDimsAttr(ctx, policy);
441 }
442 acc::setParDimsAttr(reductionOp, parDimsAttr);
443 acc::setParDimsAttr(combineRegionOp, parDimsAttr);
444 }
445
446 // Set sequential parallel dimensions attribute for loops in the recipe.
447 auto setSeqParDimsForRecipeLoops = [&](Region *r) {
448 r->walk([&](LoopLikeOpInterface loopLike) {
449 acc::setParDimsAttr(loopLike, acc::getSeqParDimsAttr(ctx, policy));
450 });
451 };
452 setSeqParDimsForRecipeLoops(&reductionOp.getRegion());
453 setSeqParDimsForRecipeLoops(&combineRegionOp.getRegion());
454
455 if (!recipe.getDestroyRegion().empty()) {
456 SmallVector<Value> results{origPtr, reductionOp.getResult()};
457 results.append(triples);
458 Block::iterator ip = std::next(Block::iterator(combineRegionOp));
459 cloneDestroy(loc, recipe, combineRegionOp->getBlock(), ip, results);
460 }
461 } else {
462 llvm_unreachable("unexpected op type");
463 }
464
465 op.erase();
466 return success();
467}
468
469template <typename OpTy>
470LogicalResult ACCRecipeMaterialization::materializeForACCOp(
471 OpTy accOp, acc::OpenACCSupport &accSupport,
472 acc::ACCToGPUMappingPolicy &policy) const {
473 assert(isa<ACC_COMPUTE_CONSTRUCT_AND_LOOP_OPS>(accOp));
474
475 // Reduction recipes use the original variable both to initialize the
476 // private reduction value and to combine the result. Preserve copy semantics
477 // when materializing reductions on compute constructs, before the
478 // acc.reduction operation carrying that intent is erased. Loop reductions
479 // are excluded: their original variable can be an outer private reduction
480 // value rather than a host variable. Keep acc.reduction referring to its
481 // original host variable and pass the mapped value to materialization
482 // separately.
483 struct ReductionMapping {
484 Value originalVar;
485 Value mappedVar;
486 };
487 SmallVector<ReductionMapping> mappedReductionVars;
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});
493 }
494 }
495
496 auto getMappedReductionVar = [&](acc::ReductionOp reductionOp) -> Value {
497 if constexpr (std::is_same_v<OpTy, acc::LoopOp>) {
498 return reductionOp.getVar();
499 } else {
500 Value originalVar = reductionOp.getVar();
501 if (isa_and_nonnull<ACC_DATA_ENTRY_OPS>(originalVar.getDefiningOp()))
502 return originalVar;
503
504 // Note that we do not require matching bounds here.
505 // Bounds may be represented by different SSA values while evaluating to
506 // the same values at runtime. Data clauses for the same variable are
507 // expected to specify matching bounds.
508 auto existing = llvm::find_if(mappedReductionVars,
509 [&](const ReductionMapping &mapping) {
510 return mapping.originalVar == originalVar;
511 });
512 if (existing != mappedReductionVars.end())
513 return existing->mappedVar;
514
515 OpBuilder builder(reductionOp);
516 acc::CopyinOp copyinOp;
517 if (std::optional<StringRef> name = reductionOp.getName())
518 copyinOp =
519 acc::CopyinOp::create(builder, reductionOp.getLoc(), originalVar,
520 /*structured=*/true, /*implicit=*/true, *name,
521 reductionOp.getBounds());
522 else
523 copyinOp = acc::CopyinOp::create(
524 builder, reductionOp.getLoc(), originalVar,
525 /*structured=*/true, /*implicit=*/true, reductionOp.getBounds());
526 copyinOp.setDataClause(acc::DataClause::acc_reduction);
527 accOp.getDataClauseOperandsMutable().append(copyinOp.getAccVar());
528
529 builder.setInsertionPointAfter(accOp);
530 acc::CopyoutOp copyoutOp;
531 if (std::optional<StringRef> name = reductionOp.getName())
532 copyoutOp = acc::CopyoutOp::create(
533 builder, reductionOp.getLoc(), copyinOp.getAccVar(), originalVar,
534 /*structured=*/true, /*implicit=*/true, *name,
535 reductionOp.getBounds());
536 else
537 copyoutOp = acc::CopyoutOp::create(
538 builder, reductionOp.getLoc(), copyinOp.getAccVar(), originalVar,
539 /*structured=*/true, /*implicit=*/true, reductionOp.getBounds());
540 copyoutOp.setDataClause(acc::DataClause::acc_reduction);
541
542 mappedReductionVars.push_back({originalVar, copyinOp.getAccVar()});
543 return copyinOp.getAccVar();
544 }
545 };
546
547 if (!accOp.getFirstprivateOperands().empty()) {
548 // Clear the firstprivate operands list so there will be no uses after
549 // the recipe is materialized.
550 SmallVector<Value> operands(accOp.getFirstprivateOperands());
551 accOp.getFirstprivateOperandsMutable().clear();
552 for (Value operand : operands) {
553 auto firstprivateOp = cast<acc::FirstprivateOp>(operand.getDefiningOp());
554 auto symbolRef = cast<SymbolRefAttr>(firstprivateOp.getRecipeAttr());
555 auto decl = SymbolTable::lookupNearestSymbolFrom(accOp, symbolRef);
556 auto recipeOp = cast<acc::FirstprivateRecipeOp>(decl);
557 LLVM_DEBUG(llvm::dbgs() << "materializing: " << firstprivateOp << "\n"
558 << symbolRef << "\n");
559 handleInitialValueMapping(firstprivateOp);
560 if (failed(
561 materialize(firstprivateOp, recipeOp, accOp, accSupport, policy)))
562 return failure();
563 }
564 }
565
566 if (!accOp.getPrivateOperands().empty()) {
567 // Clear the private operands list so there will be no uses after
568 // the recipe is materialized.
569 SmallVector<Value> operands(accOp.getPrivateOperands());
570 accOp.getPrivateOperandsMutable().clear();
571 for (Value operand : operands) {
572 auto privateOp = cast<acc::PrivateOp>(operand.getDefiningOp());
573 auto symbolRef = cast<SymbolRefAttr>(privateOp.getRecipeAttr());
574 auto decl = SymbolTable::lookupNearestSymbolFrom(accOp, symbolRef);
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)))
582 return failure();
583 }
584 }
585
586 if (!accOp.getReductionOperands().empty()) {
587 // Clear the reduction operands list so there will be no uses after
588 // the recipe is materialized.
589 SmallVector<Value> operands(accOp.getReductionOperands());
590 accOp.getReductionOperandsMutable().clear();
591 for (Value operand : operands) {
592 auto reductionOp = cast<acc::ReductionOp>(operand.getDefiningOp());
593 auto symbolRef = cast<SymbolRefAttr>(reductionOp.getRecipeAttr());
594 auto decl = SymbolTable::lookupNearestSymbolFrom(accOp, symbolRef);
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,
600 mappedVar)))
601 return failure();
602 }
603 }
604 return success();
605}
606
607void ACCRecipeMaterialization::runOnOperation() {
608 ModuleOp moduleOp = getOperation();
609 acc::OpenACCSupport &accSupport = getAnalysis<acc::OpenACCSupport>();
610
612
613 // Materialize all recipes for all compute constructs and loop constructs.
614 bool anyFailed = false;
615 moduleOp.walk([&](Operation *op) {
616 if (anyFailed)
617 return;
619 [&](auto constructOp) {
620 if (failed(materializeForACCOp(constructOp, accSupport, policy)))
621 anyFailed = true;
622 });
623 });
624 if (anyFailed) {
625 signalPassFailure();
626 return;
627 }
628
629 // Remove all recipes. Gather the symbol uses that are left with a single
630 // walk: asking whether each recipe is still referenced walks the whole module
631 // again for every recipe, and recipes are generated per type, so there can be
632 // many of them.
633 std::optional<llvm::DenseSet<StringAttr>> usedSymbols;
634 // The module region, not the module op, is the symbol table scope: asking
635 // for the uses on the op itself would not walk into the body.
636 if (std::optional<SymbolTable::UseRange> uses =
637 SymbolTable::getSymbolUses(&moduleOp.getBodyRegion())) {
638 usedSymbols.emplace();
639 for (const SymbolTable::SymbolUse &use : *uses)
640 usedSymbols->insert(use.getSymbolRef().getLeafReference());
641 }
642
643 moduleOp.walk([&](Operation *op) {
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);
650 });
651}
652} // namespace
return success()
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
Block represents an ordered list of Operations.
Definition Block.h:34
OpListType::iterator iterator
Definition Block.h:165
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
Operation & front()
Definition Block.h:178
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgListType getArguments()
Definition Block.h:112
iterator end()
Definition Block.h:169
iterator begin()
Definition Block.h:168
bool isEntryBlock()
Return if this block is the entry block in the parent region.
Definition Block.cpp:36
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.
Definition IRMapping.h:26
void clear()
Clears all mappings held by the mapper.
Definition IRMapping.h:79
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
This class helps build Operations.
Definition Builders.h:210
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
void setLoc(Location loc)
Set the source location the operation was defined or derived from.
Definition Operation.h:243
void setDiscardableAttr(StringAttr name, Attribute value)
Set a discardable attribute by name.
Definition Operation.h:514
Attribute removeDiscardableAttr(StringAttr name)
Remove the discardable attribute with the specified name if it exists.
Definition Operation.h:526
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:405
AttrClass getDiscardableAttrOfType(StringRef name)
Access a discardable attribute by name and cast it to AttrClass.
Definition Operation.h:495
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
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.
Definition Region.h:26
Block & front()
Definition Region.h:65
Block & back()
Definition Region.h:64
bool empty()
Definition Region.h:60
void cloneInto(Region *dest, IRMapping &mapper)
Clone the internal blocks from this region into dest.
Definition Region.cpp:70
unsigned getNumArguments()
Definition Region.h:136
BlockArgument getArgument(unsigned i)
Definition Region.h:137
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:198
BlockListType & getBlocks()
Definition Region.h:45
RetT walk(FnT &&callback)
Walk all nested operations, blocks or regions (including this region), depending on the type of callb...
Definition Region.h:297
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.
Definition ValueRange.h:392
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
bool use_empty() const
Returns true if this value has no uses.
Definition Value.h:208
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
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
Definition OpenACC.h:64
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.
Definition OpenACC.cpp:5053
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.
Definition OpenACC.cpp:5104
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()
Definition OpenACC.h:246
void registerAllExtensions(DialectRegistry &registry)
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.
Definition Utils.cpp:245
void replaceAllUsesInRegionWith(Value orig, Value replacement, Region &region)
Replace all uses of orig within the given region with replacement.
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:147