Skip to content
This repository was archived by the owner on Apr 23, 2021. It is now read-only.

Commit e3c2e48

Browse files
committed
LICM pass zero trip count loop handling; zero trip count loop removal in
-simplify-affine-structures - addresses #194 - change name of tablegen auto-generated internal helper for op interfaces to avoid potential conflicts with methods of same name in dialect namespaces. (addresses #197) Signed-off-by: Uday Bondhugula <[email protected]>
1 parent 705a743 commit e3c2e48

File tree

11 files changed

+144
-33
lines changed

11 files changed

+144
-33
lines changed

include/mlir/Dialect/LoopOps/LoopOps.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ void ensureLoopTerminator(Region &region, Builder &builder, Location loc);
5252
/// not an induction variable, then return nullptr.
5353
ForOp getForInductionVarOwner(Value *val);
5454

55+
/// Returns the trip count of the loop if it's a constant, None otherwise.
56+
Optional<uint64_t> getConstantTripCount(ForOp forOp);
5557
} // end namespace loop
5658
} // end namespace mlir
5759
#endif // MLIR_LOOPOPS_OPS_H_

include/mlir/Transforms/LoopLikeInterface.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#ifndef MLIR_TRANSFORMS_LOOPLIKEINTERFACE_H_
2323
#define MLIR_TRANSFORMS_LOOPLIKEINTERFACE_H_
2424

25+
#include "mlir/Analysis/LoopAnalysis.h"
2526
#include "mlir/IR/OpDefinition.h"
2627
#include "mlir/Support/LogicalResult.h"
2728
#include "llvm/ADT/ArrayRef.h"

include/mlir/Transforms/LoopLikeInterface.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ def LoopLikeOpInterface : OpInterface<"LoopLikeOpInterface"> {
5656
}],
5757
"LogicalResult", "moveOutOfLoop", (ins "ArrayRef<Operation *>":$ops)
5858
>,
59+
InterfaceMethod<[{ Get the trip count if it is a constant. }],
60+
"llvm::Optional<uint64_t>", "getConstantTripCount", (ins), [{
61+
return getConstantTripCount(op);
62+
}]>,
5963
];
6064
}
6165

lib/Dialect/LoopOps/LoopOps.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ bool ForOp::isDefinedOutsideOfLoop(Value *value) {
140140

141141
LogicalResult ForOp::moveOutOfLoop(ArrayRef<Operation *> ops) {
142142
for (auto *op : ops)
143-
op->moveBefore(this->getOperation());
143+
op->moveBefore(*this);
144144
return success();
145145
}
146146

@@ -153,6 +153,31 @@ ForOp mlir::loop::getForInductionVarOwner(Value *val) {
153153
return dyn_cast_or_null<ForOp>(containingInst);
154154
}
155155

156+
Optional<uint64_t> mlir::loop::getConstantTripCount(ForOp forOp) {
157+
Value *lb = forOp.lowerBound();
158+
Value *ub = forOp.upperBound();
159+
160+
if (lb == ub)
161+
return 0;
162+
163+
IntegerAttr lbCst, ubCst, step;
164+
if (!matchPattern(lb, m_Constant(&lbCst)) ||
165+
!matchPattern(ub, m_Constant(&ubCst)))
166+
return llvm::None;
167+
168+
int64_t lbConst = lbCst.getValue().getSExtValue();
169+
int64_t ubConst = ubCst.getValue().getSExtValue();
170+
if (ubConst - lbConst <= 0)
171+
return 0;
172+
173+
if (!matchPattern(forOp.step(), m_Constant(&step)))
174+
return llvm::None;
175+
176+
// Step is guaranteed to be positive.
177+
int64_t stepConst = step.getValue().getSExtValue();
178+
return llvm::divideCeil(ubConst - lbConst, stepConst);
179+
}
180+
156181
//===----------------------------------------------------------------------===//
157182
// IfOp
158183
//===----------------------------------------------------------------------===//

lib/Transforms/AffineLoopInvariantCodeMotion.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,17 @@
4040
#include "llvm/Support/Debug.h"
4141
#include "llvm/Support/raw_ostream.h"
4242

43-
#define DEBUG_TYPE "licm"
43+
#define DEBUG_TYPE "affine-licm"
4444

4545
using namespace mlir;
4646

4747
namespace {
4848

49-
/// Loop invariant code motion (LICM) pass.
49+
/// Affine loop invariant code motion (LICM) pass.
50+
/// TODO: This pass should be removed once the new LICM pass can handle its
51+
/// uses.
5052
/// TODO(asabne) : The pass is missing zero-trip tests.
5153
/// TODO(asabne) : Check for the presence of side effects before hoisting.
52-
/// TODO: This code should be removed once the new LICM pass can handle its
53-
/// uses.
5454
struct LoopInvariantCodeMotion : public FunctionPass<LoopInvariantCodeMotion> {
5555
void runOnFunction() override;
5656
void runOnAffineForOp(AffineForOp forOp);
@@ -245,4 +245,4 @@ mlir::createAffineLoopInvariantCodeMotionPass() {
245245

246246
static PassRegistration<LoopInvariantCodeMotion>
247247
pass("affine-loop-invariant-code-motion",
248-
"Hoist loop invariant instructions outside of the loop");
248+
"Hoist loop invariant operations outside of the loop");

lib/Transforms/LoopInvariantCodeMotion.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,15 @@ static bool canBeHoisted(Operation *op,
6262
auto thisOpIsSideEffecting = sideEffecting;
6363
if (thisOpIsSideEffecting != SideEffecting::Never) {
6464
thisOpIsSideEffecting = interface.isSideEffecting(op);
65-
// If the op always has sideeffects, we cannot hoist.
65+
// If the op always has side effects, we cannot hoist.
6666
if (thisOpIsSideEffecting == SideEffecting::Always)
6767
return false;
6868
}
6969
// Recurse into the regions for this op and check whether the contained ops
7070
// can be hoisted.
7171
for (auto &region : op->getRegions()) {
7272
for (auto &block : region.getBlocks()) {
73-
for (auto &innerOp : block) {
74-
if (innerOp.isKnownTerminator())
75-
continue;
73+
for (auto &innerOp : block.without_terminator()) {
7674
if (!canBeHoisted(&innerOp, definedOutside, thisOpIsSideEffecting,
7775
interface))
7876
return false;
@@ -112,7 +110,7 @@ static LogicalResult moveLoopInvariantCode(LoopLikeOpInterface looplike,
112110
}
113111
}
114112

115-
// For all instructions that we found to be invariant, move outside of the
113+
// For all operations that we found to be invariant, move outside of the
116114
// loop.
117115
auto result = looplike.moveOutOfLoop(opsToMove);
118116
LLVM_DEBUG(looplike.print(llvm::dbgs() << "Modified loop\n"));
@@ -128,6 +126,13 @@ void LoopInvariantCodeMotion::runOnOperation() {
128126
// the outer loop, which in turn can be further LICM'ed.
129127
getOperation()->walk([&](Operation *op) {
130128
if (auto looplike = dyn_cast<LoopLikeOpInterface>(op)) {
129+
// Skip zero trip count loops. For unknown trip counts, we still move
130+
// invariant code since it is side-effect free, and in general profitable.
131+
// TODO: when necessary, we could only move when the trip count is
132+
// guaranteed to be at least one.
133+
auto tripCount = looplike.getConstantTripCount();
134+
if (tripCount == 0UL)
135+
return;
131136
LLVM_DEBUG(op->print(llvm::dbgs() << "\nOriginal loop\n"));
132137
if (failed(moveLoopInvariantCode(looplike, interface)))
133138
signalPassFailure();
@@ -146,4 +151,4 @@ std::unique_ptr<Pass> mlir::createLoopInvariantCodeMotionPass() {
146151

147152
static PassRegistration<LoopInvariantCodeMotion>
148153
pass("loop-invariant-code-motion",
149-
"Hoist loop invariant instructions outside of the loop");
154+
"Hoist loop invariant operations outside of the loop");

lib/Transforms/SimplifyAffineStructures.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "mlir/Analysis/AffineStructures.h"
2323
#include "mlir/IR/IntegerSet.h"
2424
#include "mlir/Pass/Pass.h"
25+
#include "mlir/Transforms/LoopLikeInterface.h"
2526
#include "mlir/Transforms/Passes.h"
2627
#include "mlir/Transforms/Utils.h"
2728

@@ -93,12 +94,12 @@ std::unique_ptr<OpPassBase<FuncOp>> mlir::createSimplifyAffineStructuresPass() {
9394
void SimplifyAffineStructures::runOnFunction() {
9495
auto func = getFunction();
9596
simplifiedAttributes.clear();
96-
func.walk([&](Operation *opInst) {
97-
for (auto attr : opInst->getAttrs()) {
97+
func.walk([&](Operation *op) {
98+
for (auto attr : op->getAttrs()) {
9899
if (auto mapAttr = attr.second.dyn_cast<AffineMapAttr>())
99-
simplifyAndUpdateAttribute(opInst, attr.first, mapAttr);
100+
simplifyAndUpdateAttribute(op, attr.first, mapAttr);
100101
else if (auto setAttr = attr.second.dyn_cast<IntegerSetAttr>())
101-
simplifyAndUpdateAttribute(opInst, attr.first, setAttr);
102+
simplifyAndUpdateAttribute(op, attr.first, setAttr);
102103
}
103104
});
104105

@@ -110,8 +111,17 @@ void SimplifyAffineStructures::runOnFunction() {
110111
for (auto allocOp : allocOps) {
111112
normalizeMemRef(allocOp);
112113
}
114+
115+
// Remove zero trip count loops.
116+
// TODO: this could be moved to a more appropriate place.
117+
func.walk([&](LoopLikeOpInterface loopOp) {
118+
auto tripCount = loopOp.getConstantTripCount();
119+
if (tripCount == 0UL)
120+
loopOp.erase();
121+
});
113122
}
114123

115124
static PassRegistration<SimplifyAffineStructures>
116-
pass("simplify-affine-structures",
117-
"Simplify affine expressions in maps/sets and normalize memrefs");
125+
pass("simplify-affine-structures", "Simplify expressions in afine map/set "
126+
"attributes, normalize memrefs, remove "
127+
"zero trip-count loops");

test/Transforms/affine-loop-invariant-code-motion.mlir

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,23 @@ func @nested_loops_both_having_invariant_code() {
1717
// CHECK-NEXT: %cst_0 = constant 8.000000e+00 : f32
1818
// CHECK-NEXT: %1 = addf %cst, %cst_0 : f32
1919
// CHECK-NEXT: affine.for %arg0 = 0 to 10 {
20-
// CHECK-NEXT: affine.store %1, %0[%arg0] : memref<10xf32>
20+
// CHECK-NEXT: affine.store %1, %0[%arg0] : memref<10xf32>
2121

2222
return
2323
}
2424

25-
// The store-load forwarding can see through affine apply's since it relies on
26-
// dependence information.
27-
// CHECK-LABEL: func @store_affine_apply
28-
func @store_affine_apply() -> memref<10xf32> {
25+
// CHECK-LABEL: func @store_affine_for
26+
func @store_affine_for() -> memref<10xf32> {
2927
%cf7 = constant 7.0 : f32
3028
%m = alloc() : memref<10xf32>
3129
affine.for %arg0 = 0 to 10 {
32-
%t0 = affine.apply (d1) -> (d1 + 1)(%arg0)
33-
affine.store %cf7, %m[%t0] : memref<10xf32>
30+
affine.store %cf7, %m[%arg0 + 1] : memref<10xf32>
3431
}
3532
return %m : memref<10xf32>
3633
// CHECK: %cst = constant 7.000000e+00 : f32
3734
// CHECK-NEXT: %0 = alloc() : memref<10xf32>
3835
// CHECK-NEXT: affine.for %arg0 = 0 to 10 {
39-
// CHECK-NEXT: %1 = affine.apply #map3(%arg0)
40-
// CHECK-NEXT: affine.store %cst, %0[%1] : memref<10xf32>
36+
// CHECK-NEXT: affine.store %cst, %0[%arg0 + 1] : memref<10xf32>
4137
// CHECK-NEXT: }
4238
// CHECK-NEXT: return %0 : memref<10xf32>
4339
}

test/Transforms/loop-invariant-code-motion.mlir

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,54 @@ func @invariant_affine_nested_if_else() {
199199
// CHECK-NEXT: }
200200
// CHECK-NEXT: }
201201

202+
return
203+
}
204+
205+
// CHECK-LABEL: func @zero_trip_count_affine
206+
func @zero_trip_count_affine() {
207+
%m = alloc() : memref<10xf32>
208+
%cf7 = constant 7.0 : f32
209+
%N = constant 0 : index
210+
211+
affine.for %arg0 = 0 to %N {
212+
affine.for %arg1 = 0 to 10 {
213+
%v0 = addf %cf7, %cf7 : f32
214+
}
215+
}
216+
// CHECK: alloc() : memref<10xf32>
217+
// CHECK-NEXT: %cst = constant 7.000000e+00 : f32
218+
// CHECK-NEXT: %c0 = constant 0 : index
219+
// CHECK-NEXT: affine.for
220+
// CHECK-NEXT: addf
221+
// CHECK-NEXT: affine.for
222+
223+
return
224+
}
225+
226+
// CHECK-LABEL: func @zero_trip_count_loop
227+
func @zero_trip_count_loop(%N : index) {
228+
%m = alloc() : memref<10xf32>
229+
%cf7 = constant 7.0 : f32
230+
%c1 = constant 1 : index
231+
%c5 = constant 5 : index
232+
233+
loop.for %i = %N to %N step %c1 {
234+
loop.for %j = %c5 to %c5 step %c1 {
235+
addf %cf7, %cf7 : f32
236+
}
237+
}
238+
// CHECK: alloc() : memref<10xf32>
239+
// CHECK-NEXT: %cst = constant 7.000000e+00 : f32
240+
// CHECK-NEXT: %c1 = constant 1 : index
241+
// CHECK-NEXT: %c5 = constant 5 : index
242+
// CHECK-NEXT: loop.for
243+
// CHECK-NEXT: loop.for
244+
// CHECK-NEXT: addf
202245

203246
return
204247
}
205248

249+
// CHECK-LABEL: func @invariant_loop_dialect
206250
func @invariant_loop_dialect() {
207251
%ci0 = constant 0 : index
208252
%ci10 = constant 10 : index
@@ -211,7 +255,7 @@ func @invariant_loop_dialect() {
211255
%cf7 = constant 7.0 : f32
212256
%cf8 = constant 8.0 : f32
213257
loop.for %arg0 = %ci0 to %ci10 step %ci1 {
214-
loop.for %arg1 = %ci0 to %ci10 step %ci1 {
258+
loop.for %arg1 = %ci0 to %ci1 step %ci10 {
215259
%v0 = addf %cf7, %cf8 : f32
216260
}
217261
}
@@ -237,8 +281,8 @@ func @variant_loop_dialect() {
237281

238282
// CHECK: %0 = alloc() : memref<10xf32>
239283
// CHECK-NEXT: loop.for
240-
// CHECK-NEXT: loop.for
241-
// CHECK-NEXT: addi
284+
// CHECK-NEXT: loop.for
285+
// CHECK-NEXT: addi
242286

243287
return
244288
}

test/Transforms/simplify-affine-structures.mlir

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,22 @@ func @test_empty_set(%N : index) {
235235

236236
return
237237
}
238+
239+
// CHECK-LABEL: func @zero_trip_count_loops
240+
func @zero_trip_count_loops(%N : index) {
241+
%c0 = constant 0 : index
242+
%c1 = constant 1 : index
243+
%c-1 = constant -1 : index
244+
%M = affine.apply (d0) -> ((2*d0 + 4) mod 2)(%N)
245+
affine.for %i = 0 to %M {
246+
}
247+
affine.for %i = 0 to -1 {
248+
}
249+
loop.for %i = %M to %M step %c1 {
250+
}
251+
loop.for %i = %c0 to %c-1 step %N {
252+
}
253+
// All loops above should disappear.
254+
// CHECK-NOT: loop.for
255+
return
256+
}

tools/mlir-tblgen/OpInterfacesGen.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ using mlir::tblgen::OpInterfaceMethod;
4141
// beginning of the argument list.
4242
static void emitMethodNameAndArgs(const OpInterfaceMethod &method,
4343
raw_ostream &os, bool addOperationArg) {
44-
os << method.getName() << '(';
44+
// Whenever an operation argument is added, suffix helper method name with an
45+
// underscore to avoid conflicts with free functions of same name on the
46+
// concrete ops using this interface.
47+
os << method.getName() << (addOperationArg ? "_(" : "(");
4548
if (addOperationArg)
4649
os << "Operation *tablegen_opaque_op" << (method.arg_empty() ? "" : ", ");
4750
interleaveComma(method.getArguments(), os,
@@ -64,9 +67,11 @@ static void emitInterfaceDef(OpInterface &interface, raw_ostream &os) {
6467
emitMethodNameAndArgs(method, os, /*addOperationArg=*/false);
6568

6669
// Forward to the method on the concrete operation type.
67-
os << " {\n return getImpl()->" << method.getName() << '(';
70+
os << " {\n return getImpl()->" << method.getName();
6871
if (!method.isStatic())
69-
os << "getOperation()" << (method.arg_empty() ? "" : ", ");
72+
os << "_(getOperation()" << (method.arg_empty() ? "" : ", ");
73+
else
74+
os << "(";
7075
interleaveComma(
7176
method.getArguments(), os,
7277
[&](const OpInterfaceMethod::Argument &arg) { os << arg.name; });

0 commit comments

Comments
 (0)