Skip to content

Commit c74638d

Browse files
[MLIR][AFFINE] Handling possible side-effects during fusion
Check if the forOps have any possible side-effects and handle them during MDG formation. This commit checks if forOp contains a memoryEffect operation, which is a possible side-effects and thus, fusion is not possible. In this commit we modifify the dependency graph initialization to add dependency edges between nodes with side-effecting ops and any other node that may be affected by it (basically node that is accessing any memref). Also, modified test loop-fusion-4.mlir to add a test case.
1 parent 7da7695 commit c74638d

File tree

3 files changed

+77
-6
lines changed

3 files changed

+77
-6
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ struct LoopNestStateCollector {
3939
SmallVector<AffineForOp, 4> forOps;
4040
SmallVector<Operation *, 4> loadOpInsts;
4141
SmallVector<Operation *, 4> storeOpInsts;
42+
// Collection of possible memory side-effecting ops, other than the ops
43+
// already accounted for like load/store/alloc/free.
44+
SmallVector<Operation *, 4> memoryEffectOpInsts;
4245
bool hasNonAffineRegionOp = false;
4346

4447
// Collects load and store operations, and whether or not a region holding op
@@ -65,7 +68,8 @@ struct MemRefDependenceGraph {
6568
SmallVector<Operation *, 4> loads;
6669
// List of store op insts.
6770
SmallVector<Operation *, 4> stores;
68-
71+
// List of memory effect op insts other than the ones already accounted for.
72+
SmallVector<Operation *, 4> memEffects;
6973
Node(unsigned id, Operation *op) : id(id), op(op) {}
7074

7175
// Returns the load op count for 'memref'.

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

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,27 @@ void LoopNestStateCollector::collect(Operation *opToWalk) {
5050
loadOpInsts.push_back(op);
5151
else if (isa<AffineWriteOpInterface>(op))
5252
storeOpInsts.push_back(op);
53+
else {
54+
// Checking to see if op has alloc or dealloc effect.
55+
auto hasNoAllocOrFreeEffect = [op]() -> bool {
56+
if (auto effectInterface = dyn_cast<MemoryEffectOpInterface>(op)) {
57+
SmallVector<MemoryEffects::EffectInstance, 1> effects;
58+
effectInterface.getEffects(effects);
59+
// if any of the effects of this op are alloc or free type then
60+
// return false.
61+
return !llvm::any_of(
62+
effects, [](const MemoryEffects::EffectInstance &it) -> bool {
63+
return isa<MemoryEffects::Allocate, MemoryEffects::Free>(
64+
it.getEffect());
65+
});
66+
}
67+
return true;
68+
};
69+
// TODO: Add handling of alloc/dealloc ops in MDG construction.
70+
// Current version filters them out.
71+
if (!mlir::isMemoryEffectFree(op) && hasNoAllocOrFreeEffect())
72+
memoryEffectOpInsts.push_back(op);
73+
}
5374
});
5475
}
5576

@@ -113,6 +134,8 @@ bool MemRefDependenceGraph::init() {
113134
// Map from a memref to the set of ids of the nodes that have ops accessing
114135
// the memref.
115136
DenseMap<Value, SetVector<unsigned>> memrefAccesses;
137+
// Vector of nodes with possible memoryEffects in order of traversal.
138+
SmallVector<Node *> memoryEffectNodes;
116139

117140
DenseMap<Operation *, unsigned> forToNodeMap;
118141
for (Operation &op : block) {
@@ -129,13 +152,24 @@ bool MemRefDependenceGraph::init() {
129152
for (auto *opInst : collector.loadOpInsts) {
130153
node.loads.push_back(opInst);
131154
auto memref = cast<AffineReadOpInterface>(opInst).getMemRef();
155+
if (llvm::is_contained(memrefAccesses, memref)) {
156+
for (auto *n : memoryEffectNodes)
157+
memrefAccesses[memref].insert(n->id);
158+
}
132159
memrefAccesses[memref].insert(node.id);
133160
}
134161
for (auto *opInst : collector.storeOpInsts) {
135162
node.stores.push_back(opInst);
136163
auto memref = cast<AffineWriteOpInterface>(opInst).getMemRef();
164+
if (llvm::is_contained(memrefAccesses, memref)) {
165+
for (auto *n : memoryEffectNodes)
166+
memrefAccesses[memref].insert(n->id);
167+
}
137168
memrefAccesses[memref].insert(node.id);
138169
}
170+
node.memEffects = collector.memoryEffectOpInsts;
171+
if (!node.memEffects.empty())
172+
memoryEffectNodes.push_back(&node);
139173
forToNodeMap[&op] = node.id;
140174
nodes.insert({node.id, node});
141175
} else if (dyn_cast<AffineReadOpInterface>(op)) {
@@ -214,17 +248,23 @@ bool MemRefDependenceGraph::init() {
214248
}
215249

216250
// Walk memref access lists and add graph edges between dependent nodes.
251+
// add edge between nodes accessing memrefs where atleast one of them has a
252+
// write op or has a potential side-effect op.
217253
for (auto &memrefAndList : memrefAccesses) {
218254
unsigned n = memrefAndList.second.size();
219255
for (unsigned i = 0; i < n; ++i) {
220256
unsigned srcId = memrefAndList.second[i];
221-
bool srcHasStore =
222-
getNode(srcId)->getStoreOpCount(memrefAndList.first) > 0;
257+
Node *srcNode = getNode(srcId);
258+
bool srcMayHaveMemEffOrStore =
259+
!srcNode->memEffects.empty() ||
260+
srcNode->getStoreOpCount(memrefAndList.first) > 0;
223261
for (unsigned j = i + 1; j < n; ++j) {
224262
unsigned dstId = memrefAndList.second[j];
225-
bool dstHasStore =
226-
getNode(dstId)->getStoreOpCount(memrefAndList.first) > 0;
227-
if (srcHasStore || dstHasStore)
263+
Node *dstNode = getNode(dstId);
264+
bool dstMayHaveMemEffOrStore =
265+
!dstNode->memEffects.empty() ||
266+
dstNode->getStoreOpCount(memrefAndList.first) > 0;
267+
if (srcMayHaveMemEffOrStore || dstMayHaveMemEffOrStore)
228268
addEdge(srcId, dstId, memrefAndList.first);
229269
}
230270
}

mlir/test/Dialect/Affine/loop-fusion-4.mlir

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// RUN: mlir-opt -allow-unregistered-dialect %s -pass-pipeline='builtin.module(func.func(affine-loop-fusion{mode=producer}))' -split-input-file | FileCheck %s --check-prefix=PRODUCER-CONSUMER
22
// RUN: mlir-opt -allow-unregistered-dialect %s -pass-pipeline='builtin.module(func.func(affine-loop-fusion{fusion-maximal mode=sibling}))' -split-input-file | FileCheck %s --check-prefix=SIBLING-MAXIMAL
3+
// RUN: mlir-opt -allow-unregistered-dialect %s -pass-pipeline='builtin.module(func.func(affine-loop-fusion{fusion-maximal}))' -split-input-file | FileCheck %s --check-prefix=FUSION-MAXIMAL
34

45
// Part I of fusion tests in mlir/test/Transforms/loop-fusion.mlir.
56
// Part II of fusion tests in mlir/test/Transforms/loop-fusion-2.mlir
@@ -226,3 +227,29 @@ func.func @fuse_higher_dim_nest_into_lower_dim_nest() {
226227
// PRODUCER-CONSUMER: return
227228
return
228229
}
230+
231+
// -----
232+
233+
// FUSION-MAXIMAL-LABEL: func @should_not_fuse_loops_with_side_effects
234+
func.func @should_not_fuse_loops_with_side_effects() {
235+
%arg0=memref.alloc(): memref<10xf32>
236+
affine.for %i = 0 to 10 {
237+
%0 = affine.load %arg0[%i] : memref<10xf32>
238+
"bar"():()->()
239+
}
240+
affine.for %j = 0 to 10 {
241+
%0 = affine.load %arg0[%j] : memref<10xf32>
242+
"foo"():()->()
243+
}
244+
// Should not fuse loops with side-effects.
245+
// FUSION-MAXIMAL: affine.for %{{.*}} = 0 to 10
246+
// FUSION-MAXIMAL-NEXT: affine.load
247+
// FUSION-MAXIMAL-NEXT: "bar"
248+
// FUSION-MAXIMAL-NEXT: }
249+
// FUSION-MAXIMAL-NEXT: affine.for %{{.*}} = 0 to 10
250+
// FUSION-MAXIMAL-NEXT: affine.load
251+
// FUSION-MAXIMAL-NEXT: "foo"
252+
// FUSION-MAXIMAL-NEXT: }
253+
// FUSION-MAXIMAL: return
254+
return
255+
}

0 commit comments

Comments
 (0)