MLIR 24.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
113/// Return true if an enclosing compute construct or capture must be removed
114/// before converting an atomic operation.
116 return isInsideACCComputeConstruct(op) ||
117 op->getParentOfType<acc::AtomicCaptureOp>();
118}
119
120namespace {
121
122// Lower orphan acc.atomic.update by: load from addr, clone region expr with
123// the loaded value, then store the computed result back to addr.
124// Only matches outside compute regions and atomic captures.
125class ACCOrphanAtomicUpdateOpConversion
126 : public OpRewritePattern<acc::AtomicUpdateOp> {
127public:
128 ACCOrphanAtomicUpdateOpConversion(MLIRContext *ctx, OpenACCSupport &support)
129 : OpRewritePattern<acc::AtomicUpdateOp>(ctx), accSupport(support) {}
130
131 LogicalResult matchAndRewrite(acc::AtomicUpdateOp atomicUpdateOp,
132 PatternRewriter &rewriter) const override {
133 if (isAtomicConversionDeferred(atomicUpdateOp))
134 return failure();
135
136 Value x = atomicUpdateOp.getX();
137 Type type = x.getType();
138 auto ptrLikeType = dyn_cast<acc::PointerLikeType>(type);
139 if (ptrLikeType) {
140 auto xTyped = cast<TypedValue<acc::PointerLikeType>>(x);
141 rewriter.setInsertionPointAfter(atomicUpdateOp);
142 Value loadOp =
143 ptrLikeType.genLoad(rewriter, atomicUpdateOp.getLoc(), xTyped, {});
144 if (!loadOp) {
145 accSupport.emitNYI(atomicUpdateOp.getLoc(),
146 "failed to generate load for atomic update");
147 return failure();
148 }
149 IRMapping mapping;
150 mapping.map(atomicUpdateOp.getRegion().front().getArgument(0), loadOp);
151 Block &block = atomicUpdateOp.getRegion().front();
152 for (Operation &op : block.without_terminator())
153 rewriter.clone(op, mapping);
154 auto yieldOp = cast<acc::YieldOp>(block.getTerminator());
155 Value result = mapping.lookup(yieldOp.getOperand(0));
156 if (!ptrLikeType.genStore(rewriter, atomicUpdateOp.getLoc(), result,
157 xTyped)) {
158 accSupport.emitNYI(atomicUpdateOp.getLoc(),
159 "failed to generate store for atomic update");
160 return failure();
161 }
162 rewriter.eraseOp(atomicUpdateOp);
163 } else {
164 accSupport.emitNYI(atomicUpdateOp.getLoc(),
165 "unsupported type for atomic update");
166 return failure();
167 }
168 return success();
169 }
170
171private:
172 OpenACCSupport &accSupport;
173};
174
175// Lower orphan acc.atomic.read by: load from src, then store into dst.
176// Only matches outside compute regions and atomic captures.
177class ACCOrphanAtomicReadOpConversion
178 : public OpRewritePattern<acc::AtomicReadOp> {
179public:
180 ACCOrphanAtomicReadOpConversion(MLIRContext *ctx, OpenACCSupport &support)
181 : OpRewritePattern<acc::AtomicReadOp>(ctx), accSupport(support) {}
182
183 LogicalResult matchAndRewrite(acc::AtomicReadOp readOp,
184 PatternRewriter &rewriter) const override {
185 if (isAtomicConversionDeferred(readOp))
186 return failure();
187
188 Value x = readOp.getX();
189 Value v = readOp.getV();
190 auto xPtrType = dyn_cast<acc::PointerLikeType>(x.getType());
191 auto vPtrType = dyn_cast<acc::PointerLikeType>(v.getType());
192 if (xPtrType && vPtrType) {
193 auto xTyped = cast<TypedValue<acc::PointerLikeType>>(x);
194 auto vTyped = cast<TypedValue<acc::PointerLikeType>>(v);
195 rewriter.setInsertionPointAfter(readOp);
196
197 // Use genCopy which does load + store
198 if (!xPtrType.genCopy(rewriter, readOp.getLoc(), vTyped, xTyped, {})) {
199 accSupport.emitNYI(readOp.getLoc(),
200 "failed to generate copy for atomic read");
201 return failure();
202 }
203 rewriter.eraseOp(readOp);
204 } else {
205 accSupport.emitNYI(readOp.getLoc(), "unsupported type for atomic read");
206 return failure();
207 }
208 return success();
209 }
210
211private:
212 OpenACCSupport &accSupport;
213};
214
215// Lower orphan acc.atomic.write by: store value into addr.
216// Only matches outside compute regions and atomic captures.
217class ACCOrphanAtomicWriteOpConversion
218 : public OpRewritePattern<acc::AtomicWriteOp> {
219public:
220 ACCOrphanAtomicWriteOpConversion(MLIRContext *ctx, OpenACCSupport &support)
221 : OpRewritePattern<acc::AtomicWriteOp>(ctx), accSupport(support) {}
222
223 LogicalResult matchAndRewrite(acc::AtomicWriteOp writeOp,
224 PatternRewriter &rewriter) const override {
225 if (isAtomicConversionDeferred(writeOp))
226 return failure();
227
228 Value x = writeOp.getX();
229 Value expr = writeOp.getExpr();
230 auto ptrLikeType = dyn_cast<acc::PointerLikeType>(x.getType());
231 if (ptrLikeType) {
232 auto xTyped = cast<TypedValue<acc::PointerLikeType>>(x);
233 rewriter.setInsertionPointAfter(writeOp);
234 if (!ptrLikeType.genStore(rewriter, writeOp.getLoc(), expr, xTyped)) {
235 accSupport.emitNYI(writeOp.getLoc(),
236 "failed to generate store for atomic write");
237 return failure();
238 }
239 rewriter.eraseOp(writeOp);
240 } else {
241 accSupport.emitNYI(writeOp.getLoc(), "unsupported type for atomic write");
242 return failure();
243 }
244 return success();
245 }
246
247private:
248 OpenACCSupport &accSupport;
249};
250
251// Lower orphan acc.atomic.capture by: unwrap the capture region and erase the
252// wrapper; inner ops are lowered in-order (e.g., read+update becomes load/store
253// to dst then load/compute/store to addr).
254// Only matches if NOT inside an ACC compute construct.
255class ACCOrphanAtomicCaptureOpConversion
256 : public OpRewritePattern<acc::AtomicCaptureOp> {
257 using OpRewritePattern<acc::AtomicCaptureOp>::OpRewritePattern;
258
259 LogicalResult matchAndRewrite(acc::AtomicCaptureOp captureOp,
260 PatternRewriter &rewriter) const override {
261 // Only convert if this op is not inside an ACC compute construct
262 if (isInsideACCComputeConstruct(captureOp))
263 return failure();
264
265 assert(captureOp.getRegion().hasOneBlock() && "expected one block");
266 Block *block = &captureOp.getRegion().front();
267 // Remove the terminator before inlining
268 rewriter.eraseOp(block->getTerminator());
269 rewriter.inlineBlockBefore(block, captureOp);
270 rewriter.eraseOp(captureOp);
271 return success();
272 }
273};
274
275// Convert orphan acc.loop to scf.for or scf.execute_region.
276// Only matches if NOT inside an ACC compute construct.
277class ACCOrphanLoopOpConversion : public OpRewritePattern<acc::LoopOp> {
278 using OpRewritePattern<acc::LoopOp>::OpRewritePattern;
279
280 LogicalResult matchAndRewrite(acc::LoopOp loopOp,
281 PatternRewriter &rewriter) const override {
282 // Only convert if this op is not inside an ACC compute construct
283 if (isInsideACCComputeConstruct(loopOp))
284 return failure();
285
286 if (loopOp.getUnstructured()) {
287 auto executeRegion =
289 if (!executeRegion)
290 return failure();
291 rewriter.replaceOp(loopOp, executeRegion);
292 } else {
293 auto forOp = acc::convertACCLoopToSCFFor(loopOp, rewriter,
294 /*enableCollapse=*/false);
295 if (!forOp)
296 return failure();
297 rewriter.replaceOp(loopOp, forOp);
298 }
299 return success();
300 }
301};
302
303/// Check if an operation is used by a compute construct or loop op
304static bool isUsedByComputeOrLoop(Operation *op) {
305 for (auto *user : op->getUsers())
306 if (isa<acc::ParallelOp, acc::SerialOp, acc::KernelsOp, acc::LoopOp>(user))
307 return true;
308 return false;
309}
310
311/// Orphan data entry ops - only match if NOT connected to compute/loop and
312/// NOT inside a compute region. Used for acc.cache, acc.private,
313/// acc.firstprivate, acc.reduction.
314template <typename OpTy>
315class ACCOrphanDataEntryConversion : public OpRewritePattern<OpTy> {
316 using OpRewritePattern<OpTy>::OpRewritePattern;
317
318 LogicalResult matchAndRewrite(OpTy op,
319 PatternRewriter &rewriter) const override {
320 // Only convert if this op is not used by a compute construct or loop,
321 // and not inside an ACC compute construct.
322 if (isUsedByComputeOrLoop(op) || isInsideACCComputeConstruct(op))
323 return failure();
324
325 if (op->use_empty())
326 rewriter.eraseOp(op);
327 else
328 rewriter.replaceOp(op, op.getVar());
329 return success();
330 }
331};
332
333class ACCSpecializeForHost
334 : public acc::impl::ACCSpecializeForHostBase<ACCSpecializeForHost> {
335public:
336 using ACCSpecializeForHostBase<
337 ACCSpecializeForHost>::ACCSpecializeForHostBase;
338
339 void runOnOperation() override {
340 LLVM_DEBUG(llvm::dbgs() << "Enter ACCSpecializeForHost()\n");
341
342 func::FuncOp funcOp = getOperation();
343 if (!acc::isSpecializedAccRoutine(funcOp)) {
344 // Convert orphan operations to host, or all ACC operations if
345 // host fallback patterns are enabled.
346 auto *context = &getContext();
347 RewritePatternSet patterns(context);
348 OpenACCSupport &accSupport = getAnalysis<OpenACCSupport>();
349 if (enableHostFallback)
350 populateACCHostFallbackPatterns(patterns, accSupport);
351 else
352 populateACCOrphanToHostPatterns(patterns, accSupport);
353 GreedyRewriteConfig config;
354 config.setUseTopDownTraversal(true);
355 // Deeply nested orphan acc.loops can need more than the default
356 // iteration cap to converge; lift it to avoid spurious pass failure.
358 if (failed(applyPatternsGreedily(funcOp, std::move(patterns), config)))
359 signalPassFailure();
360 }
361
362 LLVM_DEBUG(llvm::dbgs() << "Exit ACCSpecializeForHost()\n");
363 }
364};
365} // namespace
366
367//===----------------------------------------------------------------------===//
368// Pattern population functions
369//===----------------------------------------------------------------------===//
370
372 OpenACCSupport &accSupport,
373 bool enableLoopConversion) {
374 MLIRContext *context = patterns.getContext();
375
376 // For host routines (acc routine marked for host), we only convert orphan
377 // operations that are not allowed outside compute regions. All patterns
378 // here check that the operation is NOT inside a compute region before
379 // converting:
380 // - acc.atomic.* -> load/store operations
381 // - acc.loop -> scf.for or scf.execute_region
382 // - acc.cache -> replaced with var
383 // - acc.private, acc.reduction, acc.firstprivate -> replaced with var
384 // (only if NOT connected to compute constructs or loop)
385 //
386 // We do NOT remove structured/unstructured data constructs, compute
387 // constructs, or their associated data operations - those are valid
388 // in host routines and will be processed by other passes.
389
390 // Loop conversion (orphan only)
391 if (enableLoopConversion)
392 patterns.insert<ACCOrphanLoopOpConversion>(context);
393
394 // Atomic operations - convert to non-atomic load/store (orphan only)
395 patterns.insert<ACCOrphanAtomicUpdateOpConversion>(context, accSupport);
396 patterns.insert<ACCOrphanAtomicReadOpConversion>(context, accSupport);
397 patterns.insert<ACCOrphanAtomicWriteOpConversion>(context, accSupport);
398 patterns.insert<ACCOrphanAtomicCaptureOpConversion>(context);
399
400 // Orphan data entry ops - only convert if NOT connected to compute/loop
401 // and NOT inside a compute region
402 patterns.insert<ACCOrphanDataEntryConversion<acc::CacheOp>,
403 ACCOrphanDataEntryConversion<acc::PrivateOp>,
404 ACCOrphanDataEntryConversion<acc::FirstprivateOp>,
405 ACCOrphanDataEntryConversion<acc::ReductionOp>>(context);
406}
407
409 OpenACCSupport &accSupport,
410 bool enableLoopConversion) {
411 MLIRContext *context = patterns.getContext();
412
413 // For host fallback path (when `if` clause evaluates to false), ALL ACC
414 // operations within the region should be converted to host equivalents.
415 // This includes structured/unstructured data, compute constructs, and
416 // their associated data operations.
417
418 // Loop conversion - OK to use the orphan loop conversion pattern here
419 // because the parent compute constructs will also be converted.
420 if (enableLoopConversion)
421 patterns.insert<ACCOrphanLoopOpConversion>(context);
422
423 // Atomic operations - convert to non-atomic load/store. OK to use the orphan
424 // atomic conversion patterns here because the parent compute constructs will
425 // also be converted.
426 patterns.insert<ACCOrphanAtomicUpdateOpConversion>(context, accSupport);
427 patterns.insert<ACCOrphanAtomicReadOpConversion>(context, accSupport);
428 patterns.insert<ACCOrphanAtomicWriteOpConversion>(context, accSupport);
429 patterns.insert<ACCOrphanAtomicCaptureOpConversion>(context);
430
431 // acc.cache - convert ALL cache ops (including those inside compute regions)
433
434 // Privatization ops - convert ALL (including those attached to compute/loop)
438
439 // Data entry ops - replaced with their var operand
451
452 // Data exit ops - simply erased (no results)
457
458 // Structured data constructs - unwrap their regions
462
463 // Declare ops
466
467 // Unstructured data operations - erase them
471
472 // Runtime operations - erase them
473 patterns.insert<
476 context);
477
478 // Compute constructs - unwrap their regions
482}
static bool isAtomicConversionDeferred(Operation *op)
Return true if an enclosing compute construct or capture must be removed before converting an atomic ...
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:571
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
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
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:255
user_range getUsers()
Returns a range of all users.
Definition Operation.h:918
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...