Skip to content

Commit 30efb86

Browse files
author
git apple-llvm automerger
committed
Merge commit '1e9bfcd9a423' from llvm.org/main into next
2 parents e09cc3f + 1e9bfcd commit 30efb86

File tree

6 files changed

+88
-42
lines changed

6 files changed

+88
-42
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ std::optional<uint64_t> getConstantTripCount(AffineForOp forOp);
4848
/// this method is thus able to determine non-trivial divisors.
4949
uint64_t getLargestDivisorOfTripCount(AffineForOp forOp);
5050

51+
/// Checks if an affine read or write operation depends on `forOp`'s IV, i.e.,
52+
/// if the memory access is invariant on `forOp`.
53+
template <typename LoadOrStoreOp>
54+
bool isInvariantAccess(LoadOrStoreOp memOp, AffineForOp forOp);
55+
5156
/// Given an induction variable `iv` of type AffineForOp and `indices` of type
5257
/// IndexType, returns the set of `indices` that are independent of `iv`.
5358
///

mlir/include/mlir/Dialect/Affine/IR/AffineValueMap.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ class AffineValueMap {
4444
// Resets this AffineValueMap with 'map', 'operands', and 'results'.
4545
void reset(AffineMap map, ValueRange operands, ValueRange results = {});
4646

47+
/// Composes all incoming affine.apply ops and then simplifies and
48+
/// canonicalizes the map and operands. This can change the number of
49+
/// operands, but the result count remains the same.
50+
void composeSimplifyAndCanonicalize();
51+
4752
/// Return the value map that is the difference of value maps 'a' and 'b',
4853
/// represented as an affine map and its operands. The output map + operands
4954
/// are canonicalized and simplified.

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

Lines changed: 25 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -145,45 +145,36 @@ uint64_t mlir::affine::getLargestDivisorOfTripCount(AffineForOp forOp) {
145145
return *gcd;
146146
}
147147

148-
/// Given an induction variable `iv` of type AffineForOp and an access `index`
149-
/// of type index, returns `true` if `index` is independent of `iv` and
150-
/// false otherwise. The determination supports composition with at most one
151-
/// AffineApplyOp. The 'at most one AffineApplyOp' comes from the fact that
152-
/// the composition of AffineApplyOp needs to be canonicalized by construction
153-
/// to avoid writing code that composes arbitrary numbers of AffineApplyOps
154-
/// everywhere. To achieve this, at the very least, the compose-affine-apply
155-
/// pass must have been run.
148+
/// Given an affine.for `iv` and an access `index` of type index, returns `true`
149+
/// if `index` is independent of `iv` and false otherwise.
156150
///
157-
/// Prerequisites:
158-
/// 1. `iv` and `index` of the proper type;
159-
/// 2. at most one reachable AffineApplyOp from index;
160-
///
161-
/// Returns false in cases with more than one AffineApplyOp, this is
162-
/// conservative.
151+
/// Prerequisites: `iv` and `index` of the proper type;
163152
static bool isAccessIndexInvariant(Value iv, Value index) {
164-
assert(isAffineForInductionVar(iv) && "iv must be a AffineForOp");
165-
assert(isa<IndexType>(index.getType()) && "index must be of IndexType");
166-
SmallVector<Operation *, 4> affineApplyOps;
167-
getReachableAffineApplyOps({index}, affineApplyOps);
168-
169-
if (affineApplyOps.empty()) {
170-
// Pointer equality test because of Value pointer semantics.
171-
return index != iv;
172-
}
173-
174-
if (affineApplyOps.size() > 1) {
175-
affineApplyOps[0]->emitRemark(
176-
"CompositionAffineMapsPass must have been run: there should be at most "
177-
"one AffineApplyOp, returning false conservatively.");
178-
return false;
179-
}
153+
assert(isAffineForInductionVar(iv) && "iv must be an affine.for iv");
154+
assert(isa<IndexType>(index.getType()) && "index must be of 'index' type");
155+
auto map = AffineMap::getMultiDimIdentityMap(/*numDims=*/1, iv.getContext());
156+
SmallVector<Value> operands = {index};
157+
AffineValueMap avm(map, operands);
158+
avm.composeSimplifyAndCanonicalize();
159+
return !avm.isFunctionOf(0, iv);
160+
}
180161

181-
auto composeOp = cast<AffineApplyOp>(affineApplyOps[0]);
182-
// We need yet another level of indirection because the `dim` index of the
183-
// access may not correspond to the `dim` index of composeOp.
184-
return !composeOp.getAffineValueMap().isFunctionOf(0, iv);
162+
// Pre-requisite: Loop bounds should be in canonical form.
163+
template <typename LoadOrStoreOp>
164+
bool mlir::affine::isInvariantAccess(LoadOrStoreOp memOp, AffineForOp forOp) {
165+
AffineValueMap avm(memOp.getAffineMap(), memOp.getMapOperands());
166+
avm.composeSimplifyAndCanonicalize();
167+
return !llvm::is_contained(avm.getOperands(), forOp.getInductionVar());
185168
}
186169

170+
// Explicitly instantiate the template so that the compiler knows we need them.
171+
template bool mlir::affine::isInvariantAccess(AffineReadOpInterface,
172+
AffineForOp);
173+
template bool mlir::affine::isInvariantAccess(AffineWriteOpInterface,
174+
AffineForOp);
175+
template bool mlir::affine::isInvariantAccess(AffineLoadOp, AffineForOp);
176+
template bool mlir::affine::isInvariantAccess(AffineStoreOp, AffineForOp);
177+
187178
DenseSet<Value> mlir::affine::getInvariantAccesses(Value iv,
188179
ArrayRef<Value> indices) {
189180
DenseSet<Value> res;

mlir/lib/Dialect/Affine/IR/AffineValueMap.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ void AffineValueMap::reset(AffineMap map, ValueRange operands,
2424
this->results.assign(results.begin(), results.end());
2525
}
2626

27+
void AffineValueMap::composeSimplifyAndCanonicalize() {
28+
AffineMap sMap = getAffineMap();
29+
fullyComposeAffineMapAndOperands(&sMap, &operands);
30+
// Full composition also canonicalizes and simplifies before returning. We
31+
// need to canonicalize once more to drop unused operands.
32+
canonicalizeMapAndOperands(&sMap, &operands);
33+
this->map.reset(sMap);
34+
}
35+
2736
void AffineValueMap::difference(const AffineValueMap &a,
2837
const AffineValueMap &b, AffineValueMap *res) {
2938
assert(a.getNumResults() == b.getNumResults() && "invalid inputs");

mlir/test/Dialect/Affine/access-analysis.mlir

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
// RUN: mlir-opt %s -split-input-file -test-affine-access-analysis -verify-diagnostics | FileCheck %s
22

3-
// CHECK-LABEL: func @loop_1d
4-
func.func @loop_1d(%A : memref<?x?xf32>, %B : memref<?x?x?xf32>) {
3+
// CHECK-LABEL: func @loop_simple
4+
func.func @loop_simple(%A : memref<?x?xf32>, %B : memref<?x?x?xf32>) {
55
%c0 = arith.constant 0 : index
66
%M = memref.dim %A, %c0 : memref<?x?xf32>
77
affine.for %i = 0 to %M {
88
affine.for %j = 0 to %M {
99
affine.load %A[%c0, %i] : memref<?x?xf32>
1010
// expected-remark@above {{contiguous along loop 0}}
11+
// expected-remark@above {{invariant along loop 1}}
1112
affine.load %A[%c0, 8 * %i + %j] : memref<?x?xf32>
1213
// expected-remark@above {{contiguous along loop 1}}
1314
// Note/FIXME: access stride isn't being checked.
1415
// expected-remark@-3 {{contiguous along loop 0}}
1516

1617
// These are all non-contiguous along both loops. Nothing is emitted.
1718
affine.load %A[%i, %c0] : memref<?x?xf32>
19+
// expected-remark@above {{invariant along loop 1}}
1820
// Note/FIXME: access stride isn't being checked.
1921
affine.load %A[%i, 8 * %j] : memref<?x?xf32>
2022
// expected-remark@above {{contiguous along loop 1}}
@@ -27,6 +29,22 @@ func.func @loop_1d(%A : memref<?x?xf32>, %B : memref<?x?x?xf32>) {
2729

2830
// -----
2931

32+
// CHECK-LABEL: func @loop_unsimplified
33+
func.func @loop_unsimplified(%A : memref<100xf32>) {
34+
affine.for %i = 0 to 100 {
35+
affine.load %A[2 * %i - %i - %i] : memref<100xf32>
36+
// expected-remark@above {{invariant along loop 0}}
37+
38+
%m = affine.apply affine_map<(d0) -> (-2 * d0)>(%i)
39+
%n = affine.apply affine_map<(d0) -> (2 * d0)>(%i)
40+
affine.load %A[(%m + %n) floordiv 2] : memref<100xf32>
41+
// expected-remark@above {{invariant along loop 0}}
42+
}
43+
return
44+
}
45+
46+
// -----
47+
3048
#map = affine_map<(d0) -> (d0 * 16)>
3149
#map1 = affine_map<(d0) -> (d0 * 16 + 16)>
3250
#map2 = affine_map<(d0) -> (d0)>
@@ -41,11 +59,19 @@ func.func @tiled(%arg0: memref<*xf32>) {
4159
%alloc_0 = memref.alloc() : memref<1x16x1x16xf32>
4260
affine.for %arg4 = #map(%arg1) to #map1(%arg1) {
4361
affine.for %arg5 = #map(%arg3) to #map1(%arg3) {
62+
// TODO: here and below, the access isn't really invariant
63+
// along tile-space IVs where the intra-tile IVs' bounds
64+
// depend on them.
4465
%0 = affine.load %cast[%arg4] : memref<64xf32>
4566
// expected-remark@above {{contiguous along loop 3}}
67+
// expected-remark@above {{invariant along loop 0}}
68+
// expected-remark@above {{invariant along loop 1}}
69+
// expected-remark@above {{invariant along loop 2}}
70+
// expected-remark@above {{invariant along loop 4}}
4671
affine.store %0, %alloc_0[0, %arg1 * -16 + %arg4, 0, %arg3 * -16 + %arg5] : memref<1x16x1x16xf32>
4772
// expected-remark@above {{contiguous along loop 4}}
4873
// expected-remark@above {{contiguous along loop 2}}
74+
// expected-remark@above {{invariant along loop 1}}
4975
}
5076
}
5177
affine.for %arg4 = #map(%arg1) to #map1(%arg1) {
@@ -56,6 +82,9 @@ func.func @tiled(%arg0: memref<*xf32>) {
5682
// expected-remark@above {{contiguous along loop 2}}
5783
affine.store %0, %alloc[0, %arg5, %arg6, %arg4] : memref<1x224x224x64xf32>
5884
// expected-remark@above {{contiguous along loop 3}}
85+
// expected-remark@above {{invariant along loop 0}}
86+
// expected-remark@above {{invariant along loop 1}}
87+
// expected-remark@above {{invariant along loop 2}}
5988
}
6089
}
6190
}

mlir/test/lib/Dialect/Affine/TestAccessAnalysis.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,25 @@ void TestAccessAnalysis::runOnOperation() {
5959
enclosingOps.clear();
6060
getAffineForIVs(*memOp, &enclosingOps);
6161
for (unsigned d = 0, e = enclosingOps.size(); d < e; d++) {
62+
AffineForOp loop = enclosingOps[d];
6263
int memRefDim;
63-
bool isContiguous;
64+
bool isContiguous, isInvariant;
6465
if (auto read = dyn_cast<AffineReadOpInterface>(memOp)) {
65-
isContiguous = isContiguousAccess(enclosingOps[d].getInductionVar(),
66-
read, &memRefDim);
66+
isContiguous =
67+
isContiguousAccess(loop.getInductionVar(), read, &memRefDim);
68+
isInvariant = isInvariantAccess(read, loop);
6769
} else {
68-
isContiguous = isContiguousAccess(enclosingOps[d].getInductionVar(),
69-
cast<AffineWriteOpInterface>(memOp),
70-
&memRefDim);
70+
auto write = cast<AffineWriteOpInterface>(memOp);
71+
isContiguous =
72+
isContiguousAccess(loop.getInductionVar(), write, &memRefDim);
73+
isInvariant = isInvariantAccess(write, loop);
7174
}
75+
// Check for contiguity for the innermost memref dimension to avoid
76+
// emitting too many diagnostics.
7277
if (isContiguous && memRefDim == 0)
7378
memOp->emitRemark("contiguous along loop ") << d << '\n';
79+
if (isInvariant)
80+
memOp->emitRemark("invariant along loop ") << d << '\n';
7481
}
7582
}
7683
}

0 commit comments

Comments
 (0)