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