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/Dominance.h"
15#include "mlir/IR/SymbolTable.h"
18#include "llvm/ADT/SetVector.h"
19#include "llvm/ADT/TypeSwitch.h"
20#include "llvm/IR/Intrinsics.h"
21#include "llvm/Support/Casting.h"
22
24 return region
25 .getParentOfType<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::ComputeRegionOp>();
26}
27
29 auto barg = mlir::dyn_cast<mlir::BlockArgument>(v);
30 if (!barg)
31 return nullptr;
32
33 mlir::Block *block = barg.getOwner();
34 auto computeReg =
35 mlir::dyn_cast<mlir::acc::ComputeRegionOp>(block->getParentOp());
36 if (!computeReg)
37 return nullptr;
38 assert(block == computeReg.getBody() &&
39 "block must be the body of acc.compute_region");
40 return computeReg.getOperand(barg);
41}
42
45 if (!orig)
46 return nullptr;
47 mlir::Operation *def = orig.getDefiningOp();
48 return mlir::isa_and_nonnull<ACC_DATA_ENTRY_OPS>(def) ? def : nullptr;
49}
50
51template <typename OpTy>
53 auto checkIfUsedOnlyByOpInside = [&](mlir::Operation *user) {
54 // For any users which are not in the current acc region, we can ignore.
55 // Return true so that it can be used in a `all_of` check.
56 if (!region.isAncestor(user->getParentRegion()))
57 return true;
58 return mlir::isa<OpTy>(user);
59 };
60
61 return llvm::all_of(val.getUsers(), checkIfUsedOnlyByOpInside);
62}
63
68
73
74std::optional<mlir::acc::ClauseDefaultValue>
76 std::optional<mlir::acc::ClauseDefaultValue> defaultAttr;
77 Operation *currOp = op;
78
79 // Iterate outwards until a default clause is found (since OpenACC
80 // specification notes that a visible default clause is the nearest default
81 // clause appearing on the compute construct or a lexically containing data
82 // construct.
83 while (!defaultAttr.has_value() && currOp) {
84 defaultAttr =
86 std::optional<mlir::acc::ClauseDefaultValue>>(currOp)
87 .Case<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::DataOp>(
88 [&](auto op) { return op.getDefaultAttr(); })
89 .Default([&](Operation *) { return std::nullopt; });
90 currOp = currOp->getParentOp();
91 }
92
93 return defaultAttr;
94}
95
96mlir::acc::VariableTypeCategory mlir::acc::getTypeCategory(mlir::Value var) {
97 mlir::acc::VariableTypeCategory typeCategory =
98 mlir::acc::VariableTypeCategory::uncategorized;
99 if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(var.getType()))
100 typeCategory = mappableTy.getTypeCategory(var);
101 else if (auto pointerLikeTy =
102 dyn_cast<mlir::acc::PointerLikeType>(var.getType()))
103 typeCategory = pointerLikeTy.getPointeeTypeCategory(
105 pointerLikeTy.getElementType());
106 return typeCategory;
107}
108
109llvm::StringLiteral mlir::acc::getVarNamePlaceholder() {
110 return llvm::StringLiteral("<acc.varname.placeholder>");
111}
112
114 Value current = v;
115
116 // Walk through view operations until a name is found or can't go further
117 while (Operation *definingOp = current.getDefiningOp()) {
118 // For integer constants, return their value as a string.
119 if (std::optional<int64_t> constVal = getConstantIntValue(current))
120 return std::to_string(*constVal);
121
122 // Check for `acc.var_name` attribute
123 if (auto varNameAttr = definingOp->getDiscardableAttrOfType<VarNameAttr>(
125 return varNameAttr.getName().str();
126
127 // If it is a data entry operation, get name via getVarName
128 if (isa<ACC_DATA_ENTRY_OPS>(definingOp))
129 if (auto name = acc::getVarName(definingOp))
130 return name->str();
131
132 // If it's a view operation, continue to the source
133 if (auto viewOp = dyn_cast<ViewLikeOpInterface>(definingOp)) {
134 current = viewOp.getViewSource();
135 continue;
136 }
137
138 break;
139 }
140
141 return "";
142}
143
144std::string mlir::acc::getRecipeName(mlir::acc::RecipeKind kind,
145 mlir::Type type) {
146 assert(kind == mlir::acc::RecipeKind::private_recipe ||
147 kind == mlir::acc::RecipeKind::firstprivate_recipe ||
148 kind == mlir::acc::RecipeKind::reduction_recipe);
149 if (!llvm::isa<mlir::acc::PointerLikeType, mlir::acc::MappableType>(type))
150 return "";
151
152 std::string recipeName;
153 llvm::raw_string_ostream ss(recipeName);
154 ss << (kind == mlir::acc::RecipeKind::private_recipe ? "privatization_"
155 : kind == mlir::acc::RecipeKind::firstprivate_recipe
156 ? "firstprivatization_"
157 : "reduction_");
158
159 // Print the type using its dialect-defined textual format.
160 type.print(ss);
161 ss.flush();
162
163 // Replace invalid characters (anything that's not a letter, number, or
164 // period) since this needs to be a valid MLIR identifier.
165 for (char &c : recipeName) {
166 if (!std::isalnum(static_cast<unsigned char>(c)) && c != '.' && c != '_') {
167 if (c == '?')
168 c = 'U';
169 else if (c == '*')
170 c = 'Z';
171 else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' ||
172 c == '}' || c == '<' || c == '>')
173 c = '_';
174 else
175 c = 'X';
176 }
177 }
178
179 return recipeName;
180}
181
183 if (auto partialEntityAccessOp =
184 val.getDefiningOp<PartialEntityAccessOpInterface>()) {
185 if (!partialEntityAccessOp.isCompleteView())
186 return partialEntityAccessOp.getBaseEntity();
187 }
188
189 return val;
190}
191
193 mlir::SymbolRefAttr symbol,
194 mlir::Operation **definingOpPtr) {
195 mlir::Operation *definingOp =
197
198 // If there are no defining ops, we have no way to ensure validity because
199 // we cannot check for any attributes.
200 if (!definingOp)
201 return false;
202
203 if (definingOpPtr)
204 *definingOpPtr = definingOp;
205
206 // Check if the defining op is a recipe (private, reduction, firstprivate).
207 // Recipes are valid as they get materialized before being offloaded to
208 // device. They are only instructions for how to materialize.
209 if (mlir::isa<mlir::acc::PrivateRecipeOp, mlir::acc::ReductionRecipeOp,
210 mlir::acc::FirstprivateRecipeOp>(definingOp))
211 return true;
212
213 // Check if the defining op is a global variable that is device data.
214 // Device data is already resident on the device and does not need mapping.
215 if (auto globalVar =
216 mlir::dyn_cast<mlir::acc::GlobalVariableOpInterface>(definingOp))
217 if (globalVar.isDeviceData())
218 return true;
219
220 // Check if the defining op is a function
221 if (auto func =
222 mlir::dyn_cast_if_present<mlir::FunctionOpInterface>(definingOp)) {
223 // If this symbol is actually an acc routine or a specialized acc routine -
224 // then it is expected for it to be offloaded - therefore it is valid.
225 if (func->hasDiscardableAttr(mlir::acc::getRoutineInfoAttrName()) ||
226 func->hasDiscardableAttr(mlir::acc::getSpecializedRoutineAttrName()))
227 return true;
228
229 // If this symbol is a call to an LLVM intrinsic, then it is likely valid.
230 // Check the following:
231 // 1. The function is private
232 // 2. The function has no body
233 // 3. Name starts with "llvm."
234 // 4. The function's name is a valid LLVM intrinsic name
235 if (func.getVisibility() == mlir::SymbolTable::Visibility::Private &&
236 func.getFunctionBody().empty() && func.getName().starts_with("llvm.") &&
237 llvm::Intrinsic::lookupIntrinsicID(func.getName()) !=
238 llvm::Intrinsic::not_intrinsic)
239 return true;
240 }
241
242 // A declare attribute is needed for symbol references.
243 bool hasDeclare =
245 return hasDeclare;
246}
247
249 // Check if the value is device data via type interfaces.
250 // Device data is already resident on the device and does not need mapping.
251 if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(val.getType()))
252 if (mappableTy.isDeviceData(val))
253 return true;
254
255 if (auto pointerLikeTy = dyn_cast<mlir::acc::PointerLikeType>(val.getType()))
256 if (pointerLikeTy.isDeviceData(val))
257 return true;
258
259 mlir::Operation *defOp = val.getDefiningOp();
260 if (!defOp)
261 return false;
262
263 // `acc.declare` with deviceptr marks data that is already associated with
264 // the device.
265 if (auto declareAttr =
266 defOp->getDiscardableAttrOfType<mlir::acc::DeclareAttr>(
268 if (declareAttr.getDataClause().getValue() ==
269 mlir::acc::DataClause::acc_deviceptr)
270 return true;
271
272 // Handle operations that access a partial entity - check if the base entity
273 // is device data.
274 if (auto partialAccess =
275 dyn_cast<mlir::acc::PartialEntityAccessOpInterface>(defOp)) {
276 if (mlir::Value base = partialAccess.getBaseEntity())
277 return isDeviceValue(base);
278 }
279
280 // Handle address_of - check if the referenced global is device data.
281 if (auto addrOfIface =
282 dyn_cast<mlir::acc::AddressOfGlobalOpInterface>(defOp)) {
283 auto symbol = addrOfIface.getSymbol();
285 mlir::acc::GlobalVariableOpInterface>(defOp, symbol))
286 return global.isDeviceData();
287 }
288
289 return false;
290}
291
293 // Types that can be passed by value are legal.
294 Type type = val.getType();
295 if (type.isIntOrIndexOrFloat() || isa<mlir::ComplexType>(type) ||
296 llvm::isa<mlir::VectorType>(type))
297 return true;
298
299 // If this is produced by an ACC data entry operation, it is valid.
300 if (isa_and_nonnull<ACC_DATA_ENTRY_OPS>(val.getDefiningOp()))
301 return true;
302
303 // If the value is only used by private clauses, it is not a live-in.
304 if (isOnlyUsedByPrivateClauses(val, region))
305 return true;
306
307 // If this is device data, it is valid.
308 if (isDeviceValue(val))
309 return true;
310
311 return false;
312}
313
316 mlir::DominanceInfo &domInfo,
317 mlir::PostDominanceInfo &postDomInfo) {
318 llvm::SmallSetVector<mlir::Value, 8> dominatingDataClauses;
319
320 llvm::TypeSwitch<mlir::Operation *>(computeConstructOp)
321 .Case<mlir::acc::ParallelOp, mlir::acc::KernelsOp, mlir::acc::SerialOp>(
322 [&](auto op) {
323 for (auto dataClause : op.getDataClauseOperands()) {
324 dominatingDataClauses.insert(dataClause);
325 }
326 })
327 .Default([](mlir::Operation *) {});
328
329 // Collect the data clauses from enclosing data constructs.
330 mlir::Operation *currParentOp = computeConstructOp->getParentOp();
331 while (currParentOp) {
332 if (mlir::isa<mlir::acc::DataOp>(currParentOp)) {
333 for (auto dataClause : mlir::dyn_cast<mlir::acc::DataOp>(currParentOp)
334 .getDataClauseOperands()) {
335 dominatingDataClauses.insert(dataClause);
336 }
337 }
338 currParentOp = currParentOp->getParentOp();
339 }
340
341 // Find the enclosing function/subroutine
342 auto funcOp =
343 computeConstructOp->getParentOfType<mlir::FunctionOpInterface>();
344 if (!funcOp)
345 return dominatingDataClauses.takeVector();
346
347 // Walk the function to find `acc.declare_enter`/`acc.declare_exit` pairs that
348 // dominate and post-dominate the compute construct and add their data
349 // clauses to the list.
350 funcOp->walk([&](mlir::acc::DeclareEnterOp declareEnterOp) {
351 if (domInfo.dominates(declareEnterOp.getOperation(), computeConstructOp)) {
352 // Collect all `acc.declare_exit` ops for this token.
354 for (auto *user : declareEnterOp.getToken().getUsers())
355 if (auto declareExit = mlir::dyn_cast<mlir::acc::DeclareExitOp>(user))
356 exits.push_back(declareExit);
357
358 // Only add clauses if every `acc.declare_exit` op post-dominates the
359 // compute construct.
360 if (!exits.empty() &&
361 llvm::all_of(exits, [&](mlir::acc::DeclareExitOp exitOp) {
362 return postDomInfo.postDominates(exitOp, computeConstructOp);
363 })) {
364 for (auto dataClause : declareEnterOp.getDataClauseOperands())
365 dominatingDataClauses.insert(dataClause);
366 }
367 }
368 });
369
370 return dominatingDataClauses.takeVector();
371}
372
375 const std::function<std::string()> &messageFn,
376 llvm::StringRef category) {
377 using namespace mlir::remark;
378 mlir::Location loc = op->getLoc();
379 auto *engine = loc->getContext()->getRemarkEngine();
380 if (!engine)
382
383 llvm::StringRef funcName;
384 if (auto func = dyn_cast<mlir::FunctionOpInterface>(op))
385 funcName = func.getName();
386 else if (auto funcOp = op->getParentOfType<mlir::FunctionOpInterface>())
387 funcName = funcOp.getName();
388
389 auto opts = RemarkOpts::name("openacc").category(category);
390 if (!funcName.empty())
391 opts = opts.function(funcName);
392
393 auto remark = engine->emitOptimizationRemark(loc, opts);
394 if (remark)
395 remark << messageFn();
396 return remark;
397}
static bool isOnlyUsedByOpClauses(mlir::Value val, mlir::Region &region)
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:33
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:249
ParentT getParentOfType()
Find the first parent operation of the given type, or nullptr if there is no ancestor operation.
Definition Region.h:221
@ Private
The symbol is private and may only be referenced by SymbolRefAttrs local to the operations within the...
Definition SymbolTable.h:91
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.
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 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:5447
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