Skip to content

Commit 501b0a0

Browse files
authored
Prevent pops from sinking in SimplifyLocals (#2885)
This prevents `exnref.pop`s from being sinked and separated from `catch`. For example, ```wast (try (do) (catch (local.set $0 (exnref.pop)) (call $foo (i32.const 3) (local.get $0) ) ) ) ``` Here, if we sink `exnref.pop` to remove `local.set $0` and `local.get $0`, it becomes this: ```wast (try (do) (catch (nop) (call $foo (i32.const 3) (exnref.pop) ) ) ) ``` This move was possible because `i32.const 3` does not have any side effects. But this is incorrect because now `exnref.pop` does not follow right after `catch`. To prevent this, this patch checks this case in `canSink` in SimplifyLocals. When we encountered a similar case in CodeFolding, we prevented every expression that contains `Pop` anywhere in it from being moved, which was too conservative. This adds `danglingPop` property in `EffectAnalyzer`, so that only pops that are not enclosed within a `catch` count as 'dangling pops` and we only prevent those pops from being moved or sinked.
1 parent 3c4630b commit 501b0a0

10 files changed

+139
-13
lines changed

src/binaryen-c.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2898,6 +2898,10 @@ BinaryenSideEffects BinaryenSideEffectIsAtomic(void) {
28982898
BinaryenSideEffects BinaryenSideEffectThrows(void) {
28992899
return static_cast<BinaryenSideEffects>(EffectAnalyzer::SideEffects::Throws);
29002900
}
2901+
BinaryenSideEffects BinaryenSideEffectDanglingPop(void) {
2902+
return static_cast<BinaryenSideEffects>(
2903+
EffectAnalyzer::SideEffects::DanglingPop);
2904+
}
29012905
BinaryenSideEffects BinaryenSideEffectAny(void) {
29022906
return static_cast<BinaryenSideEffects>(EffectAnalyzer::SideEffects::Any);
29032907
}

src/binaryen-c.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,7 @@ BINARYEN_API BinaryenSideEffects BinaryenSideEffectWritesMemory(void);
15801580
BINARYEN_API BinaryenSideEffects BinaryenSideEffectImplicitTrap(void);
15811581
BINARYEN_API BinaryenSideEffects BinaryenSideEffectIsAtomic(void);
15821582
BINARYEN_API BinaryenSideEffects BinaryenSideEffectThrows(void);
1583+
BINARYEN_API BinaryenSideEffects BinaryenSideEffectDanglingPop(void);
15831584
BINARYEN_API BinaryenSideEffects BinaryenSideEffectAny(void);
15841585

15851586
BINARYEN_API BinaryenSideEffects BinaryenExpressionGetSideEffects(

src/ir/effects.h

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,19 @@ struct EffectAnalyzer
7676
// inner try, we don't mark it as 'throws', because it will be caught by an
7777
// inner catch.
7878
size_t tryDepth = 0;
79+
// The nested depth of catch. This is necessary to track danglng pops.
80+
size_t catchDepth = 0;
81+
// If this expression contains 'exnref.pop's that are not enclosed in 'catch'
82+
// body. For example, (drop (exnref.pop)) should set this to true.
83+
bool danglingPop = false;
7984

8085
static void scan(EffectAnalyzer* self, Expression** currp) {
8186
Expression* curr = *currp;
8287
// We need to decrement try depth before catch starts, so handle it
8388
// separately
8489
if (curr->is<Try>()) {
8590
self->pushTask(doVisitTry, currp);
91+
self->pushTask(doEndCatch, currp);
8692
self->pushTask(scan, &curr->cast<Try>()->catchBody);
8793
self->pushTask(doStartCatch, currp);
8894
self->pushTask(scan, &curr->cast<Try>()->body);
@@ -100,6 +106,12 @@ struct EffectAnalyzer
100106
static void doStartCatch(EffectAnalyzer* self, Expression** currp) {
101107
assert(self->tryDepth > 0 && "try depth cannot be negative");
102108
self->tryDepth--;
109+
self->catchDepth++;
110+
}
111+
112+
static void doEndCatch(EffectAnalyzer* self, Expression** currp) {
113+
assert(self->catchDepth > 0 && "catch depth cannot be negative");
114+
self->catchDepth--;
103115
}
104116

105117
// Helper functions to check for various effect types
@@ -128,7 +140,7 @@ struct EffectAnalyzer
128140
}
129141
bool hasSideEffects() const {
130142
return hasGlobalSideEffects() || localsWritten.size() > 0 ||
131-
transfersControlFlow() || implicitTrap;
143+
transfersControlFlow() || implicitTrap || danglingPop;
132144
}
133145
bool hasAnything() const {
134146
return hasSideEffects() || accessesLocal() || readsMemory ||
@@ -148,7 +160,8 @@ struct EffectAnalyzer
148160
if ((transfersControlFlow() && other.hasSideEffects()) ||
149161
(other.transfersControlFlow() && hasSideEffects()) ||
150162
((writesMemory || calls) && other.accessesMemory()) ||
151-
(accessesMemory() && (other.writesMemory || other.calls))) {
163+
(accessesMemory() && (other.writesMemory || other.calls)) ||
164+
(danglingPop || other.danglingPop)) {
152165
return true;
153166
}
154167
// All atomics are sequentially consistent for now, and ordered wrt other
@@ -203,6 +216,7 @@ struct EffectAnalyzer
203216
implicitTrap = implicitTrap || other.implicitTrap;
204217
isAtomic = isAtomic || other.isAtomic;
205218
throws = throws || other.throws;
219+
danglingPop = danglingPop || other.danglingPop;
206220
for (auto i : other.localsRead) {
207221
localsRead.insert(i);
208222
}
@@ -470,7 +484,11 @@ struct EffectAnalyzer
470484
}
471485
void visitNop(Nop* curr) {}
472486
void visitUnreachable(Unreachable* curr) { branchesOut = true; }
473-
void visitPop(Pop* curr) { calls = true; }
487+
void visitPop(Pop* curr) {
488+
if (catchDepth == 0) {
489+
danglingPop = true;
490+
}
491+
}
474492
void visitTupleMake(TupleMake* curr) {}
475493
void visitTupleExtract(TupleExtract* curr) {}
476494

@@ -500,7 +518,8 @@ struct EffectAnalyzer
500518
ImplicitTrap = 1 << 8,
501519
IsAtomic = 1 << 9,
502520
Throws = 1 << 10,
503-
Any = (1 << 11) - 1
521+
DanglingPop = 1 << 11,
522+
Any = (1 << 12) - 1
504523
};
505524
uint32_t getSideEffects() const {
506525
uint32_t effects = 0;
@@ -537,6 +556,9 @@ struct EffectAnalyzer
537556
if (throws) {
538557
effects |= SideEffects::Throws;
539558
}
559+
if (danglingPop) {
560+
effects |= SideEffects::DanglingPop;
561+
}
540562
return effects;
541563
}
542564

src/js/binaryen.js-post.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ function initializeConstants() {
485485
'ImplicitTrap',
486486
'IsAtomic',
487487
'Throws',
488+
'DanglingPop',
488489
'Any'
489490
].forEach(function(name) {
490491
Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name]();

src/passes/CodeFolding.cpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -305,12 +305,12 @@ struct CodeFolding : public WalkerPass<ControlFlowWalker<CodeFolding>> {
305305
return false;
306306
}
307307
if (getModule()->features.hasExceptionHandling()) {
308+
EffectAnalyzer effects(getPassOptions(), getModule()->features, item);
308309
// Currently pop instructions are only used for exnref.pop, which is a
309-
// pseudo instruction following a catch. We check if the current
310-
// expression has a pop child. This can be overly conservative, because
311-
// this can also exclude whole try-catches that contain a pop within
312-
// them.
313-
if (!FindAll<Pop>(item).list.empty()) {
310+
// pseudo instruction following a catch. We cannot move expressions
311+
// containing pops if they are not enclosed in a 'catch' body, because a
312+
// pop instruction should follow right after 'catch'.
313+
if (effects.danglingPop) {
314314
return false;
315315
}
316316
// When an expression can throw and it is within a try scope, taking it
@@ -321,9 +321,7 @@ struct CodeFolding : public WalkerPass<ControlFlowWalker<CodeFolding>> {
321321
// conservative approximation because there can be cases that 'try' is
322322
// within the expression that may throw so it is safe to take the
323323
// expression out.
324-
if (EffectAnalyzer(getPassOptions(), getModule()->features, item)
325-
.throws &&
326-
!FindAll<Try>(outOf).list.empty()) {
324+
if (effects.throws && !FindAll<Try>(outOf).list.empty()) {
327325
return false;
328326
}
329327
}

src/passes/SimplifyLocals.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,14 @@ struct SimplifyLocals
411411
if (set->isTee()) {
412412
return false;
413413
}
414+
// We cannot move expressions containing exnref.pops that are not enclosed
415+
// in 'catch', because 'exnref.pop' should follow right after 'catch'.
416+
FeatureSet features = this->getModule()->features;
417+
if (features.hasExceptionHandling() &&
418+
EffectAnalyzer(this->getPassOptions(), features, set->value)
419+
.danglingPop) {
420+
return false;
421+
}
414422
// if in the first cycle, or not allowing tees, then we cannot sink if >1
415423
// use as that would make a tee
416424
if ((firstCycle || !allowTee) && getCounter.num[set->index] > 1) {

test/binaryen.js/sideffects.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ console.log("SideEffects.WritesMemory=" + binaryen.SideEffects.WritesMemory);
1010
console.log("SideEffects.ImplicitTrap=" + binaryen.SideEffects.ImplicitTrap);
1111
console.log("SideEffects.IsAtomic=" + binaryen.SideEffects.IsAtomic);
1212
console.log("SideEffects.Throws=" + binaryen.SideEffects.Throws);
13+
console.log("SideEffects.DanglingPop=" + binaryen.SideEffects.DanglingPop);
1314
console.log("SideEffects.Any=" + binaryen.SideEffects.Any);
1415

1516
var module = new binaryen.Module();
@@ -104,3 +105,12 @@ assert(
104105
==
105106
binaryen.SideEffects.Calls | binaryen.SideEffects.Throws
106107
);
108+
109+
assert(
110+
binaryen.getSideEffects(
111+
module.drop(module.exnref.pop()),
112+
module.getFeatures()
113+
)
114+
==
115+
binaryen.SideEffects.DanglingPop
116+
);

test/binaryen.js/sideffects.js.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ SideEffects.WritesMemory=128
1010
SideEffects.ImplicitTrap=256
1111
SideEffects.IsAtomic=512
1212
SideEffects.Throws=1024
13-
SideEffects.Any=2047
13+
SideEffects.DanglingPop=2048
14+
SideEffects.Any=4095

test/passes/simplify-locals_all-features.txt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1894,6 +1894,7 @@
18941894
)
18951895
(module
18961896
(type $none_=>_none (func))
1897+
(type $i32_exnref_=>_none (func (param i32 exnref)))
18971898
(type $exnref_=>_none (func (param exnref)))
18981899
(type $none_=>_exnref (func (result exnref)))
18991900
(event $event$0 (attr 0) (param))
@@ -1937,4 +1938,46 @@
19371938
)
19381939
)
19391940
)
1941+
(func $foo (param $0 i32) (param $1 exnref)
1942+
(nop)
1943+
)
1944+
(func $pop-cannot-be-sinked
1945+
(local $0 exnref)
1946+
(try
1947+
(do
1948+
(nop)
1949+
)
1950+
(catch
1951+
(local.set $0
1952+
(exnref.pop)
1953+
)
1954+
(call $foo
1955+
(i32.const 3)
1956+
(local.get $0)
1957+
)
1958+
)
1959+
)
1960+
)
1961+
(func $pop-within-catch-can-be-sinked
1962+
(local $0 exnref)
1963+
(try
1964+
(do
1965+
(nop)
1966+
)
1967+
(catch
1968+
(nop)
1969+
(call $foo
1970+
(i32.const 3)
1971+
(try (result exnref)
1972+
(do
1973+
(ref.null)
1974+
)
1975+
(catch
1976+
(exnref.pop)
1977+
)
1978+
)
1979+
)
1980+
)
1981+
)
1982+
)
19401983
)

test/passes/simplify-locals_all-features.wast

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1711,4 +1711,42 @@
17111711
)
17121712
)
17131713
)
1714+
1715+
(func $foo (param i32 exnref))
1716+
(func $pop-cannot-be-sinked (local $0 exnref)
1717+
(try
1718+
(do)
1719+
(catch
1720+
;; This (local.set $0) of (exnref.pop) cannot be sinked to
1721+
;; (local.get $0) below, because exnref.pop should follow right after
1722+
;; 'catch'.
1723+
(local.set $0 (exnref.pop))
1724+
(call $foo
1725+
(i32.const 3)
1726+
(local.get $0)
1727+
)
1728+
)
1729+
)
1730+
)
1731+
1732+
(func $pop-within-catch-can-be-sinked (local $0 exnref)
1733+
(try
1734+
(do)
1735+
(catch
1736+
;; This whole 'try' body can be sinked to eliminate local.set /
1737+
;; local.get. Even though it contains a pop, it is enclosed within
1738+
;; try-catch, so it is OK.
1739+
(local.set $0
1740+
(try (result exnref)
1741+
(do (ref.null))
1742+
(catch (exnref.pop))
1743+
)
1744+
)
1745+
(call $foo
1746+
(i32.const 3)
1747+
(local.get $0)
1748+
)
1749+
)
1750+
)
1751+
)
17141752
)

0 commit comments

Comments
 (0)