MLIR 24.0.0git
ACCImplicitRoutine.cpp
Go to the documentation of this file.
1//===- ACCImplicitRoutine.cpp - OpenACC Implicit Routine Transform -------===//
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//
9// This pass implements the implicit rules described in OpenACC specification
10// for `Routine Directive` (OpenACC 3.4 spec, section 2.15.1).
11//
12// "If no explicit routine directive applies to a procedure whose definition
13// appears in the program unit being compiled, then the implementation applies
14// an implicit routine directive to that procedure if any of the following
15// conditions holds:
16// - The procedure is called or its address is accessed in a compute region."
17//
18// The specification further states:
19// "When the implementation applies an implicit routine directive to a
20// procedure, it must recursively apply implicit routine directives to other
21// procedures for which the above rules specify relevant dependencies. Such
22// dependencies can form a cycle, so the implementation must take care to avoid
23// infinite recursion."
24//
25// This pass implements these requirements by:
26// 1. Walking through all OpenACC compute constructs and functions already
27// marked with `acc routine` in the module and identifying function calls
28// within these regions.
29// 2. Creating implicit `acc.routine` operations for functions that don't
30// already have routine declarations.
31// 3. Recursively walking through all existing `acc routine` and creating
32// implicit routine operations for function calls within these routines,
33// while avoiding infinite recursion through proper tracking.
34//
35// Calls in a branch that does not execute for `the-device-types` are skipped.
36// That list holds the runtime `acc_device_t` values for which `acc.on_device`
37// is true on the target.
38//
39// Requirements:
40// -------------
41// To use this pass in a pipeline, the following requirements must be met:
42//
43// 1. Operation Interface Implementation: Operations that define functions
44// or call functions should implement `mlir::FunctionOpInterface` and
45// `mlir::CallOpInterface` respectively.
46//
47// 2. Analysis Registration (Optional): If custom behavior is needed for
48// determining if a symbol use is valid within GPU regions, the dialect
49// should pre-register the `acc::OpenACCSupport` analysis.
50//===----------------------------------------------------------------------===//
51
53
57#include "mlir/IR/Builders.h"
59#include "mlir/IR/BuiltinOps.h"
60#include "mlir/IR/Operation.h"
61#include "mlir/IR/Value.h"
64#include <queue>
65
66#define DEBUG_TYPE "acc-implicit-routine"
67
68namespace mlir {
69namespace acc {
70#define GEN_PASS_DEF_ACCIMPLICITROUTINE
71#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
72} // namespace acc
73} // namespace mlir
74
75namespace {
76
77using namespace mlir;
78
79class ACCImplicitRoutine
80 : public acc::impl::ACCImplicitRoutineBase<ACCImplicitRoutine> {
81private:
82 unsigned routineCounter = 0;
83 static constexpr llvm::StringRef accRoutinePrefix = "acc_routine_";
84
85 // Count existing routine operations and update counter
86 void initRoutineCounter(ModuleOp module) {
87 module.walk([&](acc::RoutineOp routineOp) { routineCounter++; });
88 }
89
90 // Check if routine has a default bind clause or a device-type specific bind
91 // clause. Returns true if `acc routine` has a default bind clause or
92 // a device-type specific bind clause.
93 bool isACCRoutineBindDefaultOrDeviceType(acc::RoutineOp op,
94 acc::DeviceType deviceType) {
95 // Fast check to avoid device-type specific lookups.
96 if (!op.getBindIdName() && !op.getBindStrName())
97 return false;
98 return op.getBindNameValue().has_value() ||
99 op.getBindNameValue(deviceType).has_value();
100 }
101
102 // Generate a unique name for the routine and create the routine operation
103 acc::RoutineOp createRoutineOp(OpBuilder &builder, Location loc,
104 FunctionOpInterface &callee) {
105 std::string routineName =
106 (accRoutinePrefix + std::to_string(routineCounter++)).str();
107 auto routineOp = acc::RoutineOp::create(
108 builder, loc,
109 /* sym_name=*/builder.getStringAttr(routineName),
110 /* sym_visibility=*/nullptr,
111 /* func_name=*/
112 mlir::SymbolRefAttr::get(builder.getContext(),
113 builder.getStringAttr(callee.getName())),
114 /* bindIdName=*/nullptr,
115 /* bindStrName=*/nullptr,
116 /* bindIdNameDeviceType=*/nullptr,
117 /* bindStrNameDeviceType=*/nullptr,
118 /* worker=*/nullptr,
119 /* vector=*/nullptr,
120 /* seq=*/nullptr,
121 /* nohost=*/nullptr,
122 /* implicit=*/builder.getUnitAttr(),
123 /* gang=*/nullptr,
124 /* gangDim=*/nullptr,
125 /* gangDimDeviceType=*/nullptr);
126
127 // Assert that the callee does not already have routine info attribute
128 assert(!callee->hasDiscardableAttr(acc::getRoutineInfoAttrName()) &&
129 "function is already associated with a routine");
130
131 callee->setDiscardableAttr(
133 mlir::acc::RoutineInfoAttr::get(
134 builder.getContext(),
135 {mlir::SymbolRefAttr::get(builder.getContext(),
136 builder.getStringAttr(routineName))}));
137 return routineOp;
138 }
139
140 // Used to walk through a compute region looking for function calls.
141 LogicalResult
142 implicitRoutineForCallsInComputeRegions(Operation *op, SymbolTable &symTab,
143 mlir::OpBuilder &builder,
144 acc::OpenACCSupport &accSupport) {
145 LogicalResult result = success();
146 op->walk([&](CallOpInterface callOp) {
147 if (acc::isInOffTargetBranch(callOp.getOperation(), *theDeviceTypes))
148 return;
149 if (!callOp.getCallableForCallee())
150 return;
151
152 auto calleeSymbolRef =
153 dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
154 // When call is done through ssa value, the callee is not a symbol.
155 // Skip it because we don't know the call target.
156 if (!calleeSymbolRef)
157 return;
158
159 auto callee = symTab.lookup<FunctionOpInterface>(
160 calleeSymbolRef.getLeafReference().str());
161 // If the callee does not exist or is already a valid symbol for GPU
162 // regions, skip it
163 if (!callee)
164 return;
165 // Already a valid symbol for GPU regions (e.g. an existing routine or an
166 // LLVM intrinsic), skip it.
167 if (accSupport.isValidSymbolUse(callOp.getOperation(), calleeSymbolRef))
168 return;
169 // Without a definition in this compilation unit an implicit routine
170 // cannot be applied, so the call target needs explicit routine
171 // information.
172 if (callee.isExternal()) {
173 callOp->emitError() << "Calls in an acc compute region must be marked "
174 "with acc routine: "
175 << calleeSymbolRef.getLeafReference();
176 result = failure();
177 return;
178 }
179 builder.setInsertionPoint(callee);
180 createRoutineOp(builder, callee.getLoc(), callee);
181 });
182 return result;
183 }
184
185 // Recursively handle calls within a routine operation
186 LogicalResult implicitRoutineForCallsInRoutine(
187 acc::RoutineOp routineOp, mlir::OpBuilder &builder,
188 acc::OpenACCSupport &accSupport, acc::DeviceType targetDeviceType) {
189 // When bind clause is used, it means that the target is different than the
190 // function to which the `acc routine` is used with. Skip this case to
191 // avoid implicitly recursively marking calls that would not end up on
192 // device.
193 if (isACCRoutineBindDefaultOrDeviceType(routineOp, targetDeviceType))
194 return success();
195
196 SymbolTable symTab(routineOp->getParentOfType<ModuleOp>());
197 std::queue<acc::RoutineOp> routineQueue;
198 routineQueue.push(routineOp);
199 LogicalResult result = success();
200 while (!routineQueue.empty()) {
201 auto currentRoutine = routineQueue.front();
202 routineQueue.pop();
203 auto func = symTab.lookup<FunctionOpInterface>(
204 currentRoutine.getFuncName().getLeafReference());
205 func.walk([&](CallOpInterface callOp) {
206 if (acc::isInOffTargetBranch(callOp.getOperation(), *theDeviceTypes))
207 return;
208 if (!callOp.getCallableForCallee())
209 return;
210
211 auto calleeSymbolRef =
212 dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
213 // When call is done through ssa value, the callee is not a symbol.
214 // Skip it because we don't know the call target.
215 if (!calleeSymbolRef)
216 return;
217
218 auto callee = symTab.lookup<FunctionOpInterface>(
219 calleeSymbolRef.getLeafReference().str());
220 // If the callee does not exist or is already a valid symbol for GPU
221 // regions, skip it
222 if (!callee)
223 return;
224 // Already a valid symbol for GPU regions (e.g. an existing routine or
225 // an LLVM intrinsic), skip it.
226 if (accSupport.isValidSymbolUse(callOp.getOperation(), calleeSymbolRef))
227 return;
228 // Without a definition in this compilation unit an implicit routine
229 // cannot be applied, so the call target needs explicit routine
230 // information.
231 if (callee.isExternal()) {
232 callOp->emitError()
233 << "Calls in acc routine must also be marked with acc routine: "
234 << calleeSymbolRef.getLeafReference();
235 result = failure();
236 return;
237 }
238 builder.setInsertionPoint(callee);
239 auto newRoutineOp = createRoutineOp(builder, callee.getLoc(), callee);
240 routineQueue.push(newRoutineOp);
241 });
242 }
243 return result;
244 }
245
246public:
247 using ACCImplicitRoutineBase<ACCImplicitRoutine>::ACCImplicitRoutineBase;
248
249 void runOnOperation() override {
250 auto module = getOperation();
251 mlir::OpBuilder builder(module.getContext());
252 SymbolTable symTab(module);
253 initRoutineCounter(module);
254
255 acc::OpenACCSupport &accSupport = getAnalysis<acc::OpenACCSupport>();
256
257 LogicalResult result = success();
258
259 // Handle compute regions
260 module.walk([&](Operation *op) {
261 if (isa<ACC_COMPUTE_CONSTRUCT_OPS>(op))
262 if (failed(implicitRoutineForCallsInComputeRegions(op, symTab, builder,
263 accSupport)))
264 result = failure();
265 });
266
267 // Use the device type option from the pass options.
268 acc::DeviceType targetDeviceType = deviceType;
269
270 // Handle existing routines
271 module.walk([&](acc::RoutineOp routineOp) {
272 if (failed(implicitRoutineForCallsInRoutine(
273 routineOp, builder, accSupport, targetDeviceType)))
274 result = failure();
275 });
276
277 if (failed(result))
278 return signalPassFailure();
279 }
280};
281
282} // namespace
return success()
UnitAttr getUnitAttr()
Definition Builders.cpp:106
StringAttr getStringAttr(const Twine &bytes)
Definition Builders.cpp:271
MLIRContext * getContext() const
Definition Builders.h:56
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
This class helps build Operations.
Definition Builders.h:210
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:849
This class allows for representing and managing the symbol table used by operations with the 'SymbolT...
Definition SymbolTable.h:25
Operation * lookup(StringRef name) const
Look up a symbol with the specified name, returning null if no such name exists.
bool isValidSymbolUse(Operation *user, SymbolRefAttr symbol, Operation **definingOpPtr=nullptr)
Check if a symbol use is valid for use in an OpenACC region.
static constexpr StringLiteral getRoutineInfoAttrName()
Definition OpenACC.h:204
bool isInOffTargetBranch(mlir::Operation *op, llvm::ArrayRef< int64_t > deviceTypes)
Returns true if op is in a branch that does not execute on the target.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
Include the generated interface declarations.