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