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// Requirements:
36// -------------
37// To use this pass in a pipeline, the following requirements must be met:
38//
39// 1. Operation Interface Implementation: Operations that define functions
40// or call functions should implement `mlir::FunctionOpInterface` and
41// `mlir::CallOpInterface` respectively.
42//
43// 2. Analysis Registration (Optional): If custom behavior is needed for
44// determining if a symbol use is valid within GPU regions, the dialect
45// should pre-register the `acc::OpenACCSupport` analysis.
46//===----------------------------------------------------------------------===//
47
49
52#include "mlir/IR/Builders.h"
54#include "mlir/IR/BuiltinOps.h"
55#include "mlir/IR/Operation.h"
56#include "mlir/IR/Value.h"
59#include <queue>
60
61#define DEBUG_TYPE "acc-implicit-routine"
62
63namespace mlir {
64namespace acc {
65#define GEN_PASS_DEF_ACCIMPLICITROUTINE
66#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
67} // namespace acc
68} // namespace mlir
69
70namespace {
71
72using namespace mlir;
73
74class ACCImplicitRoutine
75 : public acc::impl::ACCImplicitRoutineBase<ACCImplicitRoutine> {
76private:
77 unsigned routineCounter = 0;
78 static constexpr llvm::StringRef accRoutinePrefix = "acc_routine_";
79
80 // Count existing routine operations and update counter
81 void initRoutineCounter(ModuleOp module) {
82 module.walk([&](acc::RoutineOp routineOp) { routineCounter++; });
83 }
84
85 // Check if routine has a default bind clause or a device-type specific bind
86 // clause. Returns true if `acc routine` has a default bind clause or
87 // a device-type specific bind clause.
88 bool isACCRoutineBindDefaultOrDeviceType(acc::RoutineOp op,
89 acc::DeviceType deviceType) {
90 // Fast check to avoid device-type specific lookups.
91 if (!op.getBindIdName() && !op.getBindStrName())
92 return false;
93 return op.getBindNameValue().has_value() ||
94 op.getBindNameValue(deviceType).has_value();
95 }
96
97 // Generate a unique name for the routine and create the routine operation
98 acc::RoutineOp createRoutineOp(OpBuilder &builder, Location loc,
99 FunctionOpInterface &callee) {
100 std::string routineName =
101 (accRoutinePrefix + std::to_string(routineCounter++)).str();
102 auto routineOp = acc::RoutineOp::create(
103 builder, loc,
104 /* sym_name=*/builder.getStringAttr(routineName),
105 /* sym_visibility=*/nullptr,
106 /* func_name=*/
107 mlir::SymbolRefAttr::get(builder.getContext(),
108 builder.getStringAttr(callee.getName())),
109 /* bindIdName=*/nullptr,
110 /* bindStrName=*/nullptr,
111 /* bindIdNameDeviceType=*/nullptr,
112 /* bindStrNameDeviceType=*/nullptr,
113 /* worker=*/nullptr,
114 /* vector=*/nullptr,
115 /* seq=*/nullptr,
116 /* nohost=*/nullptr,
117 /* implicit=*/builder.getUnitAttr(),
118 /* gang=*/nullptr,
119 /* gangDim=*/nullptr,
120 /* gangDimDeviceType=*/nullptr);
121
122 // Assert that the callee does not already have routine info attribute
123 assert(!callee->hasDiscardableAttr(acc::getRoutineInfoAttrName()) &&
124 "function is already associated with a routine");
125
126 callee->setDiscardableAttr(
128 mlir::acc::RoutineInfoAttr::get(
129 builder.getContext(),
130 {mlir::SymbolRefAttr::get(builder.getContext(),
131 builder.getStringAttr(routineName))}));
132 return routineOp;
133 }
134
135 // Used to walk through a compute region looking for function calls.
136 LogicalResult
137 implicitRoutineForCallsInComputeRegions(Operation *op, SymbolTable &symTab,
138 mlir::OpBuilder &builder,
139 acc::OpenACCSupport &accSupport) {
140 LogicalResult result = success();
141 op->walk([&](CallOpInterface callOp) {
142 if (!callOp.getCallableForCallee())
143 return;
144
145 auto calleeSymbolRef =
146 dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
147 // When call is done through ssa value, the callee is not a symbol.
148 // Skip it because we don't know the call target.
149 if (!calleeSymbolRef)
150 return;
151
152 auto callee = symTab.lookup<FunctionOpInterface>(
153 calleeSymbolRef.getLeafReference().str());
154 // If the callee does not exist or is already a valid symbol for GPU
155 // regions, skip it
156 if (!callee)
157 return;
158 // Already a valid symbol for GPU regions (e.g. an existing routine or an
159 // LLVM intrinsic), skip it.
160 if (accSupport.isValidSymbolUse(callOp.getOperation(), calleeSymbolRef))
161 return;
162 // Without a definition in this compilation unit an implicit routine
163 // cannot be applied, so the call target needs explicit routine
164 // information.
165 if (callee.isExternal()) {
166 callOp->emitError() << "Calls in an acc compute region must be marked "
167 "with acc routine: "
168 << calleeSymbolRef.getLeafReference();
169 result = failure();
170 return;
171 }
172 builder.setInsertionPoint(callee);
173 createRoutineOp(builder, callee.getLoc(), callee);
174 });
175 return result;
176 }
177
178 // Recursively handle calls within a routine operation
179 LogicalResult implicitRoutineForCallsInRoutine(
180 acc::RoutineOp routineOp, mlir::OpBuilder &builder,
181 acc::OpenACCSupport &accSupport, acc::DeviceType targetDeviceType) {
182 // When bind clause is used, it means that the target is different than the
183 // function to which the `acc routine` is used with. Skip this case to
184 // avoid implicitly recursively marking calls that would not end up on
185 // device.
186 if (isACCRoutineBindDefaultOrDeviceType(routineOp, targetDeviceType))
187 return success();
188
189 SymbolTable symTab(routineOp->getParentOfType<ModuleOp>());
190 std::queue<acc::RoutineOp> routineQueue;
191 routineQueue.push(routineOp);
192 LogicalResult result = success();
193 while (!routineQueue.empty()) {
194 auto currentRoutine = routineQueue.front();
195 routineQueue.pop();
196 auto func = symTab.lookup<FunctionOpInterface>(
197 currentRoutine.getFuncName().getLeafReference());
198 func.walk([&](CallOpInterface callOp) {
199 if (!callOp.getCallableForCallee())
200 return;
201
202 auto calleeSymbolRef =
203 dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
204 // When call is done through ssa value, the callee is not a symbol.
205 // Skip it because we don't know the call target.
206 if (!calleeSymbolRef)
207 return;
208
209 auto callee = symTab.lookup<FunctionOpInterface>(
210 calleeSymbolRef.getLeafReference().str());
211 // If the callee does not exist or is already a valid symbol for GPU
212 // regions, skip it
213 if (!callee)
214 return;
215 // Already a valid symbol for GPU regions (e.g. an existing routine or
216 // an LLVM intrinsic), skip it.
217 if (accSupport.isValidSymbolUse(callOp.getOperation(), calleeSymbolRef))
218 return;
219 // Without a definition in this compilation unit an implicit routine
220 // cannot be applied, so the call target needs explicit routine
221 // information.
222 if (callee.isExternal()) {
223 callOp->emitError()
224 << "Calls in acc routine must also be marked with acc routine: "
225 << calleeSymbolRef.getLeafReference();
226 result = failure();
227 return;
228 }
229 builder.setInsertionPoint(callee);
230 auto newRoutineOp = createRoutineOp(builder, callee.getLoc(), callee);
231 routineQueue.push(newRoutineOp);
232 });
233 }
234 return result;
235 }
236
237public:
238 using ACCImplicitRoutineBase<ACCImplicitRoutine>::ACCImplicitRoutineBase;
239
240 void runOnOperation() override {
241 auto module = getOperation();
242 mlir::OpBuilder builder(module.getContext());
243 SymbolTable symTab(module);
244 initRoutineCounter(module);
245
246 acc::OpenACCSupport &accSupport = getAnalysis<acc::OpenACCSupport>();
247
248 LogicalResult result = success();
249
250 // Handle compute regions
251 module.walk([&](Operation *op) {
252 if (isa<ACC_COMPUTE_CONSTRUCT_OPS>(op))
253 if (failed(implicitRoutineForCallsInComputeRegions(op, symTab, builder,
254 accSupport)))
255 result = failure();
256 });
257
258 // Use the device type option from the pass options.
259 acc::DeviceType targetDeviceType = deviceType;
260
261 // Handle existing routines
262 module.walk([&](acc::RoutineOp routineOp) {
263 if (failed(implicitRoutineForCallsInRoutine(
264 routineOp, builder, accSupport, targetDeviceType)))
265 result = failure();
266 });
267
268 if (failed(result))
269 return signalPassFailure();
270 }
271};
272
273} // 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:24
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:185
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:733
Include the generated interface declarations.