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
57#include "mlir/IR/Block.h"
58#include "mlir/IR/Builders.h"
59#include "mlir/IR/IRMapping.h"
60#include "mlir/IR/SymbolTable.h"
61#include "mlir/IR/Value.h"
62#include "mlir/IR/ValueRange.h"
64#include "mlir/Support/LLVM.h"
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"
71
72namespace mlir {
73namespace acc {
74#define GEN_PASS_DEF_ACCRECIPEMATERIALIZATION
75#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
76} // namespace acc
77} // namespace mlir
78
79#define DEBUG_TYPE "acc-recipe-materialization"
80
81namespace {
82
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 void runOnOperation() override;
177
178private:
179 // When the recipe reads the original variable, its initial value needs to be
180 // available on the GPU. One way to get that value there is to map the
181 // variable through global memory.
182 // Thus, when we materialize a firstprivate, we materialize it into
183 // a mapping action first. This function ends up with doing the following:
184 // %dev = acc.firstprivate var(%var)
185 // =>
186 // %copy = acc.firstprivate_map var(%var)
187 // %dev = acc.firstprivate var(%copy)
188 // When the recipe materialization happens, the `acc.firstprivate` ends up
189 // being removed. But because of the way we chain it to the
190 // `acc.firstprivate_map`, then its result becomes live-in to the
191 // compute region and used as the variable the initial value is loaded from.
192 template <typename OpTy>
193 void handleInitialValueMapping(OpTy op) const;
194 template <typename OpTy>
195 void removeRecipe(
196 OpTy op, ModuleOp moduleOp,
197 const std::optional<llvm::DenseSet<StringAttr>> &usedSymbols) const;
198 template <typename OpTy, typename RecipeOpTy, typename AccOpTy>
199 LogicalResult materialize(OpTy op, RecipeOpTy recipe, AccOpTy accOp,
200 acc::OpenACCSupport &accSupport,
202 Value materializationVar = {}) const;
203 template <typename OpTy>
204 LogicalResult materializeForACCOp(OpTy accOp, acc::OpenACCSupport &accSupport,
205 acc::ACCToGPUMappingPolicy &policy) const;
206};
207
208template <typename OpTy>
209void ACCRecipeMaterialization::handleInitialValueMapping(OpTy op) const {
210 OpBuilder builder(op);
211 auto mapInitialOp = acc::FirstprivateMapInitialOp::create(
212 builder, op.getLoc(), op.getVar(), op.getStructured(), op.getImplicit(),
213 op.getBounds());
214 mapInitialOp.setName(op.getName());
215 op.getVarMutable().assign(mapInitialOp.getAccVar());
216}
217
218// Whether a recipe region reads the variable it privatizes - a descriptor
219// recipe loads it for the bounds, while a scalar one ignores it. Both init and
220// destroy receive it as their first argument.
221static bool readsVar(Region &region) {
222 if (region.empty() || region.getNumArguments() == 0)
223 return false;
224 return !region.getArgument(0).use_empty();
225}
226
227template <typename OpTy>
228void ACCRecipeMaterialization::removeRecipe(
229 OpTy op, ModuleOp moduleOp,
230 const std::optional<llvm::DenseSet<StringAttr>> &usedSymbols) const {
231 auto recipeName = op.getNameAttr();
232 // Fall back to scanning the module when the symbol uses could not be
233 // gathered up front.
234 bool useEmpty = usedSymbols
235 ? !usedSymbols->contains(recipeName)
236 : SymbolTable::symbolKnownUseEmpty(recipeName, moduleOp);
237 if (useEmpty) {
238 LLVM_DEBUG(llvm::dbgs() << "erasing recipe: " << recipeName << "\n");
239 op.erase();
240 } else {
241 LLVM_DEBUG({
242 std::optional<SymbolTable::UseRange> symbolUses =
243 op.getSymbolUses(moduleOp);
244 if (symbolUses.has_value()) {
245 for (SymbolTable::SymbolUse symbolUse : *symbolUses) {
246 llvm::dbgs() << "symbol use: ";
247 symbolUse.getUser()->dump();
248 }
249 }
250 });
251 llvm_unreachable("expected no use of recipe symbol");
252 }
253}
254
255template <typename OpTy, typename RecipeOpTy, typename AccOpTy>
256LogicalResult ACCRecipeMaterialization::materialize(
257 OpTy op, RecipeOpTy recipe, AccOpTy accOp, acc::OpenACCSupport &accSupport,
258 acc::ACCToGPUMappingPolicy &policy, Value materializationVar) const {
259 Region &region = accOp.getRegion();
260 Value origPtr = materializationVar ? materializationVar : op.getVar();
261 Value accPtr = op.getAccVar();
262 assert(accPtr && "invalid op: null acc var");
263
264 OpBuilder b(op);
265 SmallVector<Value> triples;
266
267 // Clone init block into the region at the insertion point specified.
268 Region &initRegion = recipe.getInitRegion();
269 unsigned initNumArguments =
270 initRegion.getBlocks().front().getArguments().size();
271 if (initNumArguments > 1) {
272 // Code from C/C++ will most likely only provide extent arguments to the
273 // recipe arguments.
274 if ((initNumArguments - 1) % 3 != 0) {
275 (void)accSupport.emitNYI(recipe.getLoc(),
276 "privatization of array section with extents");
277 return failure();
278 }
279 // The remaining arguments must be the bounds triples
280 // (lower-bound, upper-bound, step), ...
281 unsigned argIdx = 1;
282 // Cast the given value to the type of the combiner region's argument
283 // at position argIdx, and increment argIdx.
284 auto castValueToArgType = [&](Location loc, Value v) {
286 b, loc, v,
287 initRegion.getBlocks().front().getArgument(argIdx++).getType(),
288 /*isUnsignedCast=*/false);
289 };
290 for (Value bound : acc::getBounds(op)) {
291 auto dataBound = bound.getDefiningOp<acc::DataBoundsOp>();
292 assert(dataBound &&
293 "acc.reduction's bound must be defined by acc.bounds");
294 // NOTE: we should probably generate get_lowerbound, get_upperbound
295 // and get_stride here, so that we can stop looking for the acc.bounds
296 // operation above, and just use the `bound` value.
297 Value lb =
298 castValueToArgType(dataBound.getLoc(), dataBound.getLowerbound());
299 Value ub =
300 castValueToArgType(dataBound.getLoc(), dataBound.getUpperbound());
301 Value step =
302 castValueToArgType(dataBound.getLoc(), dataBound.getStride());
303 triples.append({lb, ub, step});
304 }
305 assert(triples.size() + 1 == initNumArguments &&
306 "mismatch between number bounds and number of recipe init block "
307 "arguments");
308 }
309
310 IRMapping mapping;
311 SmallVector<Value> initArgs{origPtr};
312 initArgs.append(triples);
313 mapping.map(initRegion.getBlocks().front().getArguments(), initArgs);
314
315 Location loc = op.getLoc();
316 setLocation(initRegion, loc);
317
318 if constexpr (std::is_same_v<OpTy, acc::PrivateOp>) {
319 // Clone the init region for a private.
320 Block *block = &region.front();
321 auto [results, ip] = acc::cloneACCRegionInto(
322 &initRegion, block, block->begin(), mapping, {accPtr});
323 assert(!results.empty() && "expected a result from init region");
324 saveVarName(op.getAccVar(), results[0]);
325 resolveVarNamePlaceholders(block, ip, acc::getVariableName(op.getAccVar()));
326 // Clone the destroy region for a private, if it exists.
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);
331 }
332 } else if constexpr (std::is_same_v<OpTy, acc::FirstprivateOp>) {
333 // Clone the init region for a firstprivate.
334 Block *block = &region.front();
335 auto [results, ip] = acc::cloneACCRegionInto(
336 &initRegion, block, block->begin(), mapping, {accPtr});
337 assert(!results.empty() && "expected a result from init region");
338 saveVarName(op.getAccVar(), results[0]);
339 resolveVarNamePlaceholders(block, ip, acc::getVariableName(op.getAccVar()));
340 // The copy only consumes the original and user-visible private value.
341 SmallVector<Value> copyArgs{origPtr, results.front()};
342 copyArgs.append(triples);
343 // Destruction also consumes any cleanup values yielded by init.
344 SmallVector<Value> destroyArgs{origPtr};
345 destroyArgs.append(results);
346 destroyArgs.append(triples);
347
348 // Clone the copy region for a firstprivate
349 mapping.clear();
350 mapping.map(recipe.getCopyRegion().front().getArguments(), copyArgs);
351 // Clone the copy region for a firstprivate.
352 Region &copyRegion = recipe.getCopyRegion();
353 setLocation(copyRegion, loc);
354 acc::cloneACCRegionInto(&copyRegion, block, std::next(ip), mapping, {});
355 if (!recipe.getDestroyRegion().empty()) {
356 cloneDestroy(loc, recipe, block, std::prev(block->end()), destroyArgs);
357 }
358 } else if constexpr (std::is_same_v<OpTy, acc::ReductionOp>) {
359 auto cloneRegionIntoAccRegion = [&](Region *src, Region *dest,
360 bool hasResult) {
361 src->cloneInto(dest, mapping);
362 Block *block = &dest->front();
363 Operation *terminator = block->getTerminator();
364 b.setInsertionPoint(terminator);
365 if (hasResult)
366 acc::YieldOp::create(b, op.getLoc(), terminator->getOperands());
367 else
368 acc::YieldOp::create(b, op.getLoc(), ValueRange{});
369 terminator->erase();
370 };
371
372 // Clone the init region into acc.reduction_init.
373 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>)
374 b.setInsertionPointToStart(&region.front());
375 else if constexpr (std::is_same_v<AccOpTy, acc::LoopOp>)
376 b.setInsertionPoint(op);
377 else
378 llvm_unreachable("unexpected acc op with reduction recipe");
379
380 SmallVector<Value> reductionBounds(acc::getBounds(op));
381 auto reductionOp =
382 acc::ReductionInitOp::create(b, op.getLoc(), origPtr, reductionBounds,
383 recipe.getReductionOperatorAttr());
384 saveVarName(op.getAccVar(), reductionOp.getResult());
385 cloneRegionIntoAccRegion(&initRegion, &reductionOp.getRegion(),
386 /*hasResult=*/true);
387 Block *initBlock = &reductionOp.getRegion().front();
388 resolveVarNamePlaceholders(initBlock, std::prev(initBlock->end()),
389 acc::getVariableName(op.getAccVar()));
390
391 // Update the uses within the loop to use the reduction op result.
392 replaceAllUsesInRegionWith(accPtr, reductionOp.getResult(), region);
393
394 // Clone the combiner region into acc.reduction_combine_region.
395 Region &combinerRegion = recipe.getCombinerRegion();
396 setLocation(combinerRegion, loc);
397
398 Block *entryBlock = &combinerRegion.front();
399
400 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>)
401 b.setInsertionPoint(region.back().getTerminator());
402 else if constexpr (std::is_same_v<AccOpTy, acc::LoopOp>)
403 b.setInsertionPointAfter(accOp);
404 else
405 llvm_unreachable("unexpected acc op with reduction recipe");
406
407 // Map the first two block arguments to the original and private
408 // reduction variables. If the recipe's combiner region has the bounds
409 // arguments, we have to map them to the corresponding operands of
410 // acc.reduction operation.
411 mapping.clear();
412 SmallVector<Value, 2> argsRemapping{origPtr, reductionOp.getResult()};
413 argsRemapping.append(triples);
414 mapping.map(entryBlock->getArguments(), argsRemapping);
415
416 auto combineRegionOp = acc::ReductionCombineRegionOp::create(
417 b, op.getLoc(), origPtr, reductionOp.getResult());
418 cloneRegionIntoAccRegion(&combinerRegion, &combineRegionOp.getRegion(),
419 /*hasResult=*/false);
420
421 auto *ctx = b.getContext();
422
423 // For reductions that come from parallel constructs, explicitly set the
424 // GPU parallel dimensions attribute to blockXDim since they will always be
425 // gang private. GPU parallel dimensions cannot be determined for acc.loop
426 // at this point.
427 if constexpr (std::is_same_v<AccOpTy, acc::ParallelOp>) {
428 acc::GPUParallelDimsAttr parDimsAttr;
429 if (accOp.isEffectivelySerial()) {
430 // If acc.serial has been lowered to a parallel op that is effectively
431 // sequential
432 parDimsAttr = acc::getSeqParDimsAttr(ctx, policy);
433 } else {
434 parDimsAttr = acc::getGangDim1ParDimsAttr(ctx, policy);
435 }
436 acc::setParDimsAttr(reductionOp, parDimsAttr);
437 acc::setParDimsAttr(combineRegionOp, parDimsAttr);
438 }
439
440 // Set sequential parallel dimensions attribute for loops in the recipe.
441 auto setSeqParDimsForRecipeLoops = [&](Region *r) {
442 r->walk([&](LoopLikeOpInterface loopLike) {
443 acc::setParDimsAttr(loopLike, acc::getSeqParDimsAttr(ctx, policy));
444 });
445 };
446 setSeqParDimsForRecipeLoops(&reductionOp.getRegion());
447 setSeqParDimsForRecipeLoops(&combineRegionOp.getRegion());
448
449 if (!recipe.getDestroyRegion().empty()) {
450 SmallVector<Value> results{origPtr, reductionOp.getResult()};
451 results.append(triples);
452 Block::iterator ip = std::next(Block::iterator(combineRegionOp));
453 cloneDestroy(loc, recipe, combineRegionOp->getBlock(), ip, results);
454 }
455 } else {
456 llvm_unreachable("unexpected op type");
457 }
458
459 op.erase();
460 return success();
461}
462
463template <typename OpTy>
464LogicalResult ACCRecipeMaterialization::materializeForACCOp(
465 OpTy accOp, acc::OpenACCSupport &accSupport,
466 acc::ACCToGPUMappingPolicy &policy) const {
467 assert(isa<ACC_COMPUTE_CONSTRUCT_AND_LOOP_OPS>(accOp));
468
469 // Reduction recipes use the original variable both to initialize the
470 // private reduction value and to combine the result. Preserve copy semantics
471 // when materializing reductions on compute constructs, before the
472 // acc.reduction operation carrying that intent is erased. Loop reductions
473 // are excluded: their original variable can be an outer private reduction
474 // value rather than a host variable. Keep acc.reduction referring to its
475 // original host variable and pass the mapped value to materialization
476 // separately.
477 struct ReductionMapping {
478 Value originalVar;
479 Value mappedVar;
480 };
481 SmallVector<ReductionMapping> mappedReductionVars;
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});
487 }
488 }
489
490 auto getMappedReductionVar = [&](acc::ReductionOp reductionOp) -> Value {
491 if constexpr (std::is_same_v<OpTy, acc::LoopOp>) {
492 return reductionOp.getVar();
493 } else {
494 Value originalVar = reductionOp.getVar();
495 if (isa_and_nonnull<ACC_DATA_ENTRY_OPS>(originalVar.getDefiningOp()))
496 return originalVar;
497
498 // Note that we do not require matching bounds here.
499 // Bounds may be represented by different SSA values while evaluating to
500 // the same values at runtime. Data clauses for the same variable are
501 // expected to specify matching bounds.
502 auto existing = llvm::find_if(mappedReductionVars,
503 [&](const ReductionMapping &mapping) {
504 return mapping.originalVar == originalVar;
505 });
506 if (existing != mappedReductionVars.end())
507 return existing->mappedVar;
508
509 OpBuilder builder(reductionOp);
510 acc::CopyinOp copyinOp;
511 if (std::optional<StringRef> name = reductionOp.getName())
512 copyinOp =
513 acc::CopyinOp::create(builder, reductionOp.getLoc(), originalVar,
514 /*structured=*/true, /*implicit=*/true, *name,
515 reductionOp.getBounds());
516 else
517 copyinOp = acc::CopyinOp::create(
518 builder, reductionOp.getLoc(), originalVar,
519 /*structured=*/true, /*implicit=*/true, reductionOp.getBounds());
520 copyinOp.setDataClause(acc::DataClause::acc_reduction);
521 accOp.getDataClauseOperandsMutable().append(copyinOp.getAccVar());
522
523 builder.setInsertionPointAfter(accOp);
524 acc::CopyoutOp copyoutOp;
525 if (std::optional<StringRef> name = reductionOp.getName())
526 copyoutOp = acc::CopyoutOp::create(
527 builder, reductionOp.getLoc(), copyinOp.getAccVar(), originalVar,
528 /*structured=*/true, /*implicit=*/true, *name,
529 reductionOp.getBounds());
530 else
531 copyoutOp = acc::CopyoutOp::create(
532 builder, reductionOp.getLoc(), copyinOp.getAccVar(), originalVar,
533 /*structured=*/true, /*implicit=*/true, reductionOp.getBounds());
534 copyoutOp.setDataClause(acc::DataClause::acc_reduction);
535
536 mappedReductionVars.push_back({originalVar, copyinOp.getAccVar()});
537 return copyinOp.getAccVar();
538 }
539 };
540
541 if (!accOp.getFirstprivateOperands().empty()) {
542 // Clear the firstprivate operands list so there will be no uses after
543 // the recipe is materialized.
544 SmallVector<Value> operands(accOp.getFirstprivateOperands());
545 accOp.getFirstprivateOperandsMutable().clear();
546 for (Value operand : operands) {
547 auto firstprivateOp = cast<acc::FirstprivateOp>(operand.getDefiningOp());
548 auto symbolRef = cast<SymbolRefAttr>(firstprivateOp.getRecipeAttr());
549 auto decl = SymbolTable::lookupNearestSymbolFrom(accOp, symbolRef);
550 auto recipeOp = cast<acc::FirstprivateRecipeOp>(decl);
551 LLVM_DEBUG(llvm::dbgs() << "materializing: " << firstprivateOp << "\n"
552 << symbolRef << "\n");
553 handleInitialValueMapping(firstprivateOp);
554 if (failed(
555 materialize(firstprivateOp, recipeOp, accOp, accSupport, policy)))
556 return failure();
557 }
558 }
559
560 if (!accOp.getPrivateOperands().empty()) {
561 // Clear the private operands list so there will be no uses after
562 // the recipe is materialized.
563 SmallVector<Value> operands(accOp.getPrivateOperands());
564 accOp.getPrivateOperandsMutable().clear();
565 for (Value operand : operands) {
566 auto privateOp = cast<acc::PrivateOp>(operand.getDefiningOp());
567 auto symbolRef = cast<SymbolRefAttr>(privateOp.getRecipeAttr());
568 auto decl = SymbolTable::lookupNearestSymbolFrom(accOp, symbolRef);
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)))
576 return failure();
577 }
578 }
579
580 if (!accOp.getReductionOperands().empty()) {
581 // Clear the reduction operands list so there will be no uses after
582 // the recipe is materialized.
583 SmallVector<Value> operands(accOp.getReductionOperands());
584 accOp.getReductionOperandsMutable().clear();
585 for (Value operand : operands) {
586 auto reductionOp = cast<acc::ReductionOp>(operand.getDefiningOp());
587 auto symbolRef = cast<SymbolRefAttr>(reductionOp.getRecipeAttr());
588 auto decl = SymbolTable::lookupNearestSymbolFrom(accOp, symbolRef);
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,
594 mappedVar)))
595 return failure();
596 }
597 }
598 return success();
599}
600
601void ACCRecipeMaterialization::runOnOperation() {
602 ModuleOp moduleOp = getOperation();
603 acc::OpenACCSupport &accSupport = getAnalysis<acc::OpenACCSupport>();
604
606
607 // Materialize all recipes for all compute constructs and loop constructs.
608 bool anyFailed = false;
609 moduleOp.walk([&](Operation *op) {
610 if (anyFailed)
611 return;
613 [&](auto constructOp) {
614 if (failed(materializeForACCOp(constructOp, accSupport, policy)))
615 anyFailed = true;
616 });
617 });
618 if (anyFailed) {
619 signalPassFailure();
620 return;
621 }
622
623 // Remove all recipes. Gather the symbol uses that are left with a single
624 // walk: asking whether each recipe is still referenced walks the whole module
625 // again for every recipe, and recipes are generated per type, so there can be
626 // many of them.
627 std::optional<llvm::DenseSet<StringAttr>> usedSymbols;
628 // The module region, not the module op, is the symbol table scope: asking
629 // for the uses on the op itself would not walk into the body.
630 if (std::optional<SymbolTable::UseRange> uses =
631 SymbolTable::getSymbolUses(&moduleOp.getBodyRegion())) {
632 usedSymbols.emplace();
633 for (const SymbolTable::SymbolUse &use : *uses)
634 usedSymbols->insert(use.getSymbolRef().getLeafReference());
635 }
636
637 moduleOp.walk([&](Operation *op) {
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);
644 });
645}
646
647} // 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
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:512
Attribute removeDiscardableAttr(StringAttr name)
Remove the discardable attribute with the specified name if it exists.
Definition Operation.h:524
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
AttrClass getDiscardableAttrOfType(StringRef name)
Access a discardable attribute by name and cast it to AttrClass.
Definition Operation.h:493
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:389
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:65
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:5368
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:5419
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:216
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:244
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:139