Skip to content

Commit ba8cade

Browse files
authored
Properly handle optimizing out a set from inside the value of another set in SimplifyLocals (#2064)
Details in lengthy comment in the source. Fixes #2063
1 parent 7c3d4cd commit ba8cade

3 files changed

Lines changed: 107 additions & 3 deletions

File tree

src/passes/SimplifyLocals.cpp

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ struct SimplifyLocals
216216
}
217217
}
218218

219-
void visitGetLocal(GetLocal* curr) {
219+
void optimizeGetLocal(GetLocal* curr) {
220220
auto found = sinkables.find(curr->index);
221221
if (found != sinkables.end()) {
222222
auto* set = (*found->second.item)
@@ -311,8 +311,61 @@ struct SimplifyLocals
311311
static void
312312
visitPost(SimplifyLocals<allowTee, allowStructure, allowNesting>* self,
313313
Expression** currp) {
314+
// Handling invalidations in the case where the current node is a get
315+
// that we sink into is not trivial in general. In the simple case,
316+
// all current sinkables are compatible with each other (otherwise one
317+
// would have invalidated a previous one, and removed it). Given that, if
318+
// we sink one of the sinkables, then that new code cannot invalidate any
319+
// other sinkable - we've already compared them. However, a tricky case
320+
// is when a sinkable contains another sinkable,
321+
//
322+
// (local.set $x
323+
// (block (result i32)
324+
// (A (local.get $y))
325+
// (local.set $y B)
326+
// )
327+
// )
328+
// (C (local.get $y))
329+
// (D (local.get $x))
330+
//
331+
// If we sink the set of $y, we have
332+
//
333+
// (local.set $x
334+
// (block (result i32)
335+
// (A (local.get $y))
336+
// (nop)
337+
// )
338+
// )
339+
// (C B)
340+
// (D (local.get $x))
341+
//
342+
// There is now a risk that the set of $x should be invalidated, because
343+
// if we sink it then A may happen after B (imagine that B contains
344+
// something dangerous for that). To verify the risk, we could recursively
345+
// scan all of B, but that is less efficient. Instead, the key thing is
346+
// that if we sink out an inner part of a set, we should just leave further
347+
// work on it to a later iteration. This is achieved by checking for
348+
// invalidation on the original node, the local.get $y, which is guaranteed
349+
// to invalidate the parent whose inner part was removed (since the inner
350+
// part has a set, and the original node is a get of that same local).
351+
//
352+
// To implement this, if the current node is a get, note it and use it
353+
// for invalidations later down. We must note it since optimizing the get
354+
// may perform arbitrary changes to the graph, including reuse the get.
355+
356+
Expression* original = *currp;
357+
358+
GetLocal originalGet;
359+
360+
if (auto* get = (*currp)->dynCast<GetLocal>()) {
361+
// Note: no visitor for GetLocal, so that we can handle it here.
362+
originalGet = *get;
363+
original = &originalGet;
364+
self->optimizeGetLocal(get);
365+
}
366+
314367
// perform main SetLocal processing here, since we may be the result of
315-
// replaceCurrent, i.e., the visitor was not called.
368+
// replaceCurrent, i.e., no visitor for SetLocal, like GetLocal above.
316369
auto* set = (*currp)->dynCast<SetLocal>();
317370

318371
if (set) {
@@ -332,7 +385,7 @@ struct SimplifyLocals
332385
}
333386

334387
EffectAnalyzer effects(self->getPassOptions());
335-
if (effects.checkPost(*currp)) {
388+
if (effects.checkPost(original)) {
336389
self->checkInvalidations(effects);
337390
}
338391

test/passes/simplify-locals.txt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
(module
2+
(type $0 (func (result i32)))
3+
(func $sink-from-inside (; 0 ;) (type $0) (result i32)
4+
(local $0 i32)
5+
(local $1 i32)
6+
(local $2 i32)
7+
(nop)
8+
(i32.and
9+
(select
10+
(i32.const 0)
11+
(i32.const 1)
12+
(i32.const 1)
13+
)
14+
(block $block (result i32)
15+
(nop)
16+
(nop)
17+
(nop)
18+
(i32.const 1)
19+
)
20+
)
21+
)
22+
)

test/passes/simplify-locals.wast

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
(module
2+
(func $sink-from-inside (result i32)
3+
(local $0 i32)
4+
(local $1 i32)
5+
(local $2 i32)
6+
(local.set $2
7+
(block (result i32)
8+
(local.set $0
9+
(i32.const 1)
10+
)
11+
(drop
12+
(local.get $0)
13+
)
14+
(local.set $1 ;; after we sink this, must be careful about sinking the parent, to not reorder other things badly
15+
(select
16+
(i32.const 0)
17+
(i32.const 1)
18+
(local.get $0)
19+
)
20+
)
21+
(i32.const 1)
22+
)
23+
)
24+
(i32.and
25+
(local.get $1)
26+
(local.get $2)
27+
)
28+
)
29+
)

0 commit comments

Comments
 (0)