MLIR 24.0.0git
ACCComputeLowering.cpp
Go to the documentation of this file.
1//===- ACCComputeLowering.cpp - Lower ACC compute to 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 decomposes OpenACC compute constructs into a representation that
10// separates the data environment from the compute portion and prepares for
11// parallelism assignment and privatization at the appropriate level.
12//
13// Overview:
14// ---------
15// Each compute construct (`acc.parallel`, `acc.serial`, `acc.kernels`) is
16// lowered to (1) `acc.kernel_environment`, which captures the data environment
17// and (2) `acc.compute_region`, which holds the compute body. Inside the
18// compute region, acc.loop is converted to SCF loops (`scf.parallel` or
19// `scf.for`) with any predetermined parallelism expressed as `par_dims`. This
20// decomposition allows later phases to assign parallelism and handle
21// privatization at the right granularity.
22//
23// Transformations:
24// ----------------
25// 1. Compute constructs: acc.parallel, acc.serial, and acc.kernels are
26// replaced by acc.kernel_environment containing a single acc.compute_region.
27// For acc.parallel / acc.kernels, launch arguments (num_gangs, num_workers,
28// vector_length) become acc.par_width ops (each result is `index`) and are
29// passed as compute_region launch operands. Compute regions with
30// num_gangs(1), num_workers(1), and vector_length(1) and acc serial use a
31// single sequential acc.par_width launch operand.
32//
33// 2. acc.loop: Converted according to context and attributes:
34// - Unstructured: body wrapped in scf.execute_region.
35// - Sequential (serial region, seq clause, or compute region with
36// num_gangs(1), num_workers(1), and vector_length(1)):
37// scf.parallel with par_dims = sequential.
38// - Auto (in parallel/kernels): scf.for with collapse when
39// multi-dimensional.
40// - Orphan (not inside a compute construct): scf.for, no collapse.
41// - Independent (in parallel/kernels): scf.parallel with par_dims from
42// gang/worker/vector mapping (e.g. block_x).
43//
44//===----------------------------------------------------------------------===//
45
47
56#include "mlir/IR/IRMapping.h"
57#include "mlir/IR/Matchers.h"
61#include "llvm/ADT/STLExtras.h"
62
63namespace mlir {
64namespace acc {
65#define GEN_PASS_DEF_ACCCOMPUTELOWERING
66#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
67} // namespace acc
68} // namespace mlir
69
70#define DEBUG_TYPE "acc-compute-lowering"
71
72using namespace mlir;
73using namespace mlir::acc;
74
75namespace {
76
77//===----------------------------------------------------------------------===//
78// Helper functions
79//===----------------------------------------------------------------------===//
80
81static bool isOpInComputeRegion(Operation *op) {
82 Region *region = op->getBlock()->getParent();
83 return getEnclosingComputeOp(*region) != nullptr;
84}
85
86static bool isOpInSerialRegion(Operation *op) {
87 if (auto parallelOp = op->getParentOfType<ParallelOp>())
88 return parallelOp.isEffectivelySerial();
89 if (auto kernelsOp = op->getParentOfType<KernelsOp>())
90 return kernelsOp.isEffectivelySerial();
91 if (op->getParentOfType<SerialOp>())
92 return true;
93 if (auto computeRegion = op->getParentOfType<ComputeRegionOp>())
94 return computeRegion.isEffectivelySerial();
95 if (auto funcOp = op->getParentOfType<FunctionOpInterface>()) {
96 if (isSpecializedAccRoutine(funcOp)) {
97 auto attr = funcOp->getAttrOfType<SpecializedRoutineAttr>(
99 if (attr && attr.getLevel().getValue() == ParLevel::seq)
100 return true;
101 }
102 }
103 return false;
104}
105
106/// Clone defining ops of constant live-in values into `region`, rewrite uses
107/// inside the region to the clones, and remove those values from
108/// `liveInValues` so they are not threaded through `acc.compute_region` ins.
109static void materializeConstantLiveInsIntoRegion(Region &region,
110 SetVector<Value> &liveInValues,
111 RewriterBase &rewriter) {
112 SmallVector<Value> constantLiveIns;
113 for (Value v : liveInValues) {
114 Operation *defOp = v.getDefiningOp();
115 if (defOp && matchPattern(defOp, m_Constant())) {
116 // As per the definition of ConstantLike trait, constants must have a
117 // single result.
118 assert(defOp->getNumResults() == 1 &&
119 "constants must have a single result");
120 constantLiveIns.push_back(v);
121 }
122 }
123 if (constantLiveIns.empty())
124 return;
125
126 OpBuilder::InsertionGuard guard(rewriter);
127 rewriter.setInsertionPointToStart(&region.front());
128
129 for (Value v : constantLiveIns) {
130 Value newV = rewriter.clone(*v.getDefiningOp())->getResult(0);
131 replaceAllUsesInRegionWith(v, newV, region);
132 liveInValues.remove(v);
133 }
134}
135
136/// Return the device type from which gang/worker/vector clauses should be read.
137/// If the requested device type has any such clauses, use that exclusively;
138/// otherwise fall back to the default (DeviceType::None).
139static DeviceType getGangWorkerVectorDeviceType(LoopOp loopOp,
140 DeviceType deviceType) {
141 if (deviceType != DeviceType::None &&
142 loopOp.hasAnyGangWorkerVector(deviceType))
143 return deviceType;
144 return DeviceType::None;
145}
146
147template <typename ComputeConstructT>
148static DeviceType getParDimsDeviceType(ComputeConstructT computeOp,
149 DeviceType deviceType) {
150 if (deviceType != DeviceType::None &&
151 computeOp.hasAnyGangWorkerVector(deviceType))
152 return deviceType;
153 return DeviceType::None;
154}
155
156/// Map loop parallelism clauses (gang/worker/vector) to GPU parallel
157/// dimensions using the given mapping policy.
159getParallelDimensions(LoopOp loopOp, const ACCToGPUMappingPolicy &policy,
160 DeviceType deviceType) {
161 deviceType = getGangWorkerVectorDeviceType(loopOp, deviceType);
163 auto *ctx = loopOp->getContext();
164
165 if (loopOp.hasVector(deviceType))
166 insertParDim(parDims, policy.vectorDim(ctx));
167 if (loopOp.hasWorker(deviceType))
168 insertParDim(parDims, policy.workerDim(ctx));
169 if (auto gangDimValue = loopOp.getGangValue(GangArgType::Dim, deviceType)) {
170 if (auto gangDimDefOp =
171 gangDimValue.getDefiningOp<arith::ConstantIntOp>()) {
172 auto gangLevel = getGangParLevel(gangDimDefOp.value());
173 insertParDim(parDims, policy.gangDim(ctx, gangLevel));
174 }
175 } else if (loopOp.hasGang(deviceType)) {
176 insertParDim(parDims, policy.gangDim(ctx, ParLevel::gang_dim1));
177 }
178 return parDims;
179}
180
181/// Build `acc.compute_region` launch operands: one sequential `acc.par_width`
182/// for `acc.serial`, for `acc.parallel` / `acc.kernels` when every num_gangs
183/// operand and num_workers / vector_length are the constant 1, and otherwise
184/// `acc.par_width` from gang/worker/vector (device-type operands first, then
185/// default DeviceType::None).
186template <typename ComputeConstructT>
188assignKnownLaunchArgs(ComputeConstructT computeOp, DeviceType deviceType,
189 RewriterBase &rewriter,
190 const ACCToGPUMappingPolicy &policy) {
191 auto *ctx = rewriter.getContext();
192 auto loc = computeOp->getLoc();
193
194 if constexpr (std::is_same_v<ComputeConstructT, SerialOp>) {
195 return {ParWidthOp::create(rewriter, loc, Value(), policy.seqDim(ctx))};
196 } else if constexpr (llvm::is_one_of<ComputeConstructT, ParallelOp,
197 KernelsOp>::value) {
198 if (computeOp.isEffectivelySerial())
199 return {ParWidthOp::create(rewriter, loc, Value(), policy.seqDim(ctx))};
200
201 deviceType = getParDimsDeviceType(computeOp, deviceType);
202
203 SmallVector<Value> values;
204 auto indexTy = rewriter.getIndexType();
205
206 auto numGangs = computeOp.getNumGangsValues(deviceType);
207 for (auto [gangDimIdx, gangSize] : llvm::enumerate(numGangs)) {
208 auto gangLevel = getGangParLevel(gangDimIdx + 1);
209 values.push_back(ParWidthOp::create(
210 rewriter, loc,
211 getValueOrCreateCastToIndexLike(rewriter, gangSize.getLoc(), indexTy,
212 gangSize),
213 policy.gangDim(ctx, gangLevel)));
214 }
215
216 Value numWorkers = computeOp.getNumWorkersValue(deviceType);
217 if (numWorkers) {
218 values.push_back(ParWidthOp::create(
219 rewriter, loc,
220 getValueOrCreateCastToIndexLike(rewriter, numWorkers.getLoc(),
221 indexTy, numWorkers),
222 policy.workerDim(ctx)));
223 }
224
225 Value vectorLength = computeOp.getVectorLengthValue(deviceType);
226 if (vectorLength) {
227 values.push_back(ParWidthOp::create(
228 rewriter, loc,
229 getValueOrCreateCastToIndexLike(rewriter, vectorLength.getLoc(),
230 indexTy, vectorLength),
231 policy.vectorDim(ctx)));
232 }
233 return values;
234 } else {
235 llvm_unreachable("assignKnownLaunchArgs: expected parallel, kernels, or "
236 "serial");
237 }
238}
239
240//===----------------------------------------------------------------------===//
241// Loop conversion pattern
242//===----------------------------------------------------------------------===//
243
244class ACCLoopConversion : public OpRewritePattern<LoopOp> {
245public:
246 ACCLoopConversion(MLIRContext *ctx, const ACCToGPUMappingPolicy &policy,
247 DeviceType deviceType)
248 : OpRewritePattern<LoopOp>(ctx), policy(policy), deviceType(deviceType) {}
249
250 LogicalResult matchAndRewrite(LoopOp loopOp,
251 PatternRewriter &rewriter) const override {
252 if (loopOp.getUnstructured()) {
253 auto executeRegion =
255 if (!executeRegion)
256 return failure();
257 rewriter.replaceOp(loopOp, executeRegion);
258 return success();
259 }
260
261 LoopParMode parMode = loopOp.getDefaultOrDeviceTypeParallelism(deviceType);
262
263 if (parMode == LoopParMode::loop_seq || isOpInSerialRegion(loopOp)) {
264 // Use scf.for with sequential loops, because the loop's parallelism is
265 // already determined.
266 auto forOp =
267 convertACCLoopToSCFFor(loopOp, rewriter, /*enableCollapse=*/true);
268 if (!forOp)
269 return failure();
270 setParDimsAttr(forOp, GPUParallelDimsAttr::seq(loopOp->getContext()));
271 rewriter.replaceOp(loopOp, forOp);
272 } else if (parMode == LoopParMode::loop_auto) {
273 // All loops in serial regions should have already been handled.
274 assert(!isOpInSerialRegion(loopOp) &&
275 "Expected loop to be in non-serial region");
276 // Mark as scf.for to allow auto-parallelization analysis later.
277 auto forOp =
278 convertACCLoopToSCFFor(loopOp, rewriter, /*enableCollapse=*/true);
279 if (!forOp)
280 return failure();
282 getParallelDimensions(loopOp, policy, deviceType);
283 if (!parDims.empty()) {
284 auto parDimsAttr =
285 GPUParallelDimsAttr::get(loopOp->getContext(), parDims);
286 setParDimsAttr(forOp, parDimsAttr);
287 }
288 rewriter.replaceOp(loopOp, forOp);
289 } else if (!isOpInComputeRegion(loopOp) &&
291 loopOp->getParentOfType<FunctionOpInterface>())) {
292 // This loop is an orphan `acc loop` but it is not in any sort
293 // of compute region. Thus it is just a sequential non-accelerator loop.
294 auto forOp =
295 convertACCLoopToSCFFor(loopOp, rewriter, /*enableCollapse=*/false);
296 if (!forOp)
297 return failure();
298 rewriter.replaceOp(loopOp, forOp);
299 } else {
300 assert(parMode == LoopParMode::loop_independent &&
301 "Expected loop to be independent");
302 auto parallelOp = convertACCLoopToSCFParallel(loopOp, rewriter);
303 if (!parallelOp)
304 return failure();
307 getParallelDimensions(loopOp, policy, deviceType);
308 if (!parDims.empty()) {
309 auto parDimsAttr =
310 GPUParallelDimsAttr::get(loopOp->getContext(), parDims);
311 setParDimsAttr(parallelOp, parDimsAttr);
312 }
313
314 rewriter.replaceOp(loopOp, parallelOp);
315 }
316 return success();
317 }
318
319private:
321 DeviceType deviceType;
322};
323
324//===----------------------------------------------------------------------===//
325// Compute construct conversion pattern
326//===----------------------------------------------------------------------===//
327
328template <typename ComputeConstructT>
329class ComputeOpConversion : public OpRewritePattern<ComputeConstructT> {
330public:
331 ComputeOpConversion(MLIRContext *ctx, const ACCToGPUMappingPolicy &policy,
332 DeviceType deviceType)
333 : OpRewritePattern<ComputeConstructT>(ctx), policy(policy),
335
336 LogicalResult matchAndRewrite(ComputeConstructT computeOp,
337 PatternRewriter &rewriter) const override {
338 rewriter.setInsertionPoint(computeOp);
339 auto kernelEnv =
340 KernelEnvironmentOp::createAndPopulate(computeOp, deviceType, rewriter);
341 auto launchArgs =
342 assignKnownLaunchArgs(computeOp, deviceType, rewriter, policy);
343 Region &region = computeOp.getRegion();
344 SetVector<Value> liveInValues;
345 getUsedValuesDefinedAbove(region, region, liveInValues);
346 materializeConstantLiveInsIntoRegion(region, liveInValues, rewriter);
347 IRMapping mapping;
348 auto computeRegion = buildComputeRegion(
349 computeOp->getLoc(), launchArgs, liveInValues.getArrayRef(),
350 ComputeConstructT::getOperationName(), region, rewriter, mapping);
351 if (!computeRegion) {
352 rewriter.eraseOp(kernelEnv);
353 return failure();
354 }
355 rewriter.eraseOp(computeOp);
356 return success();
357 }
358
359private:
360 const ACCToGPUMappingPolicy &policy;
361 DeviceType deviceType;
362};
363
364//===----------------------------------------------------------------------===//
365// Pass implementation
366//===----------------------------------------------------------------------===//
367
368class ACCComputeLowering
369 : public acc::impl::ACCComputeLoweringBase<ACCComputeLowering> {
370public:
371 using ACCComputeLoweringBase::ACCComputeLoweringBase;
372
373 void runOnOperation() override {
374 auto op = getOperation();
375 auto *context = op.getContext();
376
377 DefaultACCToGPUMappingPolicy policy;
378
379 // Part 1: Convert acc.loop to scf.parallel/scf.for while the parent
380 // compute construct is still present (needed to determine conversion
381 // strategy).
382 RewritePatternSet loopPatterns(context);
383 loopPatterns.insert<ACCLoopConversion>(context, policy, deviceType);
384 if (failed(applyPatternsGreedily(op, std::move(loopPatterns))))
385 return signalPassFailure();
386
387 // Part 2: Convert acc.parallel, acc.kernels, and acc.serial to
388 // acc.kernel_environment { acc.compute_region { ... } }.
389 RewritePatternSet computePatterns(context);
390 computePatterns
391 .insert<ComputeOpConversion<ParallelOp>, ComputeOpConversion<KernelsOp>,
392 ComputeOpConversion<SerialOp>>(context, policy, deviceType);
393 if (failed(applyPatternsGreedily(op, std::move(computePatterns))))
394 return signalPassFailure();
395 }
396};
397
398} // namespace
return success()
Region * getParent() const
Provide a 'getParent' method for ilist_node_with_parent methods.
Definition Block.cpp:27
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:55
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
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:350
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
Definition Builders.cpp:567
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:433
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:400
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & front()
Definition Region.h:65
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
ParDimAttrT seqDim(MLIRContext *ctx) const
ParDimAttrT vectorDim(MLIRContext *ctx) const
ParDimAttrT workerDim(MLIRContext *ctx) const
ParDimAttrT gangDim(MLIRContext *ctx, ParLevel level) const
Convenience methods for specific parallelism levels.
::mlir::Pass::Option< mlir::acc::DeviceType > deviceType
Specialization of arith.constant op that returns an integer value.
Definition Arith.h:55
ParLevel getGangParLevel(int64_t gangDimValue)
Convert a gang dimension value (1, 2, or 3) to the corresponding ParLevel.
void insertParDim(llvm::SmallVector< GPUParallelDimAttr > &parDims, GPUParallelDimAttr parDim)
Insert parDim into parDims while preserving dimension ordering.
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.
bool isSpecializedAccRoutine(mlir::Operation *op)
Used to check whether this is a specialized accelerator version of acc routine function.
Definition OpenACC.h:201
scf::ParallelOp convertACCLoopToSCFParallel(LoopOp loopOp, RewriterBase &rewriter)
Convert acc.loop to scf.parallel.
mlir::Operation * getEnclosingComputeOp(mlir::Region &region)
Used to obtain the enclosing compute construct operation that contains the provided region.
scf::ExecuteRegionOp convertUnstructuredACCLoopToSCFExecuteRegion(LoopOp loopOp, RewriterBase &rewriter)
Convert an unstructured acc.loop to scf.execute_region.
void setParDimsAttr(Operation *op, GPUParallelDimsAttr attr)
Set parallel dimensions on op.
scf::ForOp convertACCLoopToSCFFor(LoopOp loopOp, RewriterBase &rewriter, bool enableCollapse)
Convert a structured acc.loop to scf.for.
ACCParMappingPolicy< mlir::acc::GPUParallelDimAttr > ACCToGPUMappingPolicy
Type alias for the GPU-specific mapping policy.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
void replaceAllUsesInRegionWith(Value orig, Value replacement, Region &region)
Replace all uses of orig within the given region with replacement.
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
Value getValueOrCreateCastToIndexLike(OpBuilder &b, Location loc, Type targetType, Value value)
Create a cast from an index-like value (index or integer) to another index-like value.
Definition Utils.cpp:122
void getUsedValuesDefinedAbove(Region &region, Region &limit, SetVector< Value > &values)
Fill values with a list of values defined at the ancestors of the limit region and used within region...
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...