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
54
58#include "mlir/IR/Builders.h"
60#include "mlir/IR/BuiltinOps.h"
61#include "mlir/IR/Operation.h"
62#include "mlir/IR/Value.h"
65#include <queue>
66
67#define DEBUG_TYPE "acc-implicit-routine"
68
69namespace mlir {
70namespace acc {
71#define GEN_PASS_DEF_ACCIMPLICITROUTINE
72#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
73} // namespace acc
74} // namespace mlir
75
76namespace {
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 // Generate a unique name for the routine and create the routine operation
91 acc::RoutineOp createRoutineOp(OpBuilder &builder, Location loc,
92 FunctionOpInterface &callee) {
93 std::string routineName =
94 (accRoutinePrefix + std::to_string(routineCounter++)).str();
95 auto routineOp = acc::RoutineOp::create(
96 builder, loc,
97 /* sym_name=*/builder.getStringAttr(routineName),
98 /* sym_visibility=*/nullptr,
99 /* func_name=*/
100 mlir::SymbolRefAttr::get(builder.getContext(),
101 builder.getStringAttr(callee.getName())),
102 /* bindIdName=*/nullptr,
103 /* bindStrName=*/nullptr,
104 /* bindIdNameDeviceType=*/nullptr,
105 /* bindStrNameDeviceType=*/nullptr,
106 /* worker=*/nullptr,
107 /* vector=*/nullptr,
108 /* seq=*/nullptr,
109 /* nohost=*/nullptr,
110 /* implicit=*/builder.getUnitAttr(),
111 /* gang=*/nullptr,
112 /* gangDim=*/nullptr,
113 /* gangDimDeviceType=*/nullptr);
114
115 // Assert that the callee does not already have routine info attribute
116 assert(!callee->hasDiscardableAttr(acc::getRoutineInfoAttrName()) &&
117 "function is already associated with a routine");
118
119 callee->setDiscardableAttr(
121 mlir::acc::RoutineInfoAttr::get(
122 builder.getContext(),
123 {mlir::SymbolRefAttr::get(builder.getContext(),
124 builder.getStringAttr(routineName))}));
125 return routineOp;
126 }
127
128 // Used to walk through a compute region looking for function calls.
129 LogicalResult
130 implicitRoutineForCallsInComputeRegions(Operation *op, SymbolTable &symTab,
131 mlir::OpBuilder &builder,
132 acc::OpenACCSupport &accSupport) {
133 LogicalResult result = success();
134 op->walk([&](CallOpInterface callOp) {
135 if (acc::isInOffTargetBranch(callOp.getOperation(), *theDeviceTypes))
136 return;
137 if (!callOp.getCallableForCallee())
138 return;
139
140 auto calleeSymbolRef =
141 dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
142 // When call is done through ssa value, the callee is not a symbol.
143 // Skip it because we don't know the call target.
144 if (!calleeSymbolRef)
145 return;
146
147 auto callee = symTab.lookup<FunctionOpInterface>(
148 calleeSymbolRef.getLeafReference().str());
149 // If the callee does not exist or is already a valid symbol for GPU
150 // regions, skip it
151 if (!callee)
152 return;
153 // Already a valid symbol for GPU regions (e.g. an existing routine or an
154 // LLVM intrinsic), skip it.
155 if (accSupport.isValidSymbolUse(callOp.getOperation(), calleeSymbolRef))
156 return;
157 // Without a definition in this compilation unit an implicit routine
158 // cannot be applied, so the call target needs explicit routine
159 // information.
160 if (callee.isExternal()) {
161 callOp->emitError() << "Calls in an acc compute region must be marked "
162 "with acc routine: "
163 << calleeSymbolRef.getLeafReference();
164 result = failure();
165 return;
166 }
167 builder.setInsertionPoint(callee);
168 createRoutineOp(builder, callee.getLoc(), callee);
169 });
170 return result;
171 }
172
173 // Recursively handle calls within a routine operation
174 LogicalResult implicitRoutineForCallsInRoutine(
175 acc::RoutineOp routineOp, mlir::OpBuilder &builder,
176 acc::OpenACCSupport &accSupport, acc::DeviceType targetDeviceType) {
177 // When bind clause is used, it means that the target is different than the
178 // function to which the `acc routine` is used with. Skip this case to
179 // avoid implicitly recursively marking calls that would not end up on
180 // device.
181 if (routineOp.getEffectiveBindName(targetDeviceType))
182 return success();
183
184 SymbolTable symTab(routineOp->getParentOfType<ModuleOp>());
185 std::queue<acc::RoutineOp> routineQueue;
186 routineQueue.push(routineOp);
187 LogicalResult result = success();
188 while (!routineQueue.empty()) {
189 auto currentRoutine = routineQueue.front();
190 routineQueue.pop();
191 auto func = symTab.lookup<FunctionOpInterface>(
192 currentRoutine.getFuncName().getLeafReference());
193 func.walk([&](CallOpInterface callOp) {
194 if (acc::isInOffTargetBranch(callOp.getOperation(), *theDeviceTypes))
195 return;
196 if (!callOp.getCallableForCallee())
197 return;
198
199 auto calleeSymbolRef =
200 dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
201 // When call is done through ssa value, the callee is not a symbol.
202 // Skip it because we don't know the call target.
203 if (!calleeSymbolRef)
204 return;
205
206 auto callee = symTab.lookup<FunctionOpInterface>(
207 calleeSymbolRef.getLeafReference().str());
208 // If the callee does not exist or is already a valid symbol for GPU
209 // regions, skip it
210 if (!callee)
211 return;
212 // Already a valid symbol for GPU regions (e.g. an existing routine or
213 // an LLVM intrinsic), skip it.
214 if (accSupport.isValidSymbolUse(callOp.getOperation(), calleeSymbolRef))
215 return;
216 // Without a definition in this compilation unit an implicit routine
217 // cannot be applied, so the call target needs explicit routine
218 // information.
219 if (callee.isExternal()) {
220 callOp->emitError()
221 << "Calls in acc routine must also be marked with acc routine: "
222 << calleeSymbolRef.getLeafReference();
223 result = failure();
224 return;
225 }
226 builder.setInsertionPoint(callee);
227 auto newRoutineOp = createRoutineOp(builder, callee.getLoc(), callee);
228 routineQueue.push(newRoutineOp);
229 });
230 }
231 return result;
232 }
233
234public:
235 using ACCImplicitRoutineBase<ACCImplicitRoutine>::ACCImplicitRoutineBase;
236
237 void getDependentDialects(DialectRegistry &registry) const override {
238 ACCImplicitRoutineBase<ACCImplicitRoutine>::getDependentDialects(registry);
240 }
241
242 void runOnOperation() override {
243 auto module = getOperation();
244 mlir::OpBuilder builder(module.getContext());
245 SymbolTable symTab(module);
246 initRoutineCounter(module);
247
248 acc::OpenACCSupport &accSupport = getAnalysis<acc::OpenACCSupport>();
249
250 LogicalResult result = success();
251
252 // Handle compute regions
253 module.walk([&](Operation *op) {
254 if (isa<ACC_COMPUTE_CONSTRUCT_OPS>(op))
255 if (failed(implicitRoutineForCallsInComputeRegions(op, symTab, builder,
256 accSupport)))
257 result = failure();
258 });
259
260 // Use the device type option from the pass options.
261 acc::DeviceType targetDeviceType = deviceType;
262
263 // Handle existing routines
264 module.walk([&](acc::RoutineOp routineOp) {
265 if (failed(implicitRoutineForCallsInRoutine(
266 routineOp, builder, accSupport, targetDeviceType)))
267 result = failure();
268 });
269
270 if (failed(result))
271 return signalPassFailure();
272 }
273};
274} // 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
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
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:851
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:201
void registerAllExtensions(DialectRegistry &registry)
Register the standard OpenACC models for GPU, LLVM and MemRef IR.
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:744
Include the generated interface declarations.