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)
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
110 Value current = v;
111
112 // Walk through view operations until a name is found or can't go further
113 while (Operation *definingOp = current.getDefiningOp()) {
114 // For integer constants, return their value as a string.
115 if (std::optional<int64_t> constVal = getConstantIntValue(current))
116 return std::to_string(*constVal);
117
118 // Check for `acc.var_name` attribute
119 if (auto varNameAttr =
120 definingOp->getAttrOfType<VarNameAttr>(getVarNameAttrName()))
121 return varNameAttr.getName().str();
122
123 // If it is a data entry operation, get name via getVarName
124 if (isa<ACC_DATA_ENTRY_OPS>(definingOp))
125 if (auto name = acc::getVarName(definingOp))
126 return name->str();
127
128 // If it's a view operation, continue to the source
129 if (auto viewOp = dyn_cast<ViewLikeOpInterface>(definingOp)) {
130 current = viewOp.getViewSource();
131 continue;
132 }
133
134 break;
135 }
136
137 return "";
138}
139
140std::string mlir::acc::getRecipeName(mlir::acc::RecipeKind kind,
141 mlir::Type type) {
142 assert(kind == mlir::acc::RecipeKind::private_recipe ||
143 kind == mlir::acc::RecipeKind::firstprivate_recipe ||
144 kind == mlir::acc::RecipeKind::reduction_recipe);
145 if (!llvm::isa<mlir::acc::PointerLikeType, mlir::acc::MappableType>(type))
146 return "";
147
148 std::string recipeName;
149 llvm::raw_string_ostream ss(recipeName);
150 ss << (kind == mlir::acc::RecipeKind::private_recipe ? "privatization_"
151 : kind == mlir::acc::RecipeKind::firstprivate_recipe
152 ? "firstprivatization_"
153 : "reduction_");
154
155 // Print the type using its dialect-defined textual format.
156 type.print(ss);
157 ss.flush();
158
159 // Replace invalid characters (anything that's not a letter, number, or
160 // period) since this needs to be a valid MLIR identifier.
161 for (char &c : recipeName) {
162 if (!std::isalnum(static_cast<unsigned char>(c)) && c != '.' && c != '_') {
163 if (c == '?')
164 c = 'U';
165 else if (c == '*')
166 c = 'Z';
167 else if (c == '(' || c == ')' || c == '[' || c == ']' || c == '{' ||
168 c == '}' || c == '<' || c == '>')
169 c = '_';
170 else
171 c = 'X';
172 }
173 }
174
175 return recipeName;
176}
177
179 if (auto partialEntityAccessOp =
180 val.getDefiningOp<PartialEntityAccessOpInterface>()) {
181 if (!partialEntityAccessOp.isCompleteView())
182 return partialEntityAccessOp.getBaseEntity();
183 }
184
185 return val;
186}
187
189 mlir::SymbolRefAttr symbol,
190 mlir::Operation **definingOpPtr) {
191 mlir::Operation *definingOp =
193
194 // If there are no defining ops, we have no way to ensure validity because
195 // we cannot check for any attributes.
196 if (!definingOp)
197 return false;
198
199 if (definingOpPtr)
200 *definingOpPtr = definingOp;
201
202 // Check if the defining op is a recipe (private, reduction, firstprivate).
203 // Recipes are valid as they get materialized before being offloaded to
204 // device. They are only instructions for how to materialize.
205 if (mlir::isa<mlir::acc::PrivateRecipeOp, mlir::acc::ReductionRecipeOp,
206 mlir::acc::FirstprivateRecipeOp>(definingOp))
207 return true;
208
209 // Check if the defining op is a global variable that is device data.
210 // Device data is already resident on the device and does not need mapping.
211 if (auto globalVar =
212 mlir::dyn_cast<mlir::acc::GlobalVariableOpInterface>(definingOp))
213 if (globalVar.isDeviceData())
214 return true;
215
216 // Check if the defining op is a function
217 if (auto func =
218 mlir::dyn_cast_if_present<mlir::FunctionOpInterface>(definingOp)) {
219 // If this symbol is actually an acc routine - then it is expected for it
220 // to be offloaded - therefore it is valid.
222 return true;
223
224 // If this symbol is a call to an LLVM intrinsic, then it is likely valid.
225 // Check the following:
226 // 1. The function is private
227 // 2. The function has no body
228 // 3. Name starts with "llvm."
229 // 4. The function's name is a valid LLVM intrinsic name
230 if (func.getVisibility() == mlir::SymbolTable::Visibility::Private &&
231 func.getFunctionBody().empty() && func.getName().starts_with("llvm.") &&
232 llvm::Intrinsic::lookupIntrinsicID(func.getName()) !=
233 llvm::Intrinsic::not_intrinsic)
234 return true;
235 }
236
237 // A declare attribute is needed for symbol references.
238 bool hasDeclare = definingOp->hasAttr(mlir::acc::getDeclareAttrName());
239 return hasDeclare;
240}
241
243 // Check if the value is device data via type interfaces.
244 // Device data is already resident on the device and does not need mapping.
245 if (auto mappableTy = dyn_cast<mlir::acc::MappableType>(val.getType()))
246 if (mappableTy.isDeviceData(val))
247 return true;
248
249 if (auto pointerLikeTy = dyn_cast<mlir::acc::PointerLikeType>(val.getType()))
250 if (pointerLikeTy.isDeviceData(val))
251 return true;
252
253 mlir::Operation *defOp = val.getDefiningOp();
254 if (!defOp)
255 return false;
256
257 // `acc.declare` with deviceptr marks data that is already associated with
258 // the device.
259 if (auto declareAttr = defOp->getAttrOfType<mlir::acc::DeclareAttr>(
261 if (declareAttr.getDataClause().getValue() ==
262 mlir::acc::DataClause::acc_deviceptr)
263 return true;
264
265 // Handle operations that access a partial entity - check if the base entity
266 // is device data.
267 if (auto partialAccess =
268 dyn_cast<mlir::acc::PartialEntityAccessOpInterface>(defOp)) {
269 if (mlir::Value base = partialAccess.getBaseEntity())
270 return isDeviceValue(base);
271 }
272
273 // Handle address_of - check if the referenced global is device data.
274 if (auto addrOfIface =
275 dyn_cast<mlir::acc::AddressOfGlobalOpInterface>(defOp)) {
276 auto symbol = addrOfIface.getSymbol();
278 mlir::acc::GlobalVariableOpInterface>(defOp, symbol))
279 return global.isDeviceData();
280 }
281
282 return false;
283}
284
286 // Types that can be passed by value are legal.
287 Type type = val.getType();
288 if (type.isIntOrIndexOrFloat() || isa<mlir::ComplexType>(type) ||
289 llvm::isa<mlir::VectorType>(type))
290 return true;
291
292 // If this is produced by an ACC data entry operation, it is valid.
293 if (isa_and_nonnull<ACC_DATA_ENTRY_OPS>(val.getDefiningOp()))
294 return true;
295
296 // If the value is only used by private clauses, it is not a live-in.
297 if (isOnlyUsedByPrivateClauses(val, region))
298 return true;
299
300 // If this is device data, it is valid.
301 if (isDeviceValue(val))
302 return true;
303
304 return false;
305}
306
309 mlir::DominanceInfo &domInfo,
310 mlir::PostDominanceInfo &postDomInfo) {
311 llvm::SmallSetVector<mlir::Value, 8> dominatingDataClauses;
312
313 llvm::TypeSwitch<mlir::Operation *>(computeConstructOp)
314 .Case<mlir::acc::ParallelOp, mlir::acc::KernelsOp, mlir::acc::SerialOp>(
315 [&](auto op) {
316 for (auto dataClause : op.getDataClauseOperands()) {
317 dominatingDataClauses.insert(dataClause);
318 }
319 })
320 .Default([](mlir::Operation *) {});
321
322 // Collect the data clauses from enclosing data constructs.
323 mlir::Operation *currParentOp = computeConstructOp->getParentOp();
324 while (currParentOp) {
325 if (mlir::isa<mlir::acc::DataOp>(currParentOp)) {
326 for (auto dataClause : mlir::dyn_cast<mlir::acc::DataOp>(currParentOp)
327 .getDataClauseOperands()) {
328 dominatingDataClauses.insert(dataClause);
329 }
330 }
331 currParentOp = currParentOp->getParentOp();
332 }
333
334 // Find the enclosing function/subroutine
335 auto funcOp =
336 computeConstructOp->getParentOfType<mlir::FunctionOpInterface>();
337 if (!funcOp)
338 return dominatingDataClauses.takeVector();
339
340 // Walk the function to find `acc.declare_enter`/`acc.declare_exit` pairs that
341 // dominate and post-dominate the compute construct and add their data
342 // clauses to the list.
343 funcOp->walk([&](mlir::acc::DeclareEnterOp declareEnterOp) {
344 if (domInfo.dominates(declareEnterOp.getOperation(), computeConstructOp)) {
345 // Collect all `acc.declare_exit` ops for this token.
347 for (auto *user : declareEnterOp.getToken().getUsers())
348 if (auto declareExit = mlir::dyn_cast<mlir::acc::DeclareExitOp>(user))
349 exits.push_back(declareExit);
350
351 // Only add clauses if every `acc.declare_exit` op post-dominates the
352 // compute construct.
353 if (!exits.empty() &&
354 llvm::all_of(exits, [&](mlir::acc::DeclareExitOp exitOp) {
355 return postDomInfo.postDominates(exitOp, computeConstructOp);
356 })) {
357 for (auto dataClause : declareEnterOp.getDataClauseOperands())
358 dominatingDataClauses.insert(dataClause);
359 }
360 }
361 });
362
363 return dominatingDataClauses.takeVector();
364}
365
368 const std::function<std::string()> &messageFn,
369 llvm::StringRef category) {
370 using namespace mlir::remark;
371 mlir::Location loc = op->getLoc();
372 auto *engine = loc->getContext()->getRemarkEngine();
373 if (!engine)
375
376 llvm::StringRef funcName;
377 if (auto func = dyn_cast<mlir::FunctionOpInterface>(op))
378 funcName = func.getName();
379 else if (auto funcOp = op->getParentOfType<mlir::FunctionOpInterface>())
380 funcName = funcOp.getName();
381
382 auto opts = RemarkOpts::name("openacc").category(category);
383 if (!funcName.empty())
384 opts = opts.function(funcName);
385
386 auto remark = engine->emitOptimizationRemark(loc, opts);
387 if (remark)
388 remark << messageFn();
389 return remark;
390}
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: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.
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:5296
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::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:209
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