MLIR 24.0.0git
ACCRoutineLowering.cpp
Go to the documentation of this file.
1//===- ACCRoutineLowering.cpp - Wrap ACC routines in compute_region -------===//
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 handles `acc routine` directive by creating specialized
10// functions with appropriate parallelism information that can be used for
11// eventual creation of device function.
12//
13// Overview:
14// ---------
15// For each acc.routine that is not bound by name, the pass creates a new
16// function (the "device" copy) whose body is a single acc.compute_region
17// containing a clone of the original (host) function body. Parallelism is
18// expressed by one acc.par_width derived from the routine's clauses (seq,
19// vector, worker, gang). The device copy created is simply a staging
20// place for eventual move to device module level function.
21//
22// Transformations:
23// ----------------
24// 1. Device function: Same signature as the host; attributes copied except
25// acc.routine_info. The acc.specialized_routine attribute is set with the
26// routine symbol, par level, and original function name.
27//
28// 2. Body: One acc.par_width, one acc.compute_region that clones the host
29// body. Multi-block host bodies are wrapped in scf.execute_region inside
30// the compute_region.
31//
32// 3. Finalization: acc.routine's func_name is updated to the device function.
33//
34//===----------------------------------------------------------------------===//
35
37
43#include "mlir/IR/IRMapping.h"
45#include "mlir/IR/SymbolTable.h"
46#include "mlir/IR/Value.h"
47
48namespace mlir {
49namespace acc {
50#define GEN_PASS_DEF_ACCROUTINELOWERING
51#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
52} // namespace acc
53} // namespace mlir
54
55#define DEBUG_TYPE "acc-routine-lowering"
56
57using namespace mlir;
58using namespace mlir::acc;
59
60namespace {
61
62/// Compute the ParLevel from an acc.routine op for specialization.
63static ParLevel computeParLevel(RoutineOp routineOp, DeviceType deviceType) {
64 auto gangDim = routineOp.getGangDimValue(deviceType);
65 if (!gangDim)
66 gangDim = routineOp.getGangDimValue();
67 if (gangDim) {
68 switch (*gangDim) {
69 case 1:
70 return ParLevel::gang_dim1;
71 case 2:
72 return ParLevel::gang_dim2;
73 case 3:
74 return ParLevel::gang_dim3;
75 default:
76 break;
77 }
78 }
79 if (routineOp.hasGang(deviceType) || routineOp.hasGang())
80 return ParLevel::gang_dim1;
81 if (routineOp.hasWorker(deviceType) || routineOp.hasWorker())
82 return ParLevel::worker;
83 if (routineOp.hasVector(deviceType) || routineOp.hasVector())
84 return ParLevel::vector;
85 return ParLevel::seq;
86}
87
88/// Collect return operands from the function (first block with func.return).
89static void getReturnValues(func::FuncOp func, SmallVectorImpl<Value> &result) {
90 result.clear();
91 for (Block &block : func.getBody().getBlocks()) {
92 if (auto returnOp = dyn_cast<func::ReturnOp>(block.getTerminator())) {
93 result.assign(returnOp.operand_begin(), returnOp.operand_end());
94 break;
95 }
96 }
97}
98
99/// Create the device function with the same signature as the host, set
100/// specialized_routine, and add a single block with the same block arguments.
101static func::FuncOp createFunctionForDeviceStaging(func::FuncOp hostFunc,
102 RoutineOp routineOp,
103 ParLevel parLevel,
104 MLIRContext *ctx,
105 IRRewriter &rewriter) {
106 Location loc = hostFunc.getLoc();
107 FunctionType funcType = hostFunc.getFunctionType();
108 func::FuncOp deviceFunc =
109 func::FuncOp::create(rewriter, loc, hostFunc.getName(), funcType);
110 deviceFunc->setAttrs(hostFunc->getAttrs());
111 deviceFunc->removeAttr(getRoutineInfoAttrName());
112 deviceFunc->setAttr(getSpecializedRoutineAttrName(),
113 SpecializedRoutineAttr::get(
114 ctx, SymbolRefAttr::get(ctx, routineOp.getSymName()),
115 ParLevelAttr::get(ctx, parLevel),
116 StringAttr::get(ctx, hostFunc.getName())));
117
118 Block *sourceBlock = &hostFunc.getBody().front();
119 Block *newBlock = rewriter.createBlock(&deviceFunc.getRegion());
120 for (BlockArgument arg : sourceBlock->getArguments())
121 newBlock->addArgument(arg.getType(), hostFunc.getLoc());
122
123 return deviceFunc;
124}
125
126/// Fill the device function body: one acc.par_width, one acc.compute_region
127/// (cloning the host body with inputArgsToMap), then func.return.
128static LogicalResult
129buildRoutineBody(func::FuncOp deviceFunc, func::FuncOp hostFunc,
130 ArrayRef<Value> funcReturnVals, ParLevel parLevel,
131 DefaultACCToGPUMappingPolicy &policy, IRRewriter &rewriter) {
132 Block *newBlock = &deviceFunc.getBody().front();
133 Block *sourceBlock = &hostFunc.getBody().front();
134 Location loc = hostFunc.getLoc();
135 MLIRContext *ctx = rewriter.getContext();
136
137 rewriter.setInsertionPointToStart(newBlock);
138 GPUParallelDimAttr parDim = policy.map(ctx, parLevel);
139 Value parWidthVal = ParWidthOp::create(rewriter, loc, Value(), parDim);
140 SmallVector<Value, 4> inputArgs(newBlock->getArguments().begin(),
141 newBlock->getArguments().end());
142
143 // Normally the region passed to buildComputeRegion is something in the
144 // current function. Here we pass the body of the original (host) function as
145 // an optimization to avoid cloning twice (once for a staged device copy and
146 // again when creating the compute region). Since we clone only once, we must
147 // also provide the original function's arguments so the mapping is correct
148 // when cloning the body.
149 ValueRange sourceArgsToMap = sourceBlock->getArguments();
150
151 IRMapping mapping;
152 rewriter.setInsertionPointAfter(parWidthVal.getDefiningOp());
153 ComputeRegionOp computeRegion = buildComputeRegion(
154 loc, {parWidthVal}, inputArgs, RoutineOp::getOperationName(),
155 hostFunc.getBody(), rewriter, mapping,
156 /*output=*/funcReturnVals, /*kernelFuncName=*/{},
157 /*kernelModuleName=*/{}, /*stream=*/{}, sourceArgsToMap);
158 if (!computeRegion)
159 return failure();
160
161 rewriter.setInsertionPointAfter(computeRegion);
162 if (funcReturnVals.empty())
163 func::ReturnOp::create(rewriter, loc);
164 else
165 func::ReturnOp::create(rewriter, loc, computeRegion.getResults());
166
167 return success();
168}
169
170/// Update acc.routine refs
171static void finalizeRoutines(
172 SmallVectorImpl<std::pair<func::FuncOp, RoutineOp>> &accRoutineInfo,
173 MLIRContext *ctx) {
174 for (auto &[deviceFunc, routineOp] : accRoutineInfo) {
175 routineOp.setFuncNameAttr(SymbolRefAttr::get(ctx, deviceFunc.getName()));
176 routineOp->moveBefore(deviceFunc);
177 }
178}
179
180class ACCRoutineLowering
181 : public acc::impl::ACCRoutineLoweringBase<ACCRoutineLowering> {
182public:
183 using ACCRoutineLoweringBase::ACCRoutineLoweringBase;
184
185 void runOnOperation() override {
186 ModuleOp mod = getOperation();
187 if (mod.getOps<RoutineOp>().empty()) {
188 LLVM_DEBUG(llvm::dbgs()
189 << "Skipping ACCRoutineLowering - no acc.routine ops\n");
190 return;
191 }
192
193 SymbolTable symTab(mod);
194 MLIRContext *ctx = mod.getContext();
195 IRRewriter rewriter(ctx);
197
198 // Pair: device function, routine operation
200
201 for (RoutineOp routineOp : mod.getOps<RoutineOp>()) {
202 if (routineOp.getBindNameValue() ||
203 routineOp.getBindNameValue(deviceType))
204 continue;
205
206 func::FuncOp hostFunc = symTab.lookup<func::FuncOp>(
207 routineOp.getFuncName().getLeafReference());
208 if (!hostFunc) {
209 routineOp.emitError("acc routine function not found in symbol table");
210 return signalPassFailure();
211 }
212 if (hostFunc.isExternal())
213 continue;
214
215 SmallVector<Value, 4> funcReturnVals;
216 getReturnValues(hostFunc, funcReturnVals);
217
218 OpBuilder::InsertionGuard guard(rewriter);
219 ParLevel parLevel = computeParLevel(routineOp, deviceType);
220 func::FuncOp deviceFunc = createFunctionForDeviceStaging(
221 hostFunc, routineOp, parLevel, ctx, rewriter);
222 if (failed(buildRoutineBody(deviceFunc, hostFunc, funcReturnVals,
223 parLevel, policy, rewriter)))
224 return signalPassFailure();
225
226 accRoutineInfo.push_back({deviceFunc, routineOp});
227 symTab.insert(deviceFunc);
228 }
229
230 finalizeRoutines(accRoutineInfo, ctx);
231 }
232};
233
234} // namespace
return success()
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:33
Operation & front()
Definition Block.h:177
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:158
BlockArgListType getArguments()
Definition Block.h:111
MLIRContext * getContext() const
Definition Builders.h:56
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
This class coordinates rewriting a piece of IR outside of a pattern rewrite, providing a way to keep ...
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Definition Builders.cpp:439
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
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.
StringAttr insert(Operation *symbol, Block::iterator insertPt={})
Insert a new symbol into the table, and rename it as necessary to avoid collisions.
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Default policy that provides the standard GPU mapping: gang(dim:1) -> BlockX (gridDim....
mlir::acc::GPUParallelDimAttr map(MLIRContext *ctx, ParLevel level) const override
Map an OpenACC parallelism level to target dimension.
static constexpr StringLiteral getSpecializedRoutineAttrName()
Definition OpenACC.h:189
ComputeRegionOp buildComputeRegion(Location loc, ValueRange launchArgs, ValueRange inputArgs, llvm::StringRef origin, Region &regionToClone, RewriterBase &rewriter, IRMapping &mapping, ValueRange output={}, FlatSymbolRefAttr kernelFuncName={}, FlatSymbolRefAttr kernelModuleName={}, Value stream={}, ValueRange inputArgsToMap={})
Build an acc.compute_region operation by cloning a source region.
static constexpr StringLiteral getRoutineInfoAttrName()
Definition OpenACC.h:185
Include the generated interface declarations.