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