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