Skip to content

Commit 62f073f

Browse files
authored
[LICM] Defer replacing hoisted code with nop (#9326)
LoopInvariantCodeMotion uses a `LazyLocalGraph` that delays walking the function and building its CFG until the first `getSets` call. Previously, `visitLoop` immediately replaced hoisted expressions with nops before later inserting them above the loop. When an expression that wrote to a local without reading any locals was hoisted first (bypassing `hasGetDependingOnLoopSet`), and a subsequent expression in the same loop read a local and triggered the first `LazyLocalGraph::getSets` call, `LocalGraphFlower` walked the function while the hoisted expression was detached from the AST. As a result, `LocalGraph` missed the `local.set` inside the detached expression and later misclassified expressions in enclosing loops that read that local as loop-invariant. Record pointers to the hoisted expression slots in `movedCode` and only replace them with nops when constructing the replacement block at the end of `visitLoop`, keeping the AST intact whenever `LazyLocalGraph` initializes.
1 parent 4ca7ea7 commit 62f073f

2 files changed

Lines changed: 87 additions & 9 deletions

File tree

‎src/passes/LoopInvariantCodeMotion.cpp‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ struct LoopInvariantCodeMotion
6060
void visitLoop(Loop* loop) {
6161
// We accumulate all the code we can move out, and will place it
6262
// in a block just preceding the loop.
63-
std::vector<Expression*> movedCode;
63+
std::vector<Expression**> movedCode;
6464
// Accumulate effects of things we can't move out - things
6565
// we move out later must cross them, so we must verify it
6666
// is ok to do so.
@@ -172,10 +172,11 @@ struct LoopInvariantCodeMotion
172172
numSetsForIndex[set->index]++;
173173
}
174174
} else {
175-
// We can move it! Leave the changes, move the code, and update
176-
// loopSets.
177-
movedCode.push_back(curr);
178-
*currp = Builder(*getModule()).makeNop();
175+
// We can move it! Leave the changes, record the code to move, and
176+
// update loopSets. Do not replace *currp with a nop yet, as that
177+
// would detach the expression from the function body before a
178+
// potential lazy initialization of localGraph.
179+
movedCode.push_back(currp);
179180
for (auto* set : currSets.list) {
180181
loopSets.erase(set);
181182
}
@@ -195,10 +196,14 @@ struct LoopInvariantCodeMotion
195196
if (!movedCode.empty()) {
196197
// Finish the moving by emitting the code outside.
197198
Builder builder(*getModule());
198-
auto* ret = builder.makeBlock(movedCode);
199-
ret->list.push_back(loop);
200-
ret->finalize(loop->type);
201-
replaceCurrent(ret);
199+
std::vector<Expression*> exprs;
200+
exprs.reserve(movedCode.size() + 1);
201+
for (auto** currp : movedCode) {
202+
exprs.push_back(*currp);
203+
*currp = builder.makeNop();
204+
}
205+
exprs.push_back(loop);
206+
replaceCurrent(builder.makeBlock(exprs, loop->type));
202207
// Note that we do not need to modify the localGraph - we keep
203208
// each get in a position to be influenced by exactly the same
204209
// sets as before.

‎test/lit/passes/licm.wast‎

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,79 @@
186186
(local.get $y)
187187
)
188188

189+
;; CHECK: (func $nested-loop-lazy-local-graph (type $1) (param $cond i32) (result i32)
190+
;; CHECK-NEXT: (local $x i32)
191+
;; CHECK-NEXT: (local $y i32)
192+
;; CHECK-NEXT: (local $z i32)
193+
;; CHECK-NEXT: (loop $outer
194+
;; CHECK-NEXT: (local.set $y
195+
;; CHECK-NEXT: (i32.add
196+
;; CHECK-NEXT: (local.get $x)
197+
;; CHECK-NEXT: (i32.const 10)
198+
;; CHECK-NEXT: )
199+
;; CHECK-NEXT: )
200+
;; CHECK-NEXT: (block
201+
;; CHECK-NEXT: (if
202+
;; CHECK-NEXT: (i32.const 1)
203+
;; CHECK-NEXT: (then
204+
;; CHECK-NEXT: (local.set $x
205+
;; CHECK-NEXT: (i32.const 1)
206+
;; CHECK-NEXT: )
207+
;; CHECK-NEXT: )
208+
;; CHECK-NEXT: )
209+
;; CHECK-NEXT: (local.set $z
210+
;; CHECK-NEXT: (i32.eqz
211+
;; CHECK-NEXT: (local.get $x)
212+
;; CHECK-NEXT: )
213+
;; CHECK-NEXT: )
214+
;; CHECK-NEXT: (loop $inner
215+
;; CHECK-NEXT: (nop)
216+
;; CHECK-NEXT: (nop)
217+
;; CHECK-NEXT: )
218+
;; CHECK-NEXT: )
219+
;; CHECK-NEXT: (br_if $outer
220+
;; CHECK-NEXT: (local.get $cond)
221+
;; CHECK-NEXT: )
222+
;; CHECK-NEXT: )
223+
;; CHECK-NEXT: (local.get $y)
224+
;; CHECK-NEXT: )
225+
(func $nested-loop-lazy-local-graph (param $cond i32) (result i32)
226+
(local $x i32)
227+
(local $y i32)
228+
(local $z i32)
229+
;; Hoisting the (if ...) out of $inner must not detach its (local.set $x)
230+
;; from the function before LazyLocalGraph initializes when checking the
231+
;; subsequent (local.get $x) in $inner. Otherwise, $outer would miss the
232+
;; write to $x and invalidly hoist (local.set $y ...).
233+
(loop $outer
234+
(local.set $y
235+
(i32.add
236+
(local.get $x)
237+
(i32.const 10)
238+
)
239+
)
240+
(loop $inner
241+
(if
242+
(i32.const 1)
243+
(then
244+
(local.set $x
245+
(i32.const 1)
246+
)
247+
)
248+
)
249+
(local.set $z
250+
(i32.eqz
251+
(local.get $x)
252+
)
253+
)
254+
)
255+
(br_if $outer
256+
(local.get $cond)
257+
)
258+
)
259+
(local.get $y)
260+
)
261+
189262
;; CHECK: (func $hoist-trap-before-backedge (type $1) (param $x i32) (result i32)
190263
;; CHECK-NEXT: (local $y i32)
191264
;; CHECK-NEXT: (block

0 commit comments

Comments
 (0)