MLIR 23.0.0git
ACCSpecializeForHost.cpp
Go to the documentation of this file.
1//===- ACCSpecializeForHost.cpp -------------------------------------------===//
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 converts OpenACC operations to host-compatible representations,
10// enabling execution on the host rather than on accelerator devices.
11//
12// Overview:
13// ---------
14// The pass operates in two modes depending on the `enableHostFallback` option:
15//
16// 1. Default Mode (Orphan Operations Only):
17// Only converts "orphan" ACC operations that are not inside or attached to
18// compute regions. This is used for host routines (acc routine marked for
19// host) where structured/unstructured data constructs, compute constructs,
20// and their associated data operations should be preserved.
21//
22// 2. Host Fallback Mode (enableHostFallback=true):
23// Converts ALL ACC operations within the region to host equivalents. This
24// is used when the `if` clause evaluates to false at runtime and the
25// entire ACC region needs to fall back to host execution.
26//
27// Transformations (Orphan Mode):
28// ------------------------------
29// The following orphan operations are converted:
30//
31// 1. Atomic Ops (converted to load/store):
32// acc.atomic.update -> load + compute + store
33// acc.atomic.read -> load + store (copy)
34// acc.atomic.write -> store
35// acc.atomic.capture -> inline region contents
36//
37// 2. Loop Ops (converted to SCF):
38// acc.loop (structured) -> scf.for
39// acc.loop (unstructured) -> scf.execute_region
40//
41// 3. Orphan Data Entry Ops (replaced with var operand):
42// acc.cache, acc.private, acc.firstprivate, acc.reduction
43// (only if NOT connected to compute constructs or loop)
44//
45// Transformations (Host Fallback Mode):
46// -------------------------------------
47// In addition to orphan transformations, ALL of the following are converted:
48//
49// 1. Data Entry Ops (replaced with var operand):
50// acc.copyin, acc.create, acc.attach, acc.present, acc.deviceptr,
51// acc.get_deviceptr, acc.nocreate, acc.declare_device_resident,
52// acc.declare_link, acc.use_device, acc.update_device
53//
54// 2. Data Exit Ops (erased):
55// acc.copyout, acc.delete, acc.detach, acc.update_host
56//
57// 3. Structured Data/Compute Constructs (region inlined):
58// acc.data, acc.host_data, acc.kernel_environment, acc.declare,
59// acc.parallel, acc.serial, acc.kernels
60//
61// 4. Unstructured Data Ops (erased):
62// acc.enter_data, acc.exit_data, acc.update
63//
64// 5. Declare Ops (erased):
65// acc.declare_enter, acc.declare_exit
66//
67// 6. Runtime Ops (erased):
68// acc.init, acc.shutdown, acc.set, acc.wait, acc.terminator
69//
70// Requirements:
71// -------------
72// For atomic operation conversion, variables must implement the
73// `acc::PointerLikeType` interface to enable generating load/store operations.
74//
75// The pass uses `OpenACCSupport::emitNYI()` to report unsupported cases.
76//
77//===----------------------------------------------------------------------===//
78
80
87#include "mlir/IR/BuiltinOps.h"
89#include "mlir/IR/Operation.h"
92
93namespace mlir {
94namespace acc {
95#define GEN_PASS_DEF_ACCSPECIALIZEFORHOST
96#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
97} // namespace acc
98} // namespace mlir
99
100#define DEBUG_TYPE "acc-specialize-for-host"
101
102using namespace mlir;
103using namespace mlir::acc;
104
105/// Check if an operation is inside an ACC compute construct.
107 while ((op = op->getParentOp()))
108 if (isa<ACC_COMPUTE_CONSTRUCT_OPS>(op))
109 return true;
110 return false;
111}
112
113namespace {
114
115// Lower orphan acc.atomic.update by: load from addr, clone region expr with
116// the loaded value, then store the computed result back to addr.
117// Only matches if NOT inside a compute region.
118class ACCOrphanAtomicUpdateOpConversion
119 : public OpRewritePattern<acc::AtomicUpdateOp> {
120public:
121 ACCOrphanAtomicUpdateOpConversion(MLIRContext *ctx, OpenACCSupport &support)
122 : OpRewritePattern<acc::AtomicUpdateOp>(ctx), accSupport(support) {}
123
124 LogicalResult matchAndRewrite(acc::AtomicUpdateOp atomicUpdateOp,
125 PatternRewriter &rewriter) const override {
126 // Only convert if this op is not inside an ACC compute construct
127 if (isInsideACCComputeConstruct(atomicUpdateOp))
128 return failure();
129
130 Value x = atomicUpdateOp.getX();
131 Type type = x.getType();
132 auto ptrLikeType = dyn_cast<acc::PointerLikeType>(type);
133 if (ptrLikeType) {
134 auto xTyped = cast<TypedValue<acc::PointerLikeType>>(x);
135 rewriter.setInsertionPointAfter(atomicUpdateOp);
136 Value loadOp =
137 ptrLikeType.genLoad(rewriter, atomicUpdateOp.getLoc(), xTyped, {});
138 if (!loadOp) {
139 accSupport.emitNYI(atomicUpdateOp.getLoc(),
140 "failed to generate load for atomic update");
141 return failure();
142 }
143 IRMapping mapping;
144 mapping.map(atomicUpdateOp.getRegion().front().getArgument(0), loadOp);
145 Block &block = atomicUpdateOp.getRegion().front();
146 for (Operation &op : block.without_terminator())
147 rewriter.clone(op, mapping);
148 auto yieldOp = cast<acc::YieldOp>(block.getTerminator());
149 Value result = mapping.lookup(yieldOp.getOperand(0));
150 if (!ptrLikeType.genStore(rewriter, atomicUpdateOp.getLoc(), result,
151 xTyped)) {
152 accSupport.emitNYI(atomicUpdateOp.getLoc(),
153 "failed to generate store for atomic update");
154 return failure();
155 }
156 rewriter.eraseOp(atomicUpdateOp);
157 } else {
158 accSupport.emitNYI(atomicUpdateOp.getLoc(),
159 "unsupported type for atomic update");
160 return failure();
161 }
162 return success();
163 }
164
165private:
166 OpenACCSupport &accSupport;
167};
168
169// Lower orphan acc.atomic.read by: load from src, then store into dst.
170// Only matches if NOT inside an ACC compute construct.
171class ACCOrphanAtomicReadOpConversion
172 : public OpRewritePattern<acc::AtomicReadOp> {
173public:
174 ACCOrphanAtomicReadOpConversion(MLIRContext *ctx, OpenACCSupport &support)
175 : OpRewritePattern<acc::AtomicReadOp>(ctx), accSupport(support) {}
176
177 LogicalResult matchAndRewrite(acc::AtomicReadOp readOp,
178 PatternRewriter &rewriter) const override {
179 // Only convert if this op is not inside an ACC compute construct
180 if (isInsideACCComputeConstruct(readOp))
181 return failure();
182
183 Value x = readOp.getX();
184 Value v = readOp.getV();
185 auto xPtrType = dyn_cast<acc::PointerLikeType>(x.getType());
186 auto vPtrType = dyn_cast<acc::PointerLikeType>(v.getType());
187 if (xPtrType && vPtrType) {
188 auto xTyped = cast<TypedValue<acc::PointerLikeType>>(x);
189 auto vTyped = cast<TypedValue<acc::PointerLikeType>>(v);
190 rewriter.setInsertionPointAfter(readOp);
191
192 // Use genCopy which does load + store
193 if (!xPtrType.genCopy(rewriter, readOp.getLoc(), vTyped, xTyped, {})) {
194 accSupport.emitNYI(readOp.getLoc(),
195 "failed to generate copy for atomic read");
196 return failure();
197 }
198 rewriter.eraseOp(readOp);
199 } else {
200 accSupport.emitNYI(readOp.getLoc(), "unsupported type for atomic read");
201 return failure();
202 }
203 return success();
204 }
205
206private:
207 OpenACCSupport &accSupport;
208};
209
210// Lower orphan acc.atomic.write by: store value into addr.
211// Only matches if NOT inside an ACC compute construct.
212class ACCOrphanAtomicWriteOpConversion
213 : public OpRewritePattern<acc::AtomicWriteOp> {
214public:
215 ACCOrphanAtomicWriteOpConversion(MLIRContext *ctx, OpenACCSupport &support)
216 : OpRewritePattern<acc::AtomicWriteOp>(ctx), accSupport(support) {}
217
218 LogicalResult matchAndRewrite(acc::AtomicWriteOp writeOp,
219 PatternRewriter &rewriter) const override {
220 // Only convert if this op is not inside an ACC compute construct
221 if (isInsideACCComputeConstruct(writeOp))
222 return failure();
223
224 Value x = writeOp.getX();
225 Value expr = writeOp.getExpr();
226 auto ptrLikeType = dyn_cast<acc::PointerLikeType>(x.getType());
227 if (ptrLikeType) {
228 auto xTyped = cast<TypedValue<acc::PointerLikeType>>(x);
229 rewriter.setInsertionPointAfter(writeOp);
230 if (!ptrLikeType.genStore(rewriter, writeOp.getLoc(), expr, xTyped)) {
231 accSupport.emitNYI(writeOp.getLoc(),
232 "failed to generate store for atomic write");
233 return failure();
234 }
235 rewriter.eraseOp(writeOp);
236 } else {
237 accSupport.emitNYI(writeOp.getLoc(), "unsupported type for atomic write");
238 return failure();
239 }
240 return success();
241 }
242
243private:
244 OpenACCSupport &accSupport;
245};
246
247// Lower orphan acc.atomic.capture by: unwrap the capture region and erase the
248// wrapper; inner ops are lowered in-order (e.g., read+update becomes load/store
249// to dst then load/compute/store to addr).
250// Only matches if NOT inside an ACC compute construct.
251class ACCOrphanAtomicCaptureOpConversion
252 : public OpRewritePattern<acc::AtomicCaptureOp> {
253 using OpRewritePattern<acc::AtomicCaptureOp>::OpRewritePattern;
254
255 LogicalResult matchAndRewrite(acc::AtomicCaptureOp captureOp,
256 PatternRewriter &rewriter) const override {
257 // Only convert if this op is not inside an ACC compute construct
258 if (isInsideACCComputeConstruct(captureOp))
259 return failure();
260
261 assert(captureOp.getRegion().hasOneBlock() && "expected one block");
262 Block *block = &captureOp.getRegion().front();
263 // Remove the terminator before inlining
264 rewriter.eraseOp(block->getTerminator());
265 rewriter.inlineBlockBefore(block, captureOp);
266 rewriter.eraseOp(captureOp);
267 return success();
268 }
269};
270
271// Convert orphan acc.loop to scf.for or scf.execute_region.
272// Only matches if NOT inside an ACC compute construct.
273class ACCOrphanLoopOpConversion : public OpRewritePattern<acc::LoopOp> {
274 using OpRewritePattern<acc::LoopOp>::OpRewritePattern;
275
276 LogicalResult matchAndRewrite(acc::LoopOp loopOp,
277 PatternRewriter &rewriter) const override {
278 // Only convert if this op is not inside an ACC compute construct
279 if (isInsideACCComputeConstruct(loopOp))
280 return failure();
281
282 if (loopOp.getUnstructured()) {
283 auto executeRegion =
285 if (!executeRegion)
286 return failure();
287 rewriter.replaceOp(loopOp, executeRegion);
288 } else {
289 auto forOp = acc::convertACCLoopToSCFFor(loopOp, rewriter,
290 /*enableCollapse=*/false);
291 if (!forOp)
292 return failure();
293 rewriter.replaceOp(loopOp, forOp);
294 }
295 return success();
296 }
297};
298
299/// Check if an operation is used by a compute construct or loop op
300static bool isUsedByComputeOrLoop(Operation *op) {
301 for (auto *user : op->getUsers())
302 if (isa<acc::ParallelOp, acc::SerialOp, acc::KernelsOp, acc::LoopOp>(user))
303 return true;
304 return false;
305}
306
307/// Orphan data entry ops - only match if NOT connected to compute/loop and
308/// NOT inside a compute region. Used for acc.cache, acc.private,
309/// acc.firstprivate, acc.reduction.
310template <typename OpTy>
311class ACCOrphanDataEntryConversion : public OpRewritePattern<OpTy> {
312 using OpRewritePattern<OpTy>::OpRewritePattern;
313
314 LogicalResult matchAndRewrite(OpTy op,
315 PatternRewriter &rewriter) const override {
316 // Only convert if this op is not used by a compute construct or loop,
317 // and not inside an ACC compute construct.
318 if (isUsedByComputeOrLoop(op) || isInsideACCComputeConstruct(op))
319 return failure();
320
321 if (op->use_empty())
322 rewriter.eraseOp(op);
323 else
324 rewriter.replaceOp(op, op.getVar());
325 return success();
326 }
327};
328
329class ACCSpecializeForHost
330 : public acc::impl::ACCSpecializeForHostBase<ACCSpecializeForHost> {
331public:
332 using ACCSpecializeForHostBase<
333 ACCSpecializeForHost>::ACCSpecializeForHostBase;
334
335 void runOnOperation() override {
336 LLVM_DEBUG(llvm::dbgs() << "Enter ACCSpecializeForHost()\n");
337
338 func::FuncOp funcOp = getOperation();
339 if (!acc::isSpecializedAccRoutine(funcOp)) {
340 // Convert orphan operations to host, or all ACC operations if
341 // host fallback patterns are enabled.
342 auto *context = &getContext();
343 RewritePatternSet patterns(context);
344 OpenACCSupport &accSupport = getAnalysis<OpenACCSupport>();
345 if (enableHostFallback)
346 populateACCHostFallbackPatterns(patterns, accSupport);
347 else
348 populateACCOrphanToHostPatterns(patterns, accSupport);
349 GreedyRewriteConfig config;
350 config.setUseTopDownTraversal(true);
351 // Deeply nested orphan acc.loops can need more than the default
352 // iteration cap to converge; lift it to avoid spurious pass failure.
354 if (failed(applyPatternsGreedily(funcOp, std::move(patterns), config)))
355 signalPassFailure();
356 }
357
358 LLVM_DEBUG(llvm::dbgs() << "Exit ACCSpecializeForHost()\n");
359 }
360};
361} // namespace
362
363//===----------------------------------------------------------------------===//
364// Pattern population functions
365//===----------------------------------------------------------------------===//
366
368 OpenACCSupport &accSupport,
369 bool enableLoopConversion) {
370 MLIRContext *context = patterns.getContext();
371
372 // For host routines (acc routine marked for host), we only convert orphan
373 // operations that are not allowed outside compute regions. All patterns
374 // here check that the operation is NOT inside a compute region before
375 // converting:
376 // - acc.atomic.* -> load/store operations
377 // - acc.loop -> scf.for or scf.execute_region
378 // - acc.cache -> replaced with var
379 // - acc.private, acc.reduction, acc.firstprivate -> replaced with var
380 // (only if NOT connected to compute constructs or loop)
381 //
382 // We do NOT remove structured/unstructured data constructs, compute
383 // constructs, or their associated data operations - those are valid
384 // in host routines and will be processed by other passes.
385
386 // Loop conversion (orphan only)
387 if (enableLoopConversion)
388 patterns.insert<ACCOrphanLoopOpConversion>(context);
389
390 // Atomic operations - convert to non-atomic load/store (orphan only)
391 patterns.insert<ACCOrphanAtomicUpdateOpConversion>(context, accSupport);
392 patterns.insert<ACCOrphanAtomicReadOpConversion>(context, accSupport);
393 patterns.insert<ACCOrphanAtomicWriteOpConversion>(context, accSupport);
394 patterns.insert<ACCOrphanAtomicCaptureOpConversion>(context);
395
396 // Orphan data entry ops - only convert if NOT connected to compute/loop
397 // and NOT inside a compute region
398 patterns.insert<ACCOrphanDataEntryConversion<acc::CacheOp>,
399 ACCOrphanDataEntryConversion<acc::PrivateOp>,
400 ACCOrphanDataEntryConversion<acc::FirstprivateOp>,
401 ACCOrphanDataEntryConversion<acc::ReductionOp>>(context);
402}
403
405 OpenACCSupport &accSupport,
406 bool enableLoopConversion) {
407 MLIRContext *context = patterns.getContext();
408
409 // For host fallback path (when `if` clause evaluates to false), ALL ACC
410 // operations within the region should be converted to host equivalents.
411 // This includes structured/unstructured data, compute constructs, and
412 // their associated data operations.
413
414 // Loop conversion - OK to use the orphan loop conversion pattern here
415 // because the parent compute constructs will also be converted.
416 if (enableLoopConversion)
417 patterns.insert<ACCOrphanLoopOpConversion>(context);
418
419 // Atomic operations - convert to non-atomic load/store. OK to use the orphan
420 // atomic conversion patterns here because the parent compute constructs will
421 // also be converted.
422 patterns.insert<ACCOrphanAtomicUpdateOpConversion>(context, accSupport);
423 patterns.insert<ACCOrphanAtomicReadOpConversion>(context, accSupport);
424 patterns.insert<ACCOrphanAtomicWriteOpConversion>(context, accSupport);
425 patterns.insert<ACCOrphanAtomicCaptureOpConversion>(context);
426
427 // acc.cache - convert ALL cache ops (including those inside compute regions)
429
430 // Privatization ops - convert ALL (including those attached to compute/loop)
434
435 // Data entry ops - replaced with their var operand
447
448 // Data exit ops - simply erased (no results)
453
454 // Structured data constructs - unwrap their regions
458
459 // Declare ops
462
463 // Unstructured data operations - erase them
467
468 // Runtime operations - erase them
469 patterns.insert<
472 context);
473
474 // Compute constructs - unwrap their regions
478}
static bool isInsideACCComputeConstruct(Operation *op)
Check if an operation is inside an ACC compute construct.
return success()
b getContext())
Operation & front()
Definition Block.h:177
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
Definition Block.h:236
static constexpr int64_t kNoLimit
GreedyRewriteConfig & setMaxIterations(int64_t iterations)
GreedyRewriteConfig & setUseTopDownTraversal(bool use=true)
auto lookup(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:72
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
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 setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:414
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
user_range getUsers()
Returns a range of all users.
Definition Operation.h:898
RewritePatternSet & insert(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
MLIRContext * getContext() const
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.
virtual void inlineBlockBefore(Block *source, Block *dest, Block::iterator before, ValueRange argValues={})
Inline the operations of block 'source' into block 'dest' before the given position.
Type getType() const
Return the type of this value.
Definition Value.h:105
Pattern to erase acc.declare_enter and its associated acc.declare_exit.
Pattern to simply erase an ACC op (for ops with no results).
Pattern to replace an ACC op with its var operand.
Pattern to unwrap a region from an ACC op and erase the wrapper.
bool isSpecializedAccRoutine(mlir::Operation *op)
Used to check whether this is a specialized accelerator version of acc routine function.
Definition OpenACC.h:201
scf::ExecuteRegionOp convertUnstructuredACCLoopToSCFExecuteRegion(LoopOp loopOp, RewriterBase &rewriter)
Convert an unstructured acc.loop to scf.execute_region.
void populateACCOrphanToHostPatterns(RewritePatternSet &patterns, OpenACCSupport &accSupport, bool enableLoopConversion=true)
Populates patterns for converting orphan ACC operations to host.
void populateACCHostFallbackPatterns(RewritePatternSet &patterns, OpenACCSupport &accSupport, bool enableLoopConversion=true)
Populates all patterns for host fallback path (when if clause evaluates to false).
scf::ForOp convertACCLoopToSCFFor(LoopOp loopOp, RewriterBase &rewriter, bool enableCollapse)
Convert a structured acc.loop to scf.for.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
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...
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...