1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
//===- ACCImplicitRoutine.cpp - OpenACC Implicit Routine Transform -------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This pass implements the implicit rules described in OpenACC specification
// for `Routine Directive` (OpenACC 3.4 spec, section 2.15.1).
//
// "If no explicit routine directive applies to a procedure whose definition
// appears in the program unit being compiled, then the implementation applies
// an implicit routine directive to that procedure if any of the following
// conditions holds:
// - The procedure is called or its address is accessed in a compute region."
//
// The specification further states:
// "When the implementation applies an implicit routine directive to a
// procedure, it must recursively apply implicit routine directives to other
// procedures for which the above rules specify relevant dependencies. Such
// dependencies can form a cycle, so the implementation must take care to avoid
// infinite recursion."
//
// This pass implements these requirements by:
// 1. Walking through all OpenACC compute constructs and functions already
// marked with `acc routine` in the module and identifying function calls
// within these regions.
// 2. Creating implicit `acc.routine` operations for functions that don't
// already have routine declarations.
// 3. Recursively walking through all existing `acc routine` and creating
// implicit routine operations for function calls within these routines,
// while avoiding infinite recursion through proper tracking.
//
// Requirements:
// -------------
// To use this pass in a pipeline, the following requirements must be met:
//
// 1. Operation Interface Implementation: Operations that define functions
// or call functions should implement `mlir::FunctionOpInterface` and
// `mlir::CallOpInterface` respectively.
//
// 2. Analysis Registration (Optional): If custom behavior is needed for
// determining if a symbol use is valid within GPU regions, the dialect
// should pre-register the `acc::OpenACCSupport` analysis.
//===----------------------------------------------------------------------===//
#include "mlir/Dialect/OpenACC/Transforms/Passes.h"
#include "mlir/Dialect/OpenACC/Analysis/OpenACCSupport.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Operation.h"
#include "mlir/IR/Value.h"
#include "mlir/Interfaces/CallInterfaces.h"
#include "mlir/Interfaces/FunctionInterfaces.h"
#include <queue>
#define DEBUG_TYPE "acc-implicit-routine"
namespace mlir {
namespace acc {
#define GEN_PASS_DEF_ACCIMPLICITROUTINE
#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
} // namespace acc
} // namespace mlir
namespace {
using namespace mlir;
class ACCImplicitRoutine
: public acc::impl::ACCImplicitRoutineBase<ACCImplicitRoutine> {
private:
unsigned routineCounter = 0;
static constexpr llvm::StringRef accRoutinePrefix = "acc_routine_";
// Count existing routine operations and update counter
void initRoutineCounter(ModuleOp module) {
module.walk([&](acc::RoutineOp routineOp) { routineCounter++; });
}
// Check if routine has a default bind clause or a device-type specific bind
// clause. Returns true if `acc routine` has a default bind clause or
// a device-type specific bind clause.
bool isACCRoutineBindDefaultOrDeviceType(acc::RoutineOp op,
acc::DeviceType deviceType) {
// Fast check to avoid device-type specific lookups.
if (!op.getBindIdName() && !op.getBindStrName())
return false;
return op.getBindNameValue().has_value() ||
op.getBindNameValue(deviceType).has_value();
}
// Generate a unique name for the routine and create the routine operation
acc::RoutineOp createRoutineOp(OpBuilder &builder, Location loc,
FunctionOpInterface &callee) {
std::string routineName =
(accRoutinePrefix + std::to_string(routineCounter++)).str();
auto routineOp = acc::RoutineOp::create(
builder, loc,
/* sym_name=*/builder.getStringAttr(routineName),
/* func_name=*/
mlir::SymbolRefAttr::get(builder.getContext(),
builder.getStringAttr(callee.getName())),
/* bindIdName=*/nullptr,
/* bindStrName=*/nullptr,
/* bindIdNameDeviceType=*/nullptr,
/* bindStrNameDeviceType=*/nullptr,
/* worker=*/nullptr,
/* vector=*/nullptr,
/* seq=*/nullptr,
/* nohost=*/nullptr,
/* implicit=*/builder.getUnitAttr(),
/* gang=*/nullptr,
/* gangDim=*/nullptr,
/* gangDimDeviceType=*/nullptr);
// Assert that the callee does not already have routine info attribute
assert(!callee->hasAttr(acc::getRoutineInfoAttrName()) &&
"function is already associated with a routine");
callee->setAttr(
acc::getRoutineInfoAttrName(),
mlir::acc::RoutineInfoAttr::get(
builder.getContext(),
{mlir::SymbolRefAttr::get(builder.getContext(),
builder.getStringAttr(routineName))}));
return routineOp;
}
// Used to walk through a compute region looking for function calls.
void
implicitRoutineForCallsInComputeRegions(Operation *op, SymbolTable &symTab,
mlir::OpBuilder &builder,
acc::OpenACCSupport &accSupport) {
op->walk([&](CallOpInterface callOp) {
if (!callOp.getCallableForCallee())
return;
auto calleeSymbolRef =
dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
// When call is done through ssa value, the callee is not a symbol.
// Skip it because we don't know the call target.
if (!calleeSymbolRef)
return;
auto callee = symTab.lookup<FunctionOpInterface>(
calleeSymbolRef.getLeafReference().str());
// If the callee does not exist or is already a valid symbol for GPU
// regions, skip it
assert(callee && "callee function must be found in symbol table");
if (accSupport.isValidSymbolUse(callOp.getOperation(), calleeSymbolRef))
return;
builder.setInsertionPoint(callee);
createRoutineOp(builder, callee.getLoc(), callee);
});
}
// Recursively handle calls within a routine operation
void implicitRoutineForCallsInRoutine(acc::RoutineOp routineOp,
mlir::OpBuilder &builder,
acc::OpenACCSupport &accSupport,
acc::DeviceType targetDeviceType) {
// When bind clause is used, it means that the target is different than the
// function to which the `acc routine` is used with. Skip this case to
// avoid implicitly recursively marking calls that would not end up on
// device.
if (isACCRoutineBindDefaultOrDeviceType(routineOp, targetDeviceType))
return;
SymbolTable symTab(routineOp->getParentOfType<ModuleOp>());
std::queue<acc::RoutineOp> routineQueue;
routineQueue.push(routineOp);
while (!routineQueue.empty()) {
auto currentRoutine = routineQueue.front();
routineQueue.pop();
auto func = symTab.lookup<FunctionOpInterface>(
currentRoutine.getFuncName().getLeafReference());
func.walk([&](CallOpInterface callOp) {
if (!callOp.getCallableForCallee())
return;
auto calleeSymbolRef =
dyn_cast<SymbolRefAttr>(callOp.getCallableForCallee());
// When call is done through ssa value, the callee is not a symbol.
// Skip it because we don't know the call target.
if (!calleeSymbolRef)
return;
auto callee = symTab.lookup<FunctionOpInterface>(
calleeSymbolRef.getLeafReference().str());
// If the callee does not exist or is already a valid symbol for GPU
// regions, skip it
assert(callee && "callee function must be found in symbol table");
if (accSupport.isValidSymbolUse(callOp.getOperation(), calleeSymbolRef))
return;
builder.setInsertionPoint(callee);
auto newRoutineOp = createRoutineOp(builder, callee.getLoc(), callee);
routineQueue.push(newRoutineOp);
});
}
}
public:
using ACCImplicitRoutineBase<ACCImplicitRoutine>::ACCImplicitRoutineBase;
void runOnOperation() override {
auto module = getOperation();
mlir::OpBuilder builder(module.getContext());
SymbolTable symTab(module);
initRoutineCounter(module);
acc::OpenACCSupport &accSupport = getAnalysis<acc::OpenACCSupport>();
// Handle compute regions
module.walk([&](Operation *op) {
if (isa<ACC_COMPUTE_CONSTRUCT_OPS>(op))
implicitRoutineForCallsInComputeRegions(op, symTab, builder,
accSupport);
});
// Use the device type option from the pass options.
acc::DeviceType targetDeviceType = deviceType;
// Handle existing routines
module.walk([&](acc::RoutineOp routineOp) {
implicitRoutineForCallsInRoutine(routineOp, builder, accSupport,
targetDeviceType);
});
}
};
} // namespace
|