MLIR 22.0.0git
ACCImplicitData.cpp
Go to the documentation of this file.
1//===- ACCImplicitData.cpp ------------------------------------------------===//
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// This pass implements the OpenACC specification for "Variables with
10// Implicitly Determined Data Attributes" (OpenACC 3.4 spec, section 2.6.2).
11//
12// Overview:
13// ---------
14// The pass automatically generates data clause operations for variables used
15// within OpenACC compute constructs (parallel, kernels, serial) that do not
16// already have explicit data clauses. The semantics follow these rules:
17//
18// 1. If there is a default(none) clause visible, no implicit data actions
19// apply.
20//
21// 2. An aggregate variable (arrays, derived types, etc.) will be treated as:
22// - In a present clause when default(present) is visible.
23// - In a copy clause otherwise.
24//
25// 3. A scalar variable will be treated as if it appears in:
26// - A copy clause if the compute construct is a kernels construct.
27// - A firstprivate clause otherwise (parallel, serial).
28//
29// Requirements:
30// -------------
31// To use this pass in a pipeline, the following requirements must be met:
32//
33// 1. Type Interface Implementation: Variables from the dialect being used
34// must implement one or both of the following MLIR interfaces:
35// `acc::MappableType` and/or `acc::PointerLikeType`
36//
37// These interfaces provide the necessary methods for the pass to:
38// - Determine variable type categories (scalar vs. aggregate)
39// - Generate appropriate bounds information
40// - Generate privatization recipes
41//
42// 2. Operation Interface Implementation: Operations that access partial
43// entities or create views should implement the following MLIR
44// interfaces: `acc::PartialEntityAccess` and/or
45// `mlir::ViewLikeOpInterface`
46//
47// These interfaces are used for proper data clause ordering, ensuring
48// that base entities are mapped before derived entities (e.g., a
49// struct is mapped before its fields, an array is mapped before
50// subarray views).
51//
52// 3. Analysis Registration (Optional): If custom behavior is needed for
53// variable name extraction or alias analysis, the dialect should
54// pre-register the `acc::OpenACCSupport` and `mlir::AliasAnalysis` analyses.
55//
56// If not registered, default behavior will be used.
57//
58// Implementation Details:
59// -----------------------
60// The pass performs the following operations:
61//
62// 1. Finds candidate variables which are live-in to the compute region and
63// are not already in a data clause or private clause.
64//
65// 2. Generates both data "entry" and "exit" clause operations that match
66// the intended action depending on variable type:
67// - copy -> acc.copyin (entry) + acc.copyout (exit)
68// - present -> acc.present (entry) + acc.delete (exit)
69// - firstprivate -> acc.firstprivate (entry only, no exit)
70//
71// 3. Ensures that default clause is taken into consideration by looking
72// through current construct and parent constructs to find the "visible
73// default clause".
74//
75// 4. Fixes up SSA value links so that uses in the acc region reference the
76// result of the newly created data clause operations.
77//
78// 5. When generating implicit data clause operations, it also adds variable
79// name information and marks them with the implicit flag.
80//
81// 6. Recipes are generated by calling the appropriate entrypoints in the
82// MappableType and PointerLikeType interfaces.
83//
84// 7. AliasAnalysis is used to determine if a variable is already covered by
85// an existing data clause (e.g., an interior pointer covered by its parent).
86//
87// Examples:
88// ---------
89//
90// Example 1: Scalar in parallel construct (implicit firstprivate)
91//
92// Before:
93// func.func @test() {
94// %scalar = memref.alloca() {acc.var_name = "x"} : memref<f32>
95// acc.parallel {
96// %val = memref.load %scalar[] : memref<f32>
97// acc.yield
98// }
99// }
100//
101// After:
102// func.func @test() {
103// %scalar = memref.alloca() {acc.var_name = "x"} : memref<f32>
104// %firstpriv = acc.firstprivate varPtr(%scalar : memref<f32>)
105// -> memref<f32> {implicit = true, name = "x"}
106// acc.parallel firstprivate(@recipe -> %firstpriv : memref<f32>) {
107// %val = memref.load %firstpriv[] : memref<f32>
108// acc.yield
109// }
110// }
111//
112// Example 2: Scalar in kernels construct (implicit copy)
113//
114// Before:
115// func.func @test() {
116// %scalar = memref.alloca() {acc.var_name = "n"} : memref<i32>
117// acc.kernels {
118// %val = memref.load %scalar[] : memref<i32>
119// acc.terminator
120// }
121// }
122//
123// After:
124// func.func @test() {
125// %scalar = memref.alloca() {acc.var_name = "n"} : memref<i32>
126// %copyin = acc.copyin varPtr(%scalar : memref<i32>) -> memref<i32>
127// {dataClause = #acc<data_clause acc_copy>,
128// implicit = true, name = "n"}
129// acc.kernels dataOperands(%copyin : memref<i32>) {
130// %val = memref.load %copyin[] : memref<i32>
131// acc.terminator
132// }
133// acc.copyout accPtr(%copyin : memref<i32>)
134// to varPtr(%scalar : memref<i32>)
135// {dataClause = #acc<data_clause acc_copy>,
136// implicit = true, name = "n"}
137// }
138//
139// Example 3: Array (aggregate) in parallel (implicit copy)
140//
141// Before:
142// func.func @test() {
143// %array = memref.alloca() {acc.var_name = "arr"} : memref<100xf32>
144// acc.parallel {
145// %c0 = arith.constant 0 : index
146// %val = memref.load %array[%c0] : memref<100xf32>
147// acc.yield
148// }
149// }
150//
151// After:
152// func.func @test() {
153// %array = memref.alloca() {acc.var_name = "arr"} : memref<100xf32>
154// %copyin = acc.copyin varPtr(%array : memref<100xf32>)
155// -> memref<100xf32>
156// {dataClause = #acc<data_clause acc_copy>,
157// implicit = true, name = "arr"}
158// acc.parallel dataOperands(%copyin : memref<100xf32>) {
159// %c0 = arith.constant 0 : index
160// %val = memref.load %copyin[%c0] : memref<100xf32>
161// acc.yield
162// }
163// acc.copyout accPtr(%copyin : memref<100xf32>)
164// to varPtr(%array : memref<100xf32>)
165// {dataClause = #acc<data_clause acc_copy>,
166// implicit = true, name = "arr"}
167// }
168//
169// Example 4: Array with default(present)
170//
171// Before:
172// func.func @test() {
173// %array = memref.alloca() {acc.var_name = "arr"} : memref<100xf32>
174// acc.parallel {
175// %c0 = arith.constant 0 : index
176// %val = memref.load %array[%c0] : memref<100xf32>
177// acc.yield
178// } attributes {defaultAttr = #acc<defaultvalue present>}
179// }
180//
181// After:
182// func.func @test() {
183// %array = memref.alloca() {acc.var_name = "arr"} : memref<100xf32>
184// %present = acc.present varPtr(%array : memref<100xf32>)
185// -> memref<100xf32>
186// {implicit = true, name = "arr"}
187// acc.parallel dataOperands(%present : memref<100xf32>)
188// attributes {defaultAttr = #acc<defaultvalue present>} {
189// %c0 = arith.constant 0 : index
190// %val = memref.load %present[%c0] : memref<100xf32>
191// acc.yield
192// }
193// acc.delete accPtr(%present : memref<100xf32>)
194// {dataClause = #acc<data_clause acc_present>,
195// implicit = true, name = "arr"}
196// }
197//
198//===----------------------------------------------------------------------===//
199
201
206#include "mlir/IR/Builders.h"
207#include "mlir/IR/BuiltinOps.h"
208#include "mlir/IR/Dominance.h"
209#include "mlir/IR/Operation.h"
210#include "mlir/IR/Value.h"
214#include "llvm/ADT/STLExtras.h"
215#include "llvm/ADT/SmallVector.h"
216#include "llvm/ADT/TypeSwitch.h"
217#include "llvm/Support/ErrorHandling.h"
218#include <type_traits>
219
220namespace mlir {
221namespace acc {
222#define GEN_PASS_DEF_ACCIMPLICITDATA
223#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
224} // namespace acc
225} // namespace mlir
226
227#define DEBUG_TYPE "acc-implicit-data"
228
229using namespace mlir;
230
231namespace {
232
233class ACCImplicitData : public acc::impl::ACCImplicitDataBase<ACCImplicitData> {
234public:
235 using acc::impl::ACCImplicitDataBase<ACCImplicitData>::ACCImplicitDataBase;
236
237 void runOnOperation() override;
238
239private:
240 /// Collects all data clauses that dominate the compute construct.
241 /// Needed to determine if a variable is already covered by an existing data
242 /// clause.
243 SmallVector<Value> getDominatingDataClauses(Operation *computeConstructOp);
244
245 /// Looks through the `dominatingDataClauses` to find the original data clause
246 /// op for an alias. Returns nullptr if no original data clause op is found.
247 template <typename OpT>
248 Operation *getOriginalDataClauseOpForAlias(
249 Value var, OpBuilder &builder, OpT computeConstructOp,
250 const SmallVector<Value> &dominatingDataClauses);
251
252 /// Generates the appropriate `acc.copyin`, `acc.present`,`acc.firstprivate`,
253 /// etc. data clause op for a candidate variable.
254 template <typename OpT>
255 Operation *generateDataClauseOpForCandidate(
256 Value var, ModuleOp &module, OpBuilder &builder, OpT computeConstructOp,
257 const SmallVector<Value> &dominatingDataClauses,
258 const std::optional<acc::ClauseDefaultValue> &defaultClause);
259
260 /// Generates the implicit data ops for a compute construct.
261 template <typename OpT>
262 void generateImplicitDataOps(
263 ModuleOp &module, OpT computeConstructOp,
264 std::optional<acc::ClauseDefaultValue> &defaultClause);
265
266 /// Generates a private recipe for a variable.
267 acc::PrivateRecipeOp generatePrivateRecipe(ModuleOp &module, Value var,
268 Location loc, OpBuilder &builder,
269 acc::OpenACCSupport &accSupport);
270
271 /// Generates a firstprivate recipe for a variable.
272 acc::FirstprivateRecipeOp
273 generateFirstprivateRecipe(ModuleOp &module, Value var, Location loc,
274 OpBuilder &builder,
275 acc::OpenACCSupport &accSupport);
276
277 /// Generates recipes for a list of variables.
278 void generateRecipes(ModuleOp &module, OpBuilder &builder,
279 Operation *computeConstructOp,
280 const SmallVector<Value> &newOperands,
281 SmallVector<Attribute> &newRecipeSyms);
282};
283
284/// Determines if a variable is a candidate for implicit data mapping.
285/// Returns true if the variable is a candidate, false otherwise.
286static bool isCandidateForImplicitData(Value val, Region &accRegion) {
287 // Ensure the variable is an allowed type for data clause.
288 if (!acc::isPointerLikeType(val.getType()) &&
290 return false;
291
292 // If this is already coming from a data clause, we do not need to generate
293 // another.
294 if (isa_and_nonnull<ACC_DATA_ENTRY_OPS>(val.getDefiningOp()))
295 return false;
296
297 // If this is only used by private clauses, it is not a real live-in.
298 if (acc::isOnlyUsedByPrivateClauses(val, accRegion))
299 return false;
300
301 return true;
302}
303
305ACCImplicitData::getDominatingDataClauses(Operation *computeConstructOp) {
306 llvm::SmallSetVector<Value, 8> dominatingDataClauses;
307
308 llvm::TypeSwitch<Operation *>(computeConstructOp)
309 .Case<acc::ParallelOp, acc::KernelsOp, acc::SerialOp>([&](auto op) {
310 for (auto dataClause : op.getDataClauseOperands()) {
311 dominatingDataClauses.insert(dataClause);
312 }
313 })
314 .Default([](Operation *) {});
315
316 // Collect the data clauses from enclosing data constructs.
317 Operation *currParentOp = computeConstructOp->getParentOp();
318 while (currParentOp) {
319 if (isa<acc::DataOp>(currParentOp)) {
320 for (auto dataClause :
321 dyn_cast<acc::DataOp>(currParentOp).getDataClauseOperands()) {
322 dominatingDataClauses.insert(dataClause);
323 }
324 }
325 currParentOp = currParentOp->getParentOp();
326 }
327
328 // Find the enclosing function/subroutine
329 auto funcOp = computeConstructOp->getParentOfType<FunctionOpInterface>();
330 if (!funcOp)
331 return dominatingDataClauses.takeVector();
332
333 // Walk the function to find `acc.declare_enter`/`acc.declare_exit` pairs that
334 // dominate and post-dominate the compute construct and add their data
335 // clauses to the list.
336 auto &domInfo = this->getAnalysis<DominanceInfo>();
337 auto &postDomInfo = this->getAnalysis<PostDominanceInfo>();
338 funcOp->walk([&](acc::DeclareEnterOp declareEnterOp) {
339 if (domInfo.dominates(declareEnterOp.getOperation(), computeConstructOp)) {
340 // Collect all `acc.declare_exit` ops for this token.
342 for (auto *user : declareEnterOp.getToken().getUsers())
343 if (auto declareExit = dyn_cast<acc::DeclareExitOp>(user))
344 exits.push_back(declareExit);
345
346 // Only add clauses if every `acc.declare_exit` op post-dominates the
347 // compute construct.
348 if (!exits.empty() && llvm::all_of(exits, [&](acc::DeclareExitOp exitOp) {
349 return postDomInfo.postDominates(exitOp, computeConstructOp);
350 })) {
351 for (auto dataClause : declareEnterOp.getDataClauseOperands())
352 dominatingDataClauses.insert(dataClause);
353 }
354 }
355 });
356
357 return dominatingDataClauses.takeVector();
358}
359
360template <typename OpT>
361Operation *ACCImplicitData::getOriginalDataClauseOpForAlias(
362 Value var, OpBuilder &builder, OpT computeConstructOp,
363 const SmallVector<Value> &dominatingDataClauses) {
364 auto &aliasAnalysis = this->getAnalysis<AliasAnalysis>();
365 for (auto dataClause : dominatingDataClauses) {
366 if (auto *dataClauseOp = dataClause.getDefiningOp()) {
367 // Only accept clauses that guarantee that the alias is present.
368 if (isa<acc::CopyinOp, acc::CreateOp, acc::PresentOp, acc::NoCreateOp,
369 acc::DevicePtrOp>(dataClauseOp))
370 if (aliasAnalysis.alias(acc::getVar(dataClauseOp), var).isMust())
371 return dataClauseOp;
372 }
373 }
374 return nullptr;
375}
376
377// Generates bounds for variables that have unknown dimensions
378static void fillInBoundsForUnknownDimensions(Operation *dataClauseOp,
379 OpBuilder &builder) {
380
381 if (!acc::getBounds(dataClauseOp).empty())
382 // If bounds are already present, do not overwrite them.
383 return;
384
385 // For types that have unknown dimensions, attempt to generate bounds by
386 // relying on MappableType being able to extract it from the IR.
387 auto var = acc::getVar(dataClauseOp);
388 auto type = var.getType();
389 if (auto mappableTy = dyn_cast<acc::MappableType>(type)) {
390 if (mappableTy.hasUnknownDimensions()) {
391 TypeSwitch<Operation *>(dataClauseOp)
392 .Case<ACC_DATA_ENTRY_OPS, ACC_DATA_EXIT_OPS>([&](auto dataClauseOp) {
393 if (std::is_same_v<decltype(dataClauseOp), acc::DevicePtrOp>)
394 return;
395 OpBuilder::InsertionGuard guard(builder);
396 builder.setInsertionPoint(dataClauseOp);
397 auto bounds = mappableTy.generateAccBounds(var, builder);
398 if (!bounds.empty())
399 dataClauseOp.getBoundsMutable().assign(bounds);
400 });
401 }
402 }
403}
404
405acc::PrivateRecipeOp
406ACCImplicitData::generatePrivateRecipe(ModuleOp &module, Value var,
407 Location loc, OpBuilder &builder,
408 acc::OpenACCSupport &accSupport) {
409 auto type = var.getType();
410 std::string recipeName =
411 accSupport.getRecipeName(acc::RecipeKind::private_recipe, type, var);
412
413 // Check if recipe already exists
414 auto existingRecipe = module.lookupSymbol<acc::PrivateRecipeOp>(recipeName);
415 if (existingRecipe)
416 return existingRecipe;
417
418 // Set insertion point to module body in a scoped way
419 OpBuilder::InsertionGuard guard(builder);
420 builder.setInsertionPointToStart(module.getBody());
421
422 auto recipe =
423 acc::PrivateRecipeOp::createAndPopulate(builder, loc, recipeName, type);
424 if (!recipe.has_value())
425 return accSupport.emitNYI(loc, "implicit private"), nullptr;
426 return recipe.value();
427}
428
429acc::FirstprivateRecipeOp
430ACCImplicitData::generateFirstprivateRecipe(ModuleOp &module, Value var,
431 Location loc, OpBuilder &builder,
432 acc::OpenACCSupport &accSupport) {
433 auto type = var.getType();
434 std::string recipeName =
435 accSupport.getRecipeName(acc::RecipeKind::firstprivate_recipe, type, var);
436
437 // Check if recipe already exists
438 auto existingRecipe =
439 module.lookupSymbol<acc::FirstprivateRecipeOp>(recipeName);
440 if (existingRecipe)
441 return existingRecipe;
442
443 // Set insertion point to module body in a scoped way
444 OpBuilder::InsertionGuard guard(builder);
445 builder.setInsertionPointToStart(module.getBody());
446
447 auto recipe = acc::FirstprivateRecipeOp::createAndPopulate(builder, loc,
448 recipeName, type);
449 if (!recipe.has_value())
450 return accSupport.emitNYI(loc, "implicit firstprivate"), nullptr;
451 return recipe.value();
452}
453
454void ACCImplicitData::generateRecipes(ModuleOp &module, OpBuilder &builder,
455 Operation *computeConstructOp,
456 const SmallVector<Value> &newOperands,
457 SmallVector<Attribute> &newRecipeSyms) {
458 auto &accSupport = this->getAnalysis<acc::OpenACCSupport>();
459 for (auto var : newOperands) {
460 auto loc{var.getLoc()};
461 if (isa<acc::PrivateOp>(var.getDefiningOp())) {
462 auto recipe = generatePrivateRecipe(
463 module, acc::getVar(var.getDefiningOp()), loc, builder, accSupport);
464 if (recipe)
465 newRecipeSyms.push_back(SymbolRefAttr::get(module->getContext(),
466 recipe.getSymName().str()));
467 } else if (isa<acc::FirstprivateOp>(var.getDefiningOp())) {
468 auto recipe = generateFirstprivateRecipe(
469 module, acc::getVar(var.getDefiningOp()), loc, builder, accSupport);
470 if (recipe)
471 newRecipeSyms.push_back(SymbolRefAttr::get(module->getContext(),
472 recipe.getSymName().str()));
473 } else {
474 accSupport.emitNYI(var.getLoc(), "implicit reduction");
475 }
476 }
477}
478
479// Generates the data entry data op clause so that it adheres to OpenACC
480// rules as follows (line numbers and specification from OpenACC 3.4):
481// 1388 An aggregate variable will be treated as if it appears either:
482// 1389 - In a present clause if there is a default(present) clause visible at
483// the compute construct.
484// 1391 - In a copy clause otherwise.
485// 1392 A scalar variable will be treated as if it appears either:
486// 1393 - In a copy clause if the compute construct is a kernels construct.
487// 1394 - In a firstprivate clause otherwise.
488template <typename OpT>
489Operation *ACCImplicitData::generateDataClauseOpForCandidate(
490 Value var, ModuleOp &module, OpBuilder &builder, OpT computeConstructOp,
491 const SmallVector<Value> &dominatingDataClauses,
492 const std::optional<acc::ClauseDefaultValue> &defaultClause) {
493 auto &accSupport = this->getAnalysis<acc::OpenACCSupport>();
494 acc::VariableTypeCategory typeCategory =
495 acc::VariableTypeCategory::uncategorized;
496 if (auto mappableTy = dyn_cast<acc::MappableType>(var.getType())) {
497 typeCategory = mappableTy.getTypeCategory(var);
498 } else if (auto pointerLikeTy =
499 dyn_cast<acc::PointerLikeType>(var.getType())) {
500 typeCategory = pointerLikeTy.getPointeeTypeCategory(
502 pointerLikeTy.getElementType());
503 }
504
505 bool isScalar =
506 acc::bitEnumContainsAny(typeCategory, acc::VariableTypeCategory::scalar);
507 bool isAnyAggregate = acc::bitEnumContainsAny(
508 typeCategory, acc::VariableTypeCategory::aggregate);
509 Location loc = computeConstructOp->getLoc();
510
511 Operation *op = nullptr;
512 op = getOriginalDataClauseOpForAlias(var, builder, computeConstructOp,
513 dominatingDataClauses);
514 if (op) {
515 if (isa<acc::NoCreateOp>(op))
516 return acc::NoCreateOp::create(builder, loc, var,
517 /*structured=*/true, /*implicit=*/true,
518 accSupport.getVariableName(var),
519 acc::getBounds(op));
520
521 if (isa<acc::DevicePtrOp>(op))
522 return acc::DevicePtrOp::create(builder, loc, var,
523 /*structured=*/true, /*implicit=*/true,
524 accSupport.getVariableName(var),
525 acc::getBounds(op));
526
527 // The original data clause op is a PresentOp, CopyinOp, or CreateOp,
528 // hence guaranteed to be present.
529 return acc::PresentOp::create(builder, loc, var,
530 /*structured=*/true, /*implicit=*/true,
531 accSupport.getVariableName(var),
532 acc::getBounds(op));
533 } else if (isScalar) {
534 if (enableImplicitReductionCopy &&
536 computeConstructOp->getRegion(0))) {
537 auto copyinOp =
538 acc::CopyinOp::create(builder, loc, var,
539 /*structured=*/true, /*implicit=*/true,
540 accSupport.getVariableName(var));
541 copyinOp.setDataClause(acc::DataClause::acc_reduction);
542 return copyinOp.getOperation();
543 }
544 if constexpr (std::is_same_v<OpT, acc::KernelsOp> ||
545 std::is_same_v<OpT, acc::KernelEnvironmentOp>) {
546 // Scalars are implicit copyin in kernels construct.
547 // We also do the same for acc.kernel_environment because semantics
548 // of user variable mappings should be applied while ACC construct exists
549 // and at this point we should only be dealing with unmapped variables
550 // that were made live-in by the compiler.
551 // TODO: This may be revisited.
552 auto copyinOp =
553 acc::CopyinOp::create(builder, loc, var,
554 /*structured=*/true, /*implicit=*/true,
555 accSupport.getVariableName(var));
556 copyinOp.setDataClause(acc::DataClause::acc_copy);
557 return copyinOp.getOperation();
558 } else {
559 // Scalars are implicit firstprivate in parallel and serial construct.
560 return acc::FirstprivateOp::create(builder, loc, var,
561 /*structured=*/true, /*implicit=*/true,
562 accSupport.getVariableName(var));
563 }
564 } else if (isAnyAggregate) {
565 Operation *newDataOp = nullptr;
566
567 // When default(present) is true, the implicit behavior is present.
568 if (defaultClause.has_value() &&
569 defaultClause.value() == acc::ClauseDefaultValue::Present) {
570 newDataOp = acc::PresentOp::create(builder, loc, var,
571 /*structured=*/true, /*implicit=*/true,
572 accSupport.getVariableName(var));
573 } else {
574 auto copyinOp =
575 acc::CopyinOp::create(builder, loc, var,
576 /*structured=*/true, /*implicit=*/true,
577 accSupport.getVariableName(var));
578 copyinOp.setDataClause(acc::DataClause::acc_copy);
579 newDataOp = copyinOp.getOperation();
580 }
581
582 return newDataOp;
583 } else {
584 // This is not a fatal error - for example when the element type is
585 // pointer type (aka we have a pointer of pointer), it is potentially a
586 // deep copy scenario which is not being handled here.
587 // Other types need to be canonicalized. Thus just log unhandled cases.
588 LLVM_DEBUG(llvm::dbgs()
589 << "Unhandled case for implicit data mapping " << var << "\n");
590 }
591 return nullptr;
592}
593
594// Ensures that result values from the acc data clause ops are used inside the
595// acc region. ie:
596// acc.kernels {
597// use %val
598// }
599// =>
600// %dev = acc.dataop %val
601// acc.kernels {
602// use %dev
603// }
604static void legalizeValuesInRegion(Region &accRegion,
605 SmallVector<Value> &newPrivateOperands,
606 SmallVector<Value> &newDataClauseOperands) {
607 for (Value dataClause :
608 llvm::concat<Value>(newDataClauseOperands, newPrivateOperands)) {
609 Value var = acc::getVar(dataClause.getDefiningOp());
610 replaceAllUsesInRegionWith(var, dataClause, accRegion);
611 }
612}
613
614// Adds the private operands and private recipes to the data construct
615// operation in a valid way (ensures that the index in the privatizationRecipes
616// array matches the position of the private operand).
617template <typename OpT>
618static void
619addNewPrivateOperands(OpT &accOp, const SmallVector<Value> &privateOperands,
620 const SmallVector<Attribute> &privateRecipeSyms) {
621 assert(privateOperands.size() == privateRecipeSyms.size());
622 if (privateOperands.empty())
623 return;
624
625 SmallVector<Attribute> completePrivateRecipesSyms;
626 SmallVector<Attribute> completeFirstprivateRecipesSyms;
627 SmallVector<Value> newPrivateOperands;
628 SmallVector<Value> newFirstprivateOperands;
629
630 // Collect all of the existing recipes since they are held in an attribute.
631 // To add to it, we need to create a brand new one.
632 if (accOp.getPrivatizationRecipes().has_value())
633 for (auto privatization : accOp.getPrivatizationRecipesAttr())
634 completePrivateRecipesSyms.push_back(privatization);
635 if (accOp.getFirstprivatizationRecipes().has_value())
636 for (auto privatization : accOp.getFirstprivatizationRecipesAttr())
637 completeFirstprivateRecipesSyms.push_back(privatization);
638
639 // Now separate between private and firstprivate operands.
640 for (auto [priv, privateRecipeSym] :
641 llvm::zip(privateOperands, privateRecipeSyms)) {
642 if (isa<acc::PrivateOp>(priv.getDefiningOp())) {
643 newPrivateOperands.push_back(priv);
644 completePrivateRecipesSyms.push_back(privateRecipeSym);
645 } else if (isa<acc::FirstprivateOp>(priv.getDefiningOp())) {
646 newFirstprivateOperands.push_back(priv);
647 completeFirstprivateRecipesSyms.push_back(privateRecipeSym);
648 } else {
649 llvm_unreachable("unhandled private operand");
650 }
651 }
652
653 // Append all of the new private operands to their appropriate list.
654 accOp.getPrivateOperandsMutable().append(newPrivateOperands);
655 accOp.getFirstprivateOperandsMutable().append(newFirstprivateOperands);
656
657 // Update the privatizationRecipes attributes to hold all of the new recipes.
658 if (!completePrivateRecipesSyms.empty())
659 accOp.setPrivatizationRecipesAttr(
660 ArrayAttr::get(accOp.getContext(), completePrivateRecipesSyms));
661 if (!completeFirstprivateRecipesSyms.empty())
662 accOp.setFirstprivatizationRecipesAttr(
663 ArrayAttr::get(accOp.getContext(), completeFirstprivateRecipesSyms));
664}
665
666static Operation *findDataExitOp(Operation *dataEntryOp) {
667 auto res = acc::getAccVar(dataEntryOp);
668 for (auto *user : res.getUsers())
669 if (isa<ACC_DATA_EXIT_OPS>(user))
670 return user;
671 return nullptr;
672}
673
674// Generates matching data exit operation as described in the acc dialect
675// for how data clauses are decomposed:
676// https://mlir.llvm.org/docs/Dialects/OpenACCDialect/#operation-categories
677// Key ones used here:
678// * acc {construct} copy -> acc.copyin (before region) + acc.copyout (after
679// region)
680// * acc {construct} present -> acc.present (before region) + acc.delete
681// (after region)
682static void
683generateDataExitOperations(OpBuilder &builder, Operation *accOp,
684 const SmallVector<Value> &newDataClauseOperands,
685 const SmallVector<Value> &sortedDataClauseOperands) {
686 builder.setInsertionPointAfter(accOp);
687 Value lastDataClause = nullptr;
688 for (auto dataEntry : llvm::reverse(sortedDataClauseOperands)) {
689 if (llvm::find(newDataClauseOperands, dataEntry) ==
690 newDataClauseOperands.end()) {
691 // If this is not a new data clause operand, we should not generate an
692 // exit operation for it.
693 lastDataClause = dataEntry;
694 continue;
695 }
696 if (lastDataClause)
697 if (auto *dataExitOp = findDataExitOp(lastDataClause.getDefiningOp()))
698 builder.setInsertionPointAfter(dataExitOp);
699 Operation *dataEntryOp = dataEntry.getDefiningOp();
700 if (isa<acc::CopyinOp>(dataEntryOp)) {
701 auto copyoutOp = acc::CopyoutOp::create(
702 builder, dataEntryOp->getLoc(), dataEntry, acc::getVar(dataEntryOp),
703 /*structured=*/true, /*implicit=*/true,
704 acc::getVarName(dataEntryOp).value(), acc::getBounds(dataEntryOp));
705 copyoutOp.setDataClause(acc::DataClause::acc_copy);
706 } else if (isa<acc::PresentOp, acc::NoCreateOp>(dataEntryOp)) {
707 auto deleteOp = acc::DeleteOp::create(
708 builder, dataEntryOp->getLoc(), dataEntry,
709 /*structured=*/true, /*implicit=*/true,
710 acc::getVarName(dataEntryOp).value(), acc::getBounds(dataEntryOp));
711 deleteOp.setDataClause(acc::getDataClause(dataEntryOp).value());
712 } else if (isa<acc::DevicePtrOp>(dataEntryOp)) {
713 // Do nothing.
714 } else {
715 llvm_unreachable("unhandled data exit");
716 }
717 lastDataClause = dataEntry;
718 }
719}
720
721/// Returns all base references of a value in order.
722/// So for example, if we have a reference to a struct field like
723/// s.f1.f2.f3, this will return <s, s.f1, s.f1.f2, s.f1.f2.f3>.
724/// Any intermediate casts/view-like operations are included in the
725/// chain as well.
726static SmallVector<Value> getBaseRefsChain(Value val) {
727 SmallVector<Value> baseRefs;
728 baseRefs.push_back(val);
729 while (true) {
730 Value prevVal = val;
731
732 val = acc::getBaseEntity(val);
733 if (val != baseRefs.front())
734 baseRefs.insert(baseRefs.begin(), val);
735
736 // If this is a view-like operation, it is effectively another
737 // view of the same entity so we should add it to the chain also.
738 if (auto viewLikeOp = val.getDefiningOp<ViewLikeOpInterface>()) {
739 val = viewLikeOp.getViewSource();
740 baseRefs.insert(baseRefs.begin(), val);
741 }
742
743 // Continue loop if we made any progress
744 if (val == prevVal)
745 break;
746 }
747
748 return baseRefs;
749}
750
751static void insertInSortedOrder(SmallVector<Value> &sortedDataClauseOperands,
752 Operation *newClause) {
753 auto *insertPos =
754 std::find_if(sortedDataClauseOperands.begin(),
755 sortedDataClauseOperands.end(), [&](Value dataClauseVal) {
756 // Get the base refs for the current clause we are looking
757 // at.
758 auto var = acc::getVar(dataClauseVal.getDefiningOp());
759 auto baseRefs = getBaseRefsChain(var);
760
761 // If the newClause is of a base ref of an existing clause,
762 // we should insert it right before the current clause.
763 // Thus return true to stop iteration when this is the
764 // case.
765 return std::find(baseRefs.begin(), baseRefs.end(),
766 acc::getVar(newClause)) != baseRefs.end();
767 });
768
769 if (insertPos != sortedDataClauseOperands.end()) {
770 newClause->moveBefore(insertPos->getDefiningOp());
771 sortedDataClauseOperands.insert(insertPos, acc::getAccVar(newClause));
772 } else {
773 sortedDataClauseOperands.push_back(acc::getAccVar(newClause));
774 }
775}
776
777template <typename OpT>
778void ACCImplicitData::generateImplicitDataOps(
779 ModuleOp &module, OpT computeConstructOp,
780 std::optional<acc::ClauseDefaultValue> &defaultClause) {
781 // Implicit data attributes are only applied if "[t]here is no default(none)
782 // clause visible at the compute construct."
783 if (defaultClause.has_value() &&
784 defaultClause.value() == acc::ClauseDefaultValue::None)
785 return;
786 assert(!defaultClause.has_value() ||
787 defaultClause.value() == acc::ClauseDefaultValue::Present);
788
789 // 1) Collect live-in values.
790 Region &accRegion = computeConstructOp->getRegion(0);
791 SetVector<Value> liveInValues;
792 getUsedValuesDefinedAbove(accRegion, liveInValues);
793
794 // 2) Run the filtering to find relevant pointers that need copied.
795 auto isCandidate{[&](Value val) -> bool {
796 return isCandidateForImplicitData(val, accRegion);
797 }};
798 auto candidateVars(
799 llvm::to_vector(llvm::make_filter_range(liveInValues, isCandidate)));
800 if (candidateVars.empty())
801 return;
802
803 // 3) Generate data clauses for the variables.
804 SmallVector<Value> newPrivateOperands;
805 SmallVector<Value> newDataClauseOperands;
806 OpBuilder builder(computeConstructOp);
807 if (!candidateVars.empty()) {
808 LLVM_DEBUG(llvm::dbgs() << "== Generating clauses for ==\n"
809 << computeConstructOp << "\n");
810 }
811 auto dominatingDataClauses = getDominatingDataClauses(computeConstructOp);
812 for (auto var : candidateVars) {
813 auto newDataClauseOp = generateDataClauseOpForCandidate(
814 var, module, builder, computeConstructOp, dominatingDataClauses,
815 defaultClause);
816 fillInBoundsForUnknownDimensions(newDataClauseOp, builder);
817 LLVM_DEBUG(llvm::dbgs() << "Generated data clause for " << var << ":\n"
818 << "\t" << *newDataClauseOp << "\n");
819 if (isa_and_nonnull<acc::PrivateOp, acc::FirstprivateOp, acc::ReductionOp>(
820 newDataClauseOp)) {
821 newPrivateOperands.push_back(acc::getAccVar(newDataClauseOp));
822 } else if (isa_and_nonnull<ACC_DATA_CLAUSE_OPS>(newDataClauseOp)) {
823 newDataClauseOperands.push_back(acc::getAccVar(newDataClauseOp));
824 dominatingDataClauses.push_back(acc::getAccVar(newDataClauseOp));
825 }
826 }
827
828 // 4) Legalize values in region (aka the uses in the region are the result
829 // of the data clause ops)
830 legalizeValuesInRegion(accRegion, newPrivateOperands, newDataClauseOperands);
831
832 SmallVector<Attribute> newPrivateRecipeSyms;
833 // 5) Generate private recipes which are required for properly attaching
834 // private operands.
835 if constexpr (!std::is_same_v<OpT, acc::KernelsOp> &&
836 !std::is_same_v<OpT, acc::KernelEnvironmentOp>)
837 generateRecipes(module, builder, computeConstructOp, newPrivateOperands,
838 newPrivateRecipeSyms);
839
840 // 6) Figure out insertion order for the new data clause operands.
841 SmallVector<Value> sortedDataClauseOperands(
842 computeConstructOp.getDataClauseOperands());
843 for (auto newClause : newDataClauseOperands)
844 insertInSortedOrder(sortedDataClauseOperands, newClause.getDefiningOp());
845
846 // 7) Generate the data exit operations.
847 generateDataExitOperations(builder, computeConstructOp, newDataClauseOperands,
848 sortedDataClauseOperands);
849
850 // 8) Add all of the new operands to the compute construct op.
851 assert(newPrivateOperands.size() == newPrivateRecipeSyms.size() &&
852 "sizes must match");
853 if constexpr (!std::is_same_v<OpT, acc::KernelsOp> &&
854 !std::is_same_v<OpT, acc::KernelEnvironmentOp>)
855 addNewPrivateOperands(computeConstructOp, newPrivateOperands,
856 newPrivateRecipeSyms);
857
858 computeConstructOp.getDataClauseOperandsMutable().assign(
859 sortedDataClauseOperands);
860}
861
862void ACCImplicitData::runOnOperation() {
863 ModuleOp module = this->getOperation();
864 module.walk([&](Operation *op) {
865 if (isa<ACC_COMPUTE_CONSTRUCT_OPS, acc::KernelEnvironmentOp>(op)) {
866 assert(op->getNumRegions() == 1 && "must have 1 region");
867
868 auto defaultClause = acc::getDefaultAttr(op);
870 .Case<ACC_COMPUTE_CONSTRUCT_OPS, acc::KernelEnvironmentOp>(
871 [&](auto op) {
872 generateImplicitDataOps(module, op, defaultClause);
873 })
874 .Default([&](Operation *) {});
875 }
876 });
877}
878
879} // namespace
#define ACC_COMPUTE_CONSTRUCT_OPS
Definition OpenACC.h:57
#define ACC_DATA_ENTRY_OPS
Definition OpenACC.h:45
#define ACC_DATA_EXIT_OPS
Definition OpenACC.h:53
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:348
This class helps build Operations.
Definition Builders.h:207
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:431
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:398
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:412
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:686
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:223
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:234
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:238
void moveBefore(Operation *existingOp)
Unlink this operation from its current block and insert it right before existingOp which may be in th...
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
InFlightDiagnostic emitNYI(Location loc, const Twine &message)
Report a case that is not yet supported by the implementation.
std::string getVariableName(Value v)
Get the variable name for a given value.
std::string getRecipeName(RecipeKind kind, Type type, Value var)
Get the recipe name for a given type and value.
mlir::Value getAccVar(mlir::Operation *accDataClauseOp)
Used to obtain the accVar from a data clause operation.
Definition OpenACC.cpp:4656
mlir::Value getVar(mlir::Operation *accDataClauseOp)
Used to obtain the var from a data clause operation.
Definition OpenACC.cpp:4625
std::optional< mlir::acc::DataClause > getDataClause(mlir::Operation *accDataEntryOp)
Used to obtain the dataClause from a data entry operation.
Definition OpenACC.cpp:4729
bool isPointerLikeType(mlir::Type type)
Used to check whether the provided type implements the PointerLikeType interface.
Definition OpenACC.h:157
mlir::SmallVector< mlir::Value > getBounds(mlir::Operation *accDataClauseOp)
Used to obtain bounds from an acc data clause operation.
Definition OpenACC.cpp:4674
std::optional< ClauseDefaultValue > getDefaultAttr(mlir::Operation *op)
Looks for an OpenACC default attribute on the current operation op or in a parent operation which enc...
bool isOnlyUsedByReductionClauses(mlir::Value val, mlir::Region &region)
Returns true if this value is only used by acc.reduction operations in the region.
std::optional< llvm::StringRef > getVarName(mlir::Operation *accOp)
Used to obtain the name from an acc operation.
Definition OpenACC.cpp:4718
bool isMappableType(mlir::Type type)
Used to check whether the provided type implements the MappableType interface.
Definition OpenACC.h:163
mlir::Value getBaseEntity(mlir::Value val)
bool isOnlyUsedByPrivateClauses(mlir::Value val, mlir::Region &region)
Returns true if this value is only used by acc.private operations in the region.
Include the generated interface declarations.
void replaceAllUsesInRegionWith(Value orig, Value replacement, Region &region)
Replace all uses of orig within the given region with replacement.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:131
std::conditional_t< std::is_same_v< Ty, mlir::Type >, mlir::Value, detail::TypedValue< Ty > > TypedValue
If Ty is mlir::Type this will select Value instead of having a wrapper around it.
Definition Value.h:497
void getUsedValuesDefinedAbove(Region &region, Region &limit, SetVector< Value > &values)
Fill values with a list of values defined at the ancestors of the limit region and used within region...
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:144