Skip to content

Commit ffa970b

Browse files
bondhugulamylai-mtk
authored andcommitted
[MLIR][Affine] Add test pass for affine isContiguousAccess (llvm#82923)
`isContiguousAccess` is an important affine analysis utility but is only tested very indirectly via passes like vectorization and is not exposed. Expose it and add a test pass for it that'll make it easier/feasible to write test cases. This is especially needed since the utility can be significantly enhanced in power, and we need a test pass to exercise it directly. This pass can in the future be used to test the utility of invariant accesses as well.
1 parent 4589e09 commit ffa970b

File tree

6 files changed

+197
-31
lines changed

6 files changed

+197
-31
lines changed

mlir/include/mlir/Dialect/Affine/Analysis/LoopAnalysis.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,26 @@ uint64_t getLargestDivisorOfTripCount(AffineForOp forOp);
6060
DenseSet<Value, DenseMapInfo<Value>>
6161
getInvariantAccesses(Value iv, ArrayRef<Value> indices);
6262

63+
/// Given:
64+
/// 1. an induction variable `iv` of type AffineForOp;
65+
/// 2. a `memoryOp` of type const LoadOp& or const StoreOp&;
66+
/// determines whether `memoryOp` has a contiguous access along `iv`. Contiguous
67+
/// is defined as either invariant or varying only along a unique MemRef dim.
68+
/// Upon success, the unique MemRef dim is written in `memRefDim` (or -1 to
69+
/// convey the memRef access is invariant along `iv`).
70+
///
71+
/// Prerequisites:
72+
/// 1. `memRefDim` ~= nullptr;
73+
/// 2. `iv` of the proper type;
74+
/// 3. the MemRef accessed by `memoryOp` has no layout map or at most an
75+
/// identity layout map.
76+
///
77+
/// Currently only supports no layout map or identity layout map in the memref.
78+
/// Returns false if the memref has a non-identity layoutMap. This behavior is
79+
/// conservative.
80+
template <typename LoadOrStoreOp>
81+
bool isContiguousAccess(Value iv, LoadOrStoreOp memoryOp, int *memRefDim);
82+
6383
using VectorizableLoopFun = std::function<bool(AffineForOp)>;
6484

6585
/// Checks whether the loop is structurally vectorizable; i.e.:

mlir/lib/Dialect/Affine/Analysis/LoopAnalysis.cpp

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -195,43 +195,25 @@ DenseSet<Value> mlir::affine::getInvariantAccesses(Value iv,
195195
return res;
196196
}
197197

198-
/// Given:
199-
/// 1. an induction variable `iv` of type AffineForOp;
200-
/// 2. a `memoryOp` of type const LoadOp& or const StoreOp&;
201-
/// determines whether `memoryOp` has a contiguous access along `iv`. Contiguous
202-
/// is defined as either invariant or varying only along a unique MemRef dim.
203-
/// Upon success, the unique MemRef dim is written in `memRefDim` (or -1 to
204-
/// convey the memRef access is invariant along `iv`).
205-
///
206-
/// Prerequisites:
207-
/// 1. `memRefDim` ~= nullptr;
208-
/// 2. `iv` of the proper type;
209-
/// 3. the MemRef accessed by `memoryOp` has no layout map or at most an
210-
/// identity layout map.
211-
///
212-
/// Currently only supports no layoutMap or identity layoutMap in the MemRef.
213-
/// Returns false if the MemRef has a non-identity layoutMap or more than 1
214-
/// layoutMap. This is conservative.
215-
///
216-
// TODO: check strides.
198+
// TODO: check access stride.
217199
template <typename LoadOrStoreOp>
218-
static bool isContiguousAccess(Value iv, LoadOrStoreOp memoryOp,
219-
int *memRefDim) {
220-
static_assert(
221-
llvm::is_one_of<LoadOrStoreOp, AffineLoadOp, AffineStoreOp>::value,
222-
"Must be called on either LoadOp or StoreOp");
200+
bool mlir::affine::isContiguousAccess(Value iv, LoadOrStoreOp memoryOp,
201+
int *memRefDim) {
202+
static_assert(llvm::is_one_of<LoadOrStoreOp, AffineReadOpInterface,
203+
AffineWriteOpInterface>::value,
204+
"Must be called on either an affine read or write op");
223205
assert(memRefDim && "memRefDim == nullptr");
224206
auto memRefType = memoryOp.getMemRefType();
225207

226208
if (!memRefType.getLayout().isIdentity())
227-
return memoryOp.emitError("NYI: non-trivial layoutMap"), false;
209+
return memoryOp.emitError("NYI: non-trivial layout map"), false;
228210

229211
int uniqueVaryingIndexAlongIv = -1;
230212
auto accessMap = memoryOp.getAffineMap();
231213
SmallVector<Value, 4> mapOperands(memoryOp.getMapOperands());
232214
unsigned numDims = accessMap.getNumDims();
233215
for (unsigned i = 0, e = memRefType.getRank(); i < e; ++i) {
234-
// Gather map operands used result expr 'i' in 'exprOperands'.
216+
// Gather map operands used in result expr 'i' in 'exprOperands'.
235217
SmallVector<Value, 4> exprOperands;
236218
auto resultExpr = accessMap.getResult(i);
237219
resultExpr.walk([&](AffineExpr expr) {
@@ -241,7 +223,7 @@ static bool isContiguousAccess(Value iv, LoadOrStoreOp memoryOp,
241223
exprOperands.push_back(mapOperands[numDims + symExpr.getPosition()]);
242224
});
243225
// Check access invariance of each operand in 'exprOperands'.
244-
for (auto exprOperand : exprOperands) {
226+
for (Value exprOperand : exprOperands) {
245227
if (!isAccessIndexInvariant(iv, exprOperand)) {
246228
if (uniqueVaryingIndexAlongIv != -1) {
247229
// 2+ varying indices -> do not vectorize along iv.
@@ -259,6 +241,13 @@ static bool isContiguousAccess(Value iv, LoadOrStoreOp memoryOp,
259241
return true;
260242
}
261243

244+
template bool mlir::affine::isContiguousAccess(Value iv,
245+
AffineReadOpInterface loadOp,
246+
int *memRefDim);
247+
template bool mlir::affine::isContiguousAccess(Value iv,
248+
AffineWriteOpInterface loadOp,
249+
int *memRefDim);
250+
262251
template <typename LoadOrStoreOp>
263252
static bool isVectorElement(LoadOrStoreOp memoryOp) {
264253
auto memRefType = memoryOp.getMemRefType();
@@ -344,10 +333,13 @@ bool mlir::affine::isVectorizableLoopBody(
344333
auto load = dyn_cast<AffineLoadOp>(op);
345334
auto store = dyn_cast<AffineStoreOp>(op);
346335
int thisOpMemRefDim = -1;
347-
bool isContiguous = load ? isContiguousAccess(loop.getInductionVar(), load,
348-
&thisOpMemRefDim)
349-
: isContiguousAccess(loop.getInductionVar(), store,
350-
&thisOpMemRefDim);
336+
bool isContiguous =
337+
load ? isContiguousAccess(loop.getInductionVar(),
338+
cast<AffineReadOpInterface>(*load),
339+
&thisOpMemRefDim)
340+
: isContiguousAccess(loop.getInductionVar(),
341+
cast<AffineWriteOpInterface>(*store),
342+
&thisOpMemRefDim);
351343
if (thisOpMemRefDim != -1) {
352344
// If memory accesses vary across different dimensions then the loop is
353345
// not vectorizable.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// RUN: mlir-opt %s -split-input-file -test-affine-access-analysis -verify-diagnostics | FileCheck %s
2+
3+
// CHECK-LABEL: func @loop_1d
4+
func.func @loop_1d(%A : memref<?x?xf32>, %B : memref<?x?x?xf32>) {
5+
%c0 = arith.constant 0 : index
6+
%M = memref.dim %A, %c0 : memref<?x?xf32>
7+
affine.for %i = 0 to %M {
8+
affine.for %j = 0 to %M {
9+
affine.load %A[%c0, %i] : memref<?x?xf32>
10+
// expected-remark@above {{contiguous along loop 0}}
11+
affine.load %A[%c0, 8 * %i + %j] : memref<?x?xf32>
12+
// expected-remark@above {{contiguous along loop 1}}
13+
// Note/FIXME: access stride isn't being checked.
14+
// expected-remark@-3 {{contiguous along loop 0}}
15+
16+
// These are all non-contiguous along both loops. Nothing is emitted.
17+
affine.load %A[%i, %c0] : memref<?x?xf32>
18+
// Note/FIXME: access stride isn't being checked.
19+
affine.load %A[%i, 8 * %j] : memref<?x?xf32>
20+
// expected-remark@above {{contiguous along loop 1}}
21+
affine.load %A[%j, 4 * %i] : memref<?x?xf32>
22+
// expected-remark@above {{contiguous along loop 0}}
23+
}
24+
}
25+
return
26+
}
27+
28+
// -----
29+
30+
#map = affine_map<(d0) -> (d0 * 16)>
31+
#map1 = affine_map<(d0) -> (d0 * 16 + 16)>
32+
#map2 = affine_map<(d0) -> (d0)>
33+
#map3 = affine_map<(d0) -> (d0 + 1)>
34+
35+
func.func @tiled(%arg0: memref<*xf32>) {
36+
%alloc = memref.alloc() {alignment = 64 : i64} : memref<1x224x224x64xf32>
37+
%cast = memref.cast %arg0 : memref<*xf32> to memref<64xf32>
38+
affine.for %arg1 = 0 to 4 {
39+
affine.for %arg2 = 0 to 224 {
40+
affine.for %arg3 = 0 to 14 {
41+
%alloc_0 = memref.alloc() : memref<1x16x1x16xf32>
42+
affine.for %arg4 = #map(%arg1) to #map1(%arg1) {
43+
affine.for %arg5 = #map(%arg3) to #map1(%arg3) {
44+
%0 = affine.load %cast[%arg4] : memref<64xf32>
45+
// expected-remark@above {{contiguous along loop 3}}
46+
affine.store %0, %alloc_0[0, %arg1 * -16 + %arg4, 0, %arg3 * -16 + %arg5] : memref<1x16x1x16xf32>
47+
// expected-remark@above {{contiguous along loop 4}}
48+
// expected-remark@above {{contiguous along loop 2}}
49+
}
50+
}
51+
affine.for %arg4 = #map(%arg1) to #map1(%arg1) {
52+
affine.for %arg5 = #map2(%arg2) to #map3(%arg2) {
53+
affine.for %arg6 = #map(%arg3) to #map1(%arg3) {
54+
%0 = affine.load %alloc_0[0, %arg1 * -16 + %arg4, -%arg2 + %arg5, %arg3 * -16 + %arg6] : memref<1x16x1x16xf32>
55+
// expected-remark@above {{contiguous along loop 5}}
56+
// expected-remark@above {{contiguous along loop 2}}
57+
affine.store %0, %alloc[0, %arg5, %arg6, %arg4] : memref<1x224x224x64xf32>
58+
// expected-remark@above {{contiguous along loop 3}}
59+
}
60+
}
61+
}
62+
memref.dealloc %alloc_0 : memref<1x16x1x16xf32>
63+
}
64+
}
65+
}
66+
return
67+
}

mlir/test/lib/Dialect/Affine/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ add_mlir_library(MLIRAffineTransformsTestPasses
33
TestAffineDataCopy.cpp
44
TestAffineLoopUnswitching.cpp
55
TestAffineLoopParametricTiling.cpp
6+
TestAccessAnalysis.cpp
67
TestDecomposeAffineOps.cpp
78
TestReifyValueBounds.cpp
89
TestLoopFusion.cpp
@@ -21,6 +22,7 @@ add_mlir_library(MLIRAffineTransformsTestPasses
2122

2223
LINK_LIBS PUBLIC
2324
MLIRArithTransforms
25+
MLIRAffineAnalysis
2426
MLIRAffineTransforms
2527
MLIRAffineUtils
2628
MLIRIR
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//===- TestAccessAnalysis.cpp - Test affine access analysis utility -------===//
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 file implements a pass to test affine access analysis utilities.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
#include "mlir/Dialect/Affine/Analysis/LoopAnalysis.h"
13+
#include "mlir/Dialect/Affine/Analysis/Utils.h"
14+
#include "mlir/Dialect/Affine/LoopFusionUtils.h"
15+
#include "mlir/Dialect/Func/IR/FuncOps.h"
16+
#include "mlir/Pass/Pass.h"
17+
18+
#define PASS_NAME "test-affine-access-analysis"
19+
20+
using namespace mlir;
21+
using namespace mlir::affine;
22+
23+
namespace {
24+
25+
struct TestAccessAnalysis
26+
: public PassWrapper<TestAccessAnalysis, OperationPass<func::FuncOp>> {
27+
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestAccessAnalysis)
28+
29+
StringRef getArgument() const final { return PASS_NAME; }
30+
StringRef getDescription() const final {
31+
return "Tests affine memory access analysis utility";
32+
}
33+
34+
void runOnOperation() override;
35+
};
36+
37+
} // namespace
38+
39+
/// Gathers all affine load/store ops in loop nest rooted at 'forOp' into
40+
/// 'loadAndStoreOps'.
41+
static void
42+
gatherLoadsAndStores(AffineForOp forOp,
43+
SmallVectorImpl<Operation *> &loadAndStoreOps) {
44+
forOp.walk([&](Operation *op) {
45+
if (isa<AffineReadOpInterface, AffineWriteOpInterface>(op))
46+
loadAndStoreOps.push_back(op);
47+
});
48+
}
49+
50+
void TestAccessAnalysis::runOnOperation() {
51+
SmallVector<Operation *> loadStores;
52+
SmallVector<AffineForOp> enclosingOps;
53+
// Go over all top-level affine.for ops and test each contained affine
54+
// access's contiguity along every surrounding loop IV.
55+
for (auto forOp : getOperation().getOps<AffineForOp>()) {
56+
loadStores.clear();
57+
gatherLoadsAndStores(forOp, loadStores);
58+
for (Operation *memOp : loadStores) {
59+
enclosingOps.clear();
60+
getAffineForIVs(*memOp, &enclosingOps);
61+
for (unsigned d = 0, e = enclosingOps.size(); d < e; d++) {
62+
int memRefDim;
63+
bool isContiguous;
64+
if (auto read = dyn_cast<AffineReadOpInterface>(memOp)) {
65+
isContiguous = isContiguousAccess(enclosingOps[d].getInductionVar(),
66+
read, &memRefDim);
67+
} else {
68+
isContiguous = isContiguousAccess(enclosingOps[d].getInductionVar(),
69+
cast<AffineWriteOpInterface>(memOp),
70+
&memRefDim);
71+
}
72+
if (isContiguous && memRefDim == 0)
73+
memOp->emitRemark("contiguous along loop ") << d << '\n';
74+
}
75+
}
76+
}
77+
}
78+
79+
namespace mlir {
80+
void registerTestAffineAccessAnalysisPass() {
81+
PassRegistration<TestAccessAnalysis>();
82+
}
83+
} // namespace mlir

mlir/tools/mlir-opt/mlir-opt.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ void registerSliceAnalysisTestPass();
4343
void registerSymbolTestPasses();
4444
void registerRegionTestPasses();
4545
void registerTestAffineDataCopyPass();
46+
void registerTestAffineAccessAnalysisPass();
4647
void registerTestAffineReifyValueBoundsPass();
4748
void registerTestAffineLoopUnswitchingPass();
4849
void registerTestAffineWalk();
@@ -169,6 +170,7 @@ void registerTestPasses() {
169170
registerSymbolTestPasses();
170171
registerRegionTestPasses();
171172
registerTestAffineDataCopyPass();
173+
registerTestAffineAccessAnalysisPass();
172174
registerTestAffineLoopUnswitchingPass();
173175
registerTestAffineReifyValueBoundsPass();
174176
registerTestAffineWalk();

0 commit comments

Comments
 (0)