MLIR 24.0.0git
ACCLoopTiling.cpp
Go to the documentation of this file.
1//===- ACCLoopTiling.cpp - Tile ACC Loops ---------------------------------===//
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 implements the OpenACC loop tiling transformation for acc.loop
10// operations that have the tile clause (OpenACC 3.4 spec, section 2.9.8).
11//
12// Overview:
13// ---------
14// The tile clause specifies that the iterations of the associated loops should
15// be divided into tiles (rectangular blocks). This pass transforms a single
16// or nested acc.loop with tile clauses into a structure of "tile loops"
17// (iterating over tiles) containing "element loops" (iterating within tiles).
18//
19// For example, tiling a 2-level nested loop with tile(T1, T2) produces:
20//
21// // Before tiling:
22// acc.loop tile(T1, T2) control(%i, %j) = (lb1, lb2) to (ub1, ub2) step (s1,
23// s2)
24//
25// // After tiling:
26// acc.loop control(%i) = (lb1) to (ub1) step (s1*T1) { // tile loop 1
27// acc.loop control(%j) = (lb2) to (ub2) step (s2*T2) { // tile loop 2
28// acc.loop control(%ii) = (%i) to (min(ub1, %i+s1*T1)) step (s1) { //
29// element 1
30// acc.loop control(%jj) = (%j) to (min(ub2, %j+s2*T2)) step (s2) { //
31// element 2
32// // loop body using %ii, %jj
33// }
34// }
35// }
36// }
37//
38// The example element loop bounds above assume ascending steps. For an
39// acc.loop with negative steps, a descending dimension would use
40// max(ub, %i+s*T) instead.
41//
42// Gang/worker/vector attributes are distributed as follows:
43// - gang: applied to tile loops
44// - vector: applied to element loops
45// - worker: removed from inner loops
46//
47// Unknown Tile Sizes:
48// -------------------
49// The OpenACC tile(*) syntax indicates an implementation-defined tile size.
50// In the IR, this is represented as -1. The pass resolves these to the
51// default tile size (configurable via pass option).
52//
53// Requirements:
54// -------------
55// 1. The pass uses the OpenACCSupport analysis for remark and NYI (not yet
56// implemented) emission. Custom implementations can be registered via
57// setImplementation() to provide pipeline-specific handling.
58//
59//===----------------------------------------------------------------------===//
60
69#include "mlir/Support/LLVM.h"
71#include "llvm/ADT/StringExtras.h"
72#include "llvm/Support/Debug.h"
73
74namespace mlir {
75namespace acc {
76#define GEN_PASS_DEF_ACCLOOPTILING
77#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
78} // namespace acc
79} // namespace mlir
80
81#define DEBUG_TYPE "acc-loop-tile"
82
83namespace {
84using namespace mlir;
85
86struct ACCLoopTilingImpl : public OpRewritePattern<acc::LoopOp> {
87 ACCLoopTilingImpl(MLIRContext *context, int32_t defaultTileSize,
88 acc::OpenACCSupport &accSupport)
90 defaultTileSize(defaultTileSize), accSupport(accSupport) {}
91
92 // Check that tile size types are not narrower than IV types.
93 // We only check when both types are IntegerType. For IndexType, the width
94 // is target-dependent and the casting utility will handle it correctly.
95 LogicalResult checkTileSizeTypes(acc::LoopOp loop,
96 ArrayRef<Value> tileSizes) const {
97 auto ivTypes = loop.getBody().getArgumentTypes();
98 for (size_t i = 0; i < tileSizes.size() && i < ivTypes.size(); ++i) {
99 Type tileType = tileSizes[i].getType();
100 Type ivType = ivTypes[i];
101
102 // Skip unknown tile sizes (will be created with correct type)
103 auto constVal = getConstantIntValue(tileSizes[i]);
104 if (constVal && *constVal < 0)
105 continue;
106
107 // Only compare when both are integer types (not index)
108 auto tileIntType = dyn_cast<IntegerType>(tileType);
109 auto ivIntType = dyn_cast<IntegerType>(ivType);
110 if (tileIntType && ivIntType) {
111 if (tileIntType.getWidth() > ivIntType.getWidth()) {
112 accSupport.emitNYI(loop.getLoc(),
113 "tile size type (i" +
114 std::to_string(tileIntType.getWidth()) +
115 ") is wider than loop IV type (i" +
116 std::to_string(ivIntType.getWidth()) + ")");
117 return failure();
118 }
119 }
120 }
121 return success();
122 }
123
124 void emitTilingRemarks(acc::LoopOp loop, ArrayRef<Value> tileSizes) const {
125 // Emit remarks for loop tiling
126 accSupport.emitRemark(
127 loop,
128 [&]() {
129 auto getTileSizeStr = [&](Value v) -> std::string {
130 std::string name = accSupport.getVariableName(v);
131 // Use "*" for unknown tile sizes (represented as -1 or empty)
132 if (name.empty() || name == "-1")
133 return "*";
134 return name;
135 };
137 for (Value v : tileSizes)
138 tileStrs.push_back(getTileSizeStr(v));
139 return "Tiling " + std::to_string(tileSizes.size()) +
140 "-level loop nest with tile(" + llvm::join(tileStrs, ",") +
141 ")";
142 },
143 DEBUG_TYPE);
144
145 // Emit remarks for unknown tile sizes that will be resolved to default
146 // TODO: Need to base the default tile size on some heuristics.
147 for (Value tileSize : tileSizes) {
148 std::optional<int64_t> val = getConstantIntValue(tileSize);
149 if (val && *val < 0) {
150 accSupport.emitRemark(
151 loop,
152 [&]() {
153 return "Picking default tile size " +
154 std::to_string(defaultTileSize) +
155 " for unknown tile size '*'";
156 },
157 DEBUG_TYPE);
158 }
159 }
160 }
161
162 LogicalResult matchAndRewrite(acc::LoopOp origLoop,
163 PatternRewriter &rewriter) const override {
164
165 if (origLoop.getTileValues().empty())
166 return failure();
167
168 SmallVector<Value> tileSizes(origLoop.getTileValues().begin(),
169 origLoop.getTileValues().end());
170
171 // A tile clause and a collapse clause on the same loop are not supported:
172 // they give conflicting descriptions of how many loops the construct
173 // associates.
174 if (origLoop.getCollapseAttr()) {
175 accSupport.emitNYI(origLoop.getLoc(),
176 "a tile clause combined with a collapse clause on the "
177 "same loop");
178 return failure();
179 }
180
181 // Sanity check tile size types
182 if (failed(checkTileSizeTypes(origLoop, tileSizes)))
183 return failure();
184
185 // Emit remarks for loop tiling. This is emitted before the original loop
186 // is modified. However, it assumes that tiling will not fail.
187 emitTilingRemarks(origLoop, tileSizes);
188
189 LLVM_DEBUG(llvm::dbgs() << "\nBefore tiling:\n" << *origLoop << "\n");
190
191 // Clear tile operands from origLoop
192 rewriter.startOpModification(origLoop);
193 origLoop.getTileOperandsMutable().clear();
194 origLoop.removeTileOperandsSegmentsAttr();
195 origLoop.removeTileOperandsDeviceTypeAttr();
196 rewriter.finalizeOpModification(origLoop);
197
198 // Tile the single fused loop in place: the tile iterations become one
199 // multi-IV "tile group" loop wrapping one multi-IV "element group" loop.
200 // The utility handles unknown tile sizes (*) by using `defaultTileSize`.
201 acc::tileACCLoops(origLoop, tileSizes, defaultTileSize, rewriter);
202
203 LLVM_DEBUG(llvm::dbgs() << "\nAfter tiling:\n " << *origLoop << "\n");
204 return success();
205 }
206
207private:
208 int32_t defaultTileSize;
209 acc::OpenACCSupport &accSupport;
210};
211
212class ACCLoopTiling : public acc::impl::ACCLoopTilingBase<ACCLoopTiling> {
213public:
214 using ACCLoopTilingBase<ACCLoopTiling>::ACCLoopTilingBase;
215
216 void getDependentDialects(DialectRegistry &registry) const override {
217 ACCLoopTilingBase<ACCLoopTiling>::getDependentDialects(registry);
219 }
220
221 void runOnOperation() override {
222 func::FuncOp funcOp = getOperation();
223 MLIRContext *context = funcOp.getContext();
224 acc::OpenACCSupport &accSupport = getAnalysis<acc::OpenACCSupport>();
225
226 RewritePatternSet patterns(context);
227 patterns.insert<ACCLoopTilingImpl>(context, defaultTileSize, accSupport);
229 grc.setUseTopDownTraversal(true);
230 grc.setMaxIterations(1);
231 (void)applyPatternsGreedily(funcOp, std::move(patterns), grc);
232 }
233};
234} // namespace
return success()
#define DEBUG_TYPE
The DialectRegistry maps a dialect namespace to a constructor for the matching dialect.
This class allows control over how the GreedyPatternRewriteDriver works.
GreedyRewriteConfig & setMaxIterations(int64_t iterations)
GreedyRewriteConfig & setUseTopDownTraversal(bool use=true)
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:64
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
RewritePatternSet & insert(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
virtual void finalizeOpModification(Operation *op)
This method is used to signal the end of an in-place modification of the given operation.
virtual void startOpModification(Operation *op)
This method is used to notify the rewriter that an in-place operation modification is about to happen...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
remark::detail::InFlightRemark emitRemark(Operation *op, std::function< std::string()> messageFn, llvm::StringRef category="openacc")
Emit an OpenACC remark with lazy message generation.
std::string getVariableName(Value v, VariableNameConfig config={})
Get the variable name for a given value.
InFlightDiagnostic emitNYI(Location loc, const Twine &message)
Report a case that is not yet supported by the implementation.
mlir::acc::LoopOp tileACCLoops(mlir::acc::LoopOp tileLoop, const llvm::SmallVector< mlir::Value > &tileSizes, int32_t defaultTileSize, mlir::RewriterBase &rewriter)
Tile a single fused acc.loop that carries all associated induction variables (one IV per tile dimensi...
void registerAllExtensions(DialectRegistry &registry)
Register the standard OpenACC models for GPU, LLVM and MemRef IR.
Include the generated interface declarations.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
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...