MLIR 24.0.0git
OpenACCUtils.cpp
Go to the documentation of this file.
1//===- OpenACCUtils.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
10
14#include "mlir/IR/BuiltinOps.h"
16#include "mlir/IR/Dominance.h"
17#include "mlir/IR/SymbolTable.h"
20#include "llvm/ADT/STLExtras.h"
21#include "llvm/ADT/SetVector.h"
22#include "llvm/ADT/TypeSwitch.h"
23#include "llvm/IR/Intrinsics.h"
24#include "llvm/Support/Casting.h"
25
27 return region
28 .getParentOfType<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::ComputeRegionOp>();
29}
30
32 auto barg = mlir::dyn_cast<mlir::BlockArgument>(v);
33 if (!barg)
34 return nullptr;
35
36 mlir::Block *block = barg.getOwner();
37 auto computeReg =
38 mlir::dyn_cast<mlir::acc::ComputeRegionOp>(block->getParentOp());
39 if (!computeReg)
40 return nullptr;
41 assert(block == computeReg.getBody() &&
42 "block must be the body of acc.compute_region");
43 return computeReg.getOperand(barg);
44}
45
48 if (!orig)
49 return nullptr;
50 mlir::Operation *def = orig.getDefiningOp();
51 return mlir::isa_and_nonnull<ACC_DATA_ENTRY_OPS>(def) ? def : nullptr;
52}
53
54template <typename OpTy>
56 auto checkIfUsedOnlyByOpInside = [&](mlir::Operation *user) {
57 // For any users which are not in the current acc region, we can ignore.
58 // Return true so that it can be used in a `all_of` check.
59 if (!region.isAncestor(user->getParentRegion()))
60 return true;
61 return mlir::isa<OpTy>(user);
62 };
63
64 return llvm::all_of(val.getUsers(), checkIfUsedOnlyByOpInside);
65}
66
71
76
77std::optional<mlir::acc::ClauseDefaultValue>
79 std::optional<mlir::acc::ClauseDefaultValue> defaultAttr;
80 Operation *currOp = op;
81
82 // Iterate outwards until a default clause is found (since OpenACC
83 // specification notes that a visible default clause is the nearest default
84 // clause appearing on the compute construct or a lexically containing data
85 // construct.
86 while (!defaultAttr.has_value() && currOp) {
87 defaultAttr =
89 std::optional<mlir::acc::ClauseDefaultValue>>(currOp)
90 .Case<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::DataOp>(
91 [&](auto op) { return op.getDefaultAttr(); })
92 .Default([&](Operation *) { return std::nullopt; });
93 currOp = currOp->getParentOp();
94 }
95
96 return defaultAttr;
97}
98
99mlir::acc::VariableTypeCategory mlir::acc::getTypeCategory(mlir::Value var) {
100 mlir::acc::VariableTypeCategory typeCategory =
101 mlir::acc::VariableTypeCategory::uncategorized;
102 if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(var.getType()))
103 typeCategory = mappableTy.getTypeCategory(var);
104 else if (auto pointerLikeTy =
105 dyn_cast<mlir::acc::PointerLikeType>(var.getType()))
106 typeCategory = pointerLikeTy.getPointeeTypeCategory(
108 pointerLikeTy.getElementType());
109 return typeCategory;
110}
111
112llvm::StringLiteral mlir::acc::getVarNamePlaceholder() {
113 return llvm::StringLiteral("<acc.varname.placeholder>");
114}
115
117 Value current = v;
118
119 // Walk through view operations until a name is found or can't go further
120 while (Operation *definingOp = current.getDefiningOp()) {
121 // For integer constants, return their value as a string.
122 if (std::optional<int64_t> constVal = getConstantIntValue(current))
123 return std::to_string(*constVal);
124
125 // Check for `acc.var_name` attribute
126 if (auto varNameAttr = definingOp->getDiscardableAttrOfType<VarNameAttr>(
128 return varNameAttr.getName().str();
129
130 // If it is a data entry operation, get name via getVarName
131 if (isa<ACC_DATA_ENTRY_OPS, MapInfoOp>(definingOp))
132 if (auto name = acc::getVarName(definingOp))
133 return name->str();
134
135 // A global goes by the symbol it is addressed through.
136 if (auto addressOf = dyn_cast<AddressOfGlobalOpInterface>(definingOp))
137 return addressOf.getSymbol().getLeafReference().str();
138
139 // If it's a view operation, continue to the source
140 if (auto viewOp = dyn_cast<ViewLikeOpInterface>(definingOp)) {
141 current = viewOp.getViewSource();
142 continue;
143 }
144
145 break;
146 }
147
148 return "";
149}
150
151std::string mlir::acc::getRecipeName(mlir::acc::RecipeKind kind,
152 mlir::Type type) {
153 assert(kind == mlir::acc::RecipeKind::private_recipe ||
154 kind == mlir::acc::RecipeKind::firstprivate_recipe ||
155 kind == mlir::acc::RecipeKind::reduction_recipe);
156 if (!llvm::isa<mlir::acc::PointerLikeType, mlir::acc::MappableType>(type))
157 return "";
158
159 std::string recipeName;
160 llvm::raw_string_ostream ss(recipeName);
161 ss << (kind == mlir::acc::RecipeKind::private_recipe ? "privatization_"
162 : kind == mlir::acc::RecipeKind::firstprivate_recipe
163 ? "firstprivatization_"
164 : "reduction_");
165
166 // Print the type using its dialect-defined textual format.
167 type.print(ss);
168 ss.flush();
169
170 // Replace invalid characters (anything that's not a letter, number, or
171 // period) since this needs to be a valid MLIR identifier.
172 for (char &c : recipeName) {
173 if (!std::isalnum(static_cast<unsigned char>(c)) && c != '.' && c != '_') {
174 if (c == '?')
175 c = 'U';
176 else if (c == '*')
177 c = 'Z';
178 else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' ||
179 c == '}' || c == '<' || c == '>')
180 c = '_';
181 else
182 c = 'X';
183 }
184 }
185
186 return recipeName;
187}
188
190 if (auto partialEntityAccessOp =
191 val.getDefiningOp<PartialEntityAccessOpInterface>()) {
192 if (!partialEntityAccessOp.isCompleteView())
193 return partialEntityAccessOp.getBaseEntity();
194 }
195
196 return val;
197}
198
199/// Look up `symbol` in the `gpu.module`s of the enclosing module. A
200/// `gpu.module` is its own symbol table, so `lookupNearestSymbolFrom` from a
201/// user outside of it does not find definitions placed inside.
203 mlir::SymbolRefAttr symbol) {
204 auto moduleOp = user->getParentOfType<mlir::ModuleOp>();
205 if (!moduleOp)
206 return nullptr;
207 for (auto gpuModule : moduleOp.getOps<mlir::gpu::GPUModuleOp>()) {
209 gpuModule, symbol.getRootReference()))
210 return op;
211 }
212 return nullptr;
213}
214
216 mlir::SymbolRefAttr symbol,
217 mlir::Operation **definingOpPtr) {
218 // A pass may prepare the device-side definition of a symbol inside a
219 // `gpu.module` while the use is still a reference from host IR. Such a
220 // symbol is meant to be used on device, so the use is valid.
221 if (mlir::Operation *gpuOp = lookupSymbolInGPUModules(user, symbol)) {
222 if (definingOpPtr)
223 *definingOpPtr = gpuOp;
224 return true;
225 }
226
227 mlir::Operation *definingOp =
229
230 // If there are no defining ops, we have no way to ensure validity because
231 // we cannot check for any attributes.
232 if (!definingOp)
233 return false;
234
235 if (definingOpPtr)
236 *definingOpPtr = definingOp;
237
238 // Check if the defining op is a recipe.
239 // Recipes are valid as they get materialized before being offloaded to
240 // device. They are only instructions for how to materialize.
241 if (mlir::isa<mlir::accomp::RecipeInterface>(definingOp))
242 return true;
243
244 // Check if the defining op is a global variable that is device data.
245 // Device data is already resident on the device and does not need mapping.
246 if (auto globalVar =
247 mlir::dyn_cast<mlir::acc::GlobalVariableOpInterface>(definingOp))
248 if (globalVar.isDeviceData())
249 return true;
250
251 // Check if the defining op is a function
252 if (auto func =
253 mlir::dyn_cast_if_present<mlir::FunctionOpInterface>(definingOp)) {
254 // If this symbol is actually an acc routine or a specialized acc routine -
255 // then it is expected for it to be offloaded - therefore it is valid.
256 if (func->hasDiscardableAttr(mlir::acc::getRoutineInfoAttrName()) ||
257 func->hasDiscardableAttr(mlir::acc::getSpecializedRoutineAttrName()))
258 return true;
259
260 // If this symbol is a call to an LLVM intrinsic, then it is likely valid.
261 // Check the following:
262 // 1. The function is private
263 // 2. The function has no body
264 // 3. Name starts with "llvm."
265 // 4. The function's name is a valid LLVM intrinsic name
266 if (func.getVisibility() == mlir::SymbolTable::Visibility::Private &&
267 func.getFunctionBody().empty() && func.getName().starts_with("llvm.") &&
268 llvm::Intrinsic::lookupIntrinsicID(func.getName()) !=
269 llvm::Intrinsic::not_intrinsic)
270 return true;
271 }
272
273 // A declare attribute is needed for symbol references.
274 bool hasDeclare =
276 return hasDeclare;
277}
278
280 // Check if the value is device data via type interfaces.
281 // Device data is already resident on the device and does not need mapping.
282 if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(val.getType()))
283 if (mappableTy.isDeviceData(val))
284 return true;
285
286 if (auto pointerLikeTy = dyn_cast<mlir::acc::PointerLikeType>(val.getType()))
287 if (pointerLikeTy.isDeviceData(val))
288 return true;
289
290 mlir::Operation *defOp = val.getDefiningOp();
291 if (!defOp)
292 return false;
293
294 // `acc.declare` with deviceptr marks data that is already associated with
295 // the device.
296 if (auto declareAttr =
297 defOp->getDiscardableAttrOfType<mlir::acc::DeclareAttr>(
299 if (declareAttr.getDataClause().getValue() ==
300 mlir::acc::DataClause::acc_deviceptr)
301 return true;
302
303 // Handle operations that access a partial entity - check if the base entity
304 // is device data.
305 if (auto partialAccess =
306 dyn_cast<mlir::acc::PartialEntityAccessOpInterface>(defOp)) {
307 if (mlir::Value base = partialAccess.getBaseEntity())
308 return isDeviceValue(base);
309 }
310
311 // Handle address_of - check if the referenced global is device data.
312 if (auto addrOfIface =
313 dyn_cast<mlir::acc::AddressOfGlobalOpInterface>(defOp)) {
314 auto symbol = addrOfIface.getSymbol();
316 mlir::acc::GlobalVariableOpInterface>(defOp, symbol))
317 return global.isDeviceData();
318 }
319
320 return false;
321}
322
324 // Types that can be passed by value are legal.
325 Type type = val.getType();
326 if (type.isIntOrIndexOrFloat() || isa<mlir::ComplexType>(type) ||
327 llvm::isa<mlir::VectorType>(type))
328 return true;
329
330 // If this is produced by an ACC data entry operation, it is valid.
331 if (isa_and_nonnull<ACC_DATA_ENTRY_OPS>(val.getDefiningOp()))
332 return true;
333
334 // If the value is only used by private clauses, it is not a live-in.
335 if (isOnlyUsedByPrivateClauses(val, region))
336 return true;
337
338 // If this is device data, it is valid.
339 if (isDeviceValue(val))
340 return true;
341
342 // Arguments of an enclosing acc routine are already on the device.
343 if (mlir::Operation *parent = region.getParentOp()) {
344 if (auto func = parent->getParentOfType<mlir::FunctionOpInterface>()) {
347 llvm::is_contained(func.getArguments(), val))
348 return true;
349 }
350 }
351
352 return false;
353}
354
357 mlir::DominanceInfo &domInfo,
358 mlir::PostDominanceInfo &postDomInfo) {
359 llvm::SmallSetVector<mlir::Value, 8> dominatingDataClauses;
360
361 llvm::TypeSwitch<mlir::Operation *>(computeConstructOp)
362 .Case<mlir::acc::ParallelOp, mlir::acc::KernelsOp, mlir::acc::SerialOp>(
363 [&](auto op) {
364 for (auto dataClause : op.getDataClauseOperands()) {
365 dominatingDataClauses.insert(dataClause);
366 }
367 })
368 .Default([](mlir::Operation *) {});
369
370 // Collect the data clauses from enclosing data constructs.
371 mlir::Operation *currParentOp = computeConstructOp->getParentOp();
372 while (currParentOp) {
373 if (mlir::isa<mlir::acc::DataOp>(currParentOp)) {
374 for (auto dataClause : mlir::dyn_cast<mlir::acc::DataOp>(currParentOp)
375 .getDataClauseOperands()) {
376 dominatingDataClauses.insert(dataClause);
377 }
378 }
379 currParentOp = currParentOp->getParentOp();
380 }
381
382 // Find the enclosing function/subroutine
383 auto funcOp =
384 computeConstructOp->getParentOfType<mlir::FunctionOpInterface>();
385 if (!funcOp)
386 return dominatingDataClauses.takeVector();
387
388 // Walk the function to find `acc.declare_enter`/`acc.declare_exit` pairs that
389 // dominate and post-dominate the compute construct and add their data
390 // clauses to the list.
391 funcOp->walk([&](mlir::acc::DeclareEnterOp declareEnterOp) {
392 if (domInfo.dominates(declareEnterOp.getOperation(), computeConstructOp)) {
393 // Collect all `acc.declare_exit` ops for this token.
395 for (auto *user : declareEnterOp.getToken().getUsers())
396 if (auto declareExit = mlir::dyn_cast<mlir::acc::DeclareExitOp>(user))
397 exits.push_back(declareExit);
398
399 // Only add clauses if every `acc.declare_exit` op post-dominates the
400 // compute construct.
401 if (!exits.empty() &&
402 llvm::all_of(exits, [&](mlir::acc::DeclareExitOp exitOp) {
403 return postDomInfo.postDominates(exitOp, computeConstructOp);
404 })) {
405 for (auto dataClause : declareEnterOp.getDataClauseOperands())
406 dominatingDataClauses.insert(dataClause);
407 }
408 }
409 });
410
411 return dominatingDataClauses.takeVector();
412}
413
416 const std::function<std::string()> &messageFn,
417 llvm::StringRef category) {
418 using namespace mlir::remark;
419 mlir::Location loc = op->getLoc();
420 auto *engine = loc->getContext()->getRemarkEngine();
421 if (!engine)
423
424 llvm::StringRef funcName;
425 if (auto func = dyn_cast<mlir::FunctionOpInterface>(op))
426 funcName = func.getName();
427 else if (auto funcOp = op->getParentOfType<mlir::FunctionOpInterface>())
428 funcName = funcOp.getName();
429
430 auto opts = RemarkOpts::name("openacc").category(category);
431 if (!funcName.empty())
432 opts = opts.function(funcName);
433
434 auto remark = engine->emitOptimizationRemark(loc, opts);
435 if (remark)
436 remark << messageFn();
437 return remark;
438}
static bool isOnlyUsedByOpClauses(mlir::Value val, mlir::Region &region)
static mlir::Operation * lookupSymbolInGPUModules(mlir::Operation *user, mlir::SymbolRefAttr symbol)
Look up symbol in the gpu.modules of the enclosing module.
static std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
MLIRContext * getContext() const
Return the context this attribute belongs to.
Block represents an ordered list of Operations.
Definition Block.h:34
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
A class for computing basic dominance information.
Definition Dominance.h:143
bool dominates(Operation *a, Operation *b) const
Return true if operation A dominates operation B, i.e.
Definition Dominance.h:161
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
remark::detail::RemarkEngine * getRemarkEngine()
Returns the remark engine for this context, or nullptr if none has been set.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
bool hasDiscardableAttr(StringRef name)
Return true if this operation has a discardable attribute with the provided name.
Definition Operation.h:503
AttrClass getDiscardableAttrOfType(StringRef name)
Access a discardable attribute by name and cast it to AttrClass.
Definition Operation.h:493
A class for computing basic postdominance information.
Definition Dominance.h:207
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
bool isAncestor(Region *other)
Return true if this region is ancestor of the other region.
Definition Region.h:234
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:198
ParentT getParentOfType()
Find the first parent operation of the given type, or nullptr if there is no ancestor operation.
Definition Region.h:206
@ Private
The symbol is private and may only be referenced by SymbolRefAttrs local to the operations within the...
Definition SymbolTable.h:91
static Operation * lookupSymbolIn(Operation *op, StringAttr symbol)
Returns the operation registered with the given symbol name with the regions of 'symbolTableOp'.
static Operation * lookupNearestSymbolFrom(Operation *from, StringAttr symbol)
Returns the operation registered with the given symbol name within the closest parent operation of,...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
void print(raw_ostream &os) const
Print the current type.
bool isIntOrIndexOrFloat() const
Return true if this is an integer (of any signedness), index, or float type.
Definition Types.cpp:122
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
user_range getUsers() const
Definition Value.h:218
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
A wrapper for linking remarks by query - searches the engine's registry at stream time and links to a...
Definition Remarks.h:402
#define ACC_COMPUTE_CONSTRUCT_OPS
Definition OpenACC.h:63
mlir::acc::VariableTypeCategory getTypeCategory(mlir::Value var)
Get the type category of an OpenACC variable.
std::string getVariableName(mlir::Value v)
Attempts to extract the variable name from a value by walking through view-like operations until an a...
bool isValidSymbolUse(mlir::Operation *user, mlir::SymbolRefAttr symbol, mlir::Operation **definingOpPtr=nullptr)
Check if a symbol use is valid for use in an OpenACC region.
bool isAccRoutine(mlir::Operation *op)
Used to check whether the current operation is marked with acc routine.
Definition OpenACC.h:195
static constexpr StringLiteral getSpecializedRoutineAttrName()
Definition OpenACC.h:189
mlir::Value getACCOperandForBlockArg(mlir::Value v)
If v is not a block argument of an acc.compute_region body, returns nullptr.
mlir::Operation * getACCDataClauseOpForBlockArg(mlir::Value v)
If v is not a block argument of an acc.compute_region body, returns nullptr.
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 isSpecializedAccRoutine(mlir::Operation *op)
Used to check whether this is a specialized accelerator version of acc routine function.
Definition OpenACC.h:201
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:5465
static constexpr StringLiteral getRoutineInfoAttrName()
Definition OpenACC.h:185
bool isValidValueUse(mlir::Value val, mlir::Region &region)
Check if a value use is valid in an OpenACC region.
mlir::Operation * getEnclosingComputeOp(mlir::Region &region)
Used to obtain the enclosing compute construct operation that contains the provided region.
llvm::StringLiteral getVarNamePlaceholder()
Returns a placeholder string for use as an acc.var_name attribute value when the actual variable name...
llvm::SmallVector< mlir::Value > getDominatingDataClauses(mlir::Operation *computeConstructOp, mlir::DominanceInfo &domInfo, mlir::PostDominanceInfo &postDomInfo)
Collects all data clauses that dominate the compute construct.
static constexpr StringLiteral getVarNameAttrName()
Definition OpenACC.h:216
std::string getRecipeName(mlir::acc::RecipeKind kind, mlir::Type type)
Get the recipe name for a given recipe kind and type.
remark::detail::InFlightRemark emitRemark(mlir::Operation *op, const std::function< std::string()> &messageFn, llvm::StringRef category="openacc")
Emit an OpenACC remark with lazy message generation.
static constexpr StringLiteral getDeclareAttrName()
Used to obtain the attribute name for declare.
Definition OpenACC.h:177
bool isDeviceValue(mlir::Value val)
Check if a value represents device data.
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.
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:494
static RemarkOpts name(StringRef n)
Definition Remarks.h:105