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
12#include "mlir/IR/Dominance.h"
13#include "mlir/IR/SymbolTable.h"
16#include "llvm/ADT/SetVector.h"
17#include "llvm/ADT/TypeSwitch.h"
18#include "llvm/IR/Intrinsics.h"
19#include "llvm/Support/Casting.h"
20
22 mlir::Operation *parentOp = region.getParentOp();
23 while (parentOp) {
24 if (mlir::isa<ACC_COMPUTE_CONSTRUCT_OPS>(parentOp))
25 return parentOp;
26 parentOp = parentOp->getParentOp();
27 }
28 return nullptr;
29}
30
31template <typename OpTy>
33 auto checkIfUsedOnlyByOpInside = [&](mlir::Operation *user) {
34 // For any users which are not in the current acc region, we can ignore.
35 // Return true so that it can be used in a `all_of` check.
36 if (!region.isAncestor(user->getParentRegion()))
37 return true;
38 return mlir::isa<OpTy>(user);
39 };
40
41 return llvm::all_of(val.getUsers(), checkIfUsedOnlyByOpInside);
42}
43
48
53
54std::optional<mlir::acc::ClauseDefaultValue>
56 std::optional<mlir::acc::ClauseDefaultValue> defaultAttr;
57 Operation *currOp = op;
58
59 // Iterate outwards until a default clause is found (since OpenACC
60 // specification notes that a visible default clause is the nearest default
61 // clause appearing on the compute construct or a lexically containing data
62 // construct.
63 while (!defaultAttr.has_value() && currOp) {
64 defaultAttr =
66 std::optional<mlir::acc::ClauseDefaultValue>>(currOp)
67 .Case<ACC_COMPUTE_CONSTRUCT_OPS, mlir::acc::DataOp>(
68 [&](auto op) { return op.getDefaultAttr(); })
69 .Default([&](Operation *) { return std::nullopt; });
70 currOp = currOp->getParentOp();
71 }
72
73 return defaultAttr;
74}
75
76mlir::acc::VariableTypeCategory mlir::acc::getTypeCategory(mlir::Value var) {
77 mlir::acc::VariableTypeCategory typeCategory =
78 mlir::acc::VariableTypeCategory::uncategorized;
79 if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(var.getType()))
80 typeCategory = mappableTy.getTypeCategory(var);
81 else if (auto pointerLikeTy =
82 dyn_cast<mlir::acc::PointerLikeType>(var.getType()))
83 typeCategory = pointerLikeTy.getPointeeTypeCategory(
85 pointerLikeTy.getElementType());
86 return typeCategory;
87}
88
90 Value current = v;
91
92 // Walk through view operations until a name is found or can't go further
93 while (Operation *definingOp = current.getDefiningOp()) {
94 // Check for `acc.var_name` attribute
95 if (auto varNameAttr =
96 definingOp->getAttrOfType<VarNameAttr>(getVarNameAttrName()))
97 return varNameAttr.getName().str();
98
99 // If it is a data entry operation, get name via getVarName
100 if (isa<ACC_DATA_ENTRY_OPS>(definingOp))
101 if (auto name = acc::getVarName(definingOp))
102 return name->str();
103
104 // If it's a view operation, continue to the source
105 if (auto viewOp = dyn_cast<ViewLikeOpInterface>(definingOp)) {
106 current = viewOp.getViewSource();
107 continue;
108 }
109
110 break;
111 }
112
113 return "";
114}
115
116std::string mlir::acc::getRecipeName(mlir::acc::RecipeKind kind,
117 mlir::Type type) {
118 assert(kind == mlir::acc::RecipeKind::private_recipe ||
119 kind == mlir::acc::RecipeKind::firstprivate_recipe ||
120 kind == mlir::acc::RecipeKind::reduction_recipe);
121 if (!llvm::isa<mlir::acc::PointerLikeType, mlir::acc::MappableType>(type))
122 return "";
123
124 std::string recipeName;
125 llvm::raw_string_ostream ss(recipeName);
126 ss << (kind == mlir::acc::RecipeKind::private_recipe ? "privatization_"
127 : kind == mlir::acc::RecipeKind::firstprivate_recipe
128 ? "firstprivatization_"
129 : "reduction_");
130
131 // Print the type using its dialect-defined textual format.
132 type.print(ss);
133 ss.flush();
134
135 // Replace invalid characters (anything that's not a letter, number, or
136 // period) since this needs to be a valid MLIR identifier.
137 for (char &c : recipeName) {
138 if (!std::isalnum(static_cast<unsigned char>(c)) && c != '.' && c != '_') {
139 if (c == '?')
140 c = 'U';
141 else if (c == '*')
142 c = 'Z';
143 else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' ||
144 c == '}' || c == '<' || c == '>')
145 c = '_';
146 else
147 c = 'X';
148 }
149 }
150
151 return recipeName;
152}
153
155 if (auto partialEntityAccessOp =
156 val.getDefiningOp<PartialEntityAccessOpInterface>()) {
157 if (!partialEntityAccessOp.isCompleteView())
158 return partialEntityAccessOp.getBaseEntity();
159 }
160
161 return val;
162}
163
165 mlir::SymbolRefAttr symbol,
166 mlir::Operation **definingOpPtr) {
167 mlir::Operation *definingOp =
169
170 // If there are no defining ops, we have no way to ensure validity because
171 // we cannot check for any attributes.
172 if (!definingOp)
173 return false;
174
175 if (definingOpPtr)
176 *definingOpPtr = definingOp;
177
178 // Check if the defining op is a recipe (private, reduction, firstprivate).
179 // Recipes are valid as they get materialized before being offloaded to
180 // device. They are only instructions for how to materialize.
181 if (mlir::isa<mlir::acc::PrivateRecipeOp, mlir::acc::ReductionRecipeOp,
182 mlir::acc::FirstprivateRecipeOp>(definingOp))
183 return true;
184
185 // Check if the defining op is a global variable that is device data.
186 // Device data is already resident on the device and does not need mapping.
187 if (auto globalVar =
188 mlir::dyn_cast<mlir::acc::GlobalVariableOpInterface>(definingOp))
189 if (globalVar.isDeviceData())
190 return true;
191
192 // Check if the defining op is a function
193 if (auto func =
194 mlir::dyn_cast_if_present<mlir::FunctionOpInterface>(definingOp)) {
195 // If this symbol is actually an acc routine - then it is expected for it
196 // to be offloaded - therefore it is valid.
198 return true;
199
200 // If this symbol is a call to an LLVM intrinsic, then it is likely valid.
201 // Check the following:
202 // 1. The function is private
203 // 2. The function has no body
204 // 3. Name starts with "llvm."
205 // 4. The function's name is a valid LLVM intrinsic name
206 if (func.getVisibility() == mlir::SymbolTable::Visibility::Private &&
207 func.getFunctionBody().empty() && func.getName().starts_with("llvm.") &&
208 llvm::Intrinsic::lookupIntrinsicID(func.getName()) !=
209 llvm::Intrinsic::not_intrinsic)
210 return true;
211 }
212
213 // A declare attribute is needed for symbol references.
214 bool hasDeclare = definingOp->hasAttr(mlir::acc::getDeclareAttrName());
215 return hasDeclare;
216}
217
219 // Check if the value is device data via type interfaces.
220 // Device data is already resident on the device and does not need mapping.
221 if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(val.getType()))
222 if (mappableTy.isDeviceData(val))
223 return true;
224
225 if (auto pointerLikeTy = dyn_cast<mlir::acc::PointerLikeType>(val.getType()))
226 if (pointerLikeTy.isDeviceData(val))
227 return true;
228
229 // Handle operations that access a partial entity - check if the base entity
230 // is device data.
231 if (auto *defOp = val.getDefiningOp()) {
232 if (auto partialAccess =
233 dyn_cast<mlir::acc::PartialEntityAccessOpInterface>(defOp)) {
234 if (mlir::Value base = partialAccess.getBaseEntity())
235 return isDeviceValue(base);
236 }
237
238 // Handle address_of - check if the referenced global is device data.
239 if (auto addrOfIface =
240 dyn_cast<mlir::acc::AddressOfGlobalOpInterface>(defOp)) {
241 auto symbol = addrOfIface.getSymbol();
243 mlir::acc::GlobalVariableOpInterface>(defOp, symbol))
244 return global.isDeviceData();
245 }
246 }
247
248 return false;
249}
250
252 // If this is produced by an ACC data entry operation, it is valid.
253 if (isa_and_nonnull<ACC_DATA_ENTRY_OPS>(val.getDefiningOp()))
254 return true;
255
256 // If the value is only used by private clauses, it is not a live-in.
257 if (isOnlyUsedByPrivateClauses(val, region))
258 return true;
259
260 // If this is device data, it is valid.
261 if (isDeviceValue(val))
262 return true;
263
264 return false;
265}
266
269 mlir::DominanceInfo &domInfo,
270 mlir::PostDominanceInfo &postDomInfo) {
271 llvm::SmallSetVector<mlir::Value, 8> dominatingDataClauses;
272
273 llvm::TypeSwitch<mlir::Operation *>(computeConstructOp)
274 .Case<mlir::acc::ParallelOp, mlir::acc::KernelsOp, mlir::acc::SerialOp>(
275 [&](auto op) {
276 for (auto dataClause : op.getDataClauseOperands()) {
277 dominatingDataClauses.insert(dataClause);
278 }
279 })
280 .Default([](mlir::Operation *) {});
281
282 // Collect the data clauses from enclosing data constructs.
283 mlir::Operation *currParentOp = computeConstructOp->getParentOp();
284 while (currParentOp) {
285 if (mlir::isa<mlir::acc::DataOp>(currParentOp)) {
286 for (auto dataClause : mlir::dyn_cast<mlir::acc::DataOp>(currParentOp)
287 .getDataClauseOperands()) {
288 dominatingDataClauses.insert(dataClause);
289 }
290 }
291 currParentOp = currParentOp->getParentOp();
292 }
293
294 // Find the enclosing function/subroutine
295 auto funcOp =
296 computeConstructOp->getParentOfType<mlir::FunctionOpInterface>();
297 if (!funcOp)
298 return dominatingDataClauses.takeVector();
299
300 // Walk the function to find `acc.declare_enter`/`acc.declare_exit` pairs that
301 // dominate and post-dominate the compute construct and add their data
302 // clauses to the list.
303 funcOp->walk([&](mlir::acc::DeclareEnterOp declareEnterOp) {
304 if (domInfo.dominates(declareEnterOp.getOperation(), computeConstructOp)) {
305 // Collect all `acc.declare_exit` ops for this token.
307 for (auto *user : declareEnterOp.getToken().getUsers())
308 if (auto declareExit = mlir::dyn_cast<mlir::acc::DeclareExitOp>(user))
309 exits.push_back(declareExit);
310
311 // Only add clauses if every `acc.declare_exit` op post-dominates the
312 // compute construct.
313 if (!exits.empty() &&
314 llvm::all_of(exits, [&](mlir::acc::DeclareExitOp exitOp) {
315 return postDomInfo.postDominates(exitOp, computeConstructOp);
316 })) {
317 for (auto dataClause : declareEnterOp.getDataClauseOperands())
318 dominatingDataClauses.insert(dataClause);
319 }
320 }
321 });
322
323 return dominatingDataClauses.takeVector();
324}
325
327mlir::acc::emitRemark(mlir::Operation *op, const llvm::Twine &message,
328 llvm::StringRef category) {
329 using namespace mlir::remark;
330 mlir::Location loc = op->getLoc();
331 auto *engine = loc->getContext()->getRemarkEngine();
332 if (!engine)
334
335 llvm::StringRef funcName;
336 if (auto func = dyn_cast<mlir::FunctionOpInterface>(op))
337 funcName = func.getName();
338 else if (auto funcOp = op->getParentOfType<mlir::FunctionOpInterface>())
339 funcName = funcOp.getName();
340
341 auto opts = RemarkOpts::name("openacc").category(category);
342 if (!funcName.empty())
343 opts = opts.function(funcName);
344
345 auto remark = engine->emitOptimizationRemark(loc, opts);
346 if (remark)
347 remark << message.str();
348 return remark;
349}
static bool isOnlyUsedByOpClauses(mlir::Value val, mlir::Region &region)
MLIRContext * getContext() const
Return the context this attribute belongs to.
A class for computing basic dominance information.
Definition Dominance.h:140
bool dominates(Operation *a, Operation *b) const
Return true if operation A dominates operation B, i.e.
Definition Dominance.h:158
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:88
bool hasAttr(StringAttr name)
Return true if the operation has an attribute with the provided name, false otherwise.
Definition Operation.h:560
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
A class for computing basic postdominance information.
Definition Dominance.h:204
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:222
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:200
@ 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.
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
InFlightRemark is a RAII class that holds a reference to a Remark instance and allows to build the re...
Definition Remarks.h:292
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.
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.
remark::detail::InFlightRemark emitRemark(mlir::Operation *op, const llvm::Twine &message, llvm::StringRef category="openacc")
Emit an OpenACC remark for the given operation with the given message.
std::optional< llvm::StringRef > getVarName(mlir::Operation *accOp)
Used to obtain the name from an acc operation.
Definition OpenACC.cpp:4985
static constexpr StringLiteral getRoutineInfoAttrName()
Definition OpenACC.h:179
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:203
std::string getRecipeName(mlir::acc::RecipeKind kind, mlir::Type type)
Get the recipe name for a given recipe kind and type.
static constexpr StringLiteral getDeclareAttrName()
Used to obtain the attribute name for declare.
Definition OpenACC.h:171
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:497
static constexpr RemarkOpts name(StringRef n)
Definition Remarks.h:64