Skip to content

Commit c75f2ae

Browse files
authored
Add a suspends effect (#9070)
This will be necessary for a future optimization that turns resumes of continuations that never suspend into calls. Update the effect analysis of suspends to set the new effect and clobber global state because the suspend handler might do anything before returning. Test that the effects are analyzed as intended and that they work with global effect analysis.
1 parent 180bae3 commit c75f2ae

15 files changed

Lines changed: 541 additions & 29 deletions

scripts/test/wasm2js.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,11 @@ def test_wasm2js_output():
8888
for module, asserts in support.split_wast(t):
8989
support.write_wast('split.wast', module, asserts)
9090

91-
# wasm2js does not yet support EH, and enabling it can reduce
92-
# optimization opportunities
91+
# wasm2js does not yet support EH or stack switching, and
92+
# enabling them can reduce optimization opportunities
9393
cmd = shared.WASM2JS + ['split.wast', '-all',
94-
'--disable-exception-handling']
94+
'--disable-exception-handling',
95+
'--disable-stack-switching']
9596
if opt:
9697
cmd += ['-O']
9798
if 'emscripten' in basename:
@@ -150,7 +151,8 @@ def test_asserts_output():
150151

151152
wasm = os.path.join(shared.get_test_dir('wasm2js'), wasm)
152153
cmd = shared.WASM2JS + [wasm, '--allow-asserts', '-all',
153-
'--disable-exception-handling']
154+
'--disable-exception-handling',
155+
'--disable-stack-switching']
154156
out = support.run_command(cmd)
155157
shared.fail_if_not_identical_to_file(out, asserts_expected_file)
156158

@@ -201,10 +203,11 @@ def update_wasm2js_tests():
201203
for module, asserts in support.split_wast(t):
202204
support.write_wast('split.wast', module, asserts)
203205

204-
# wasm2js does not yet support EH, and enable it can reduce
205-
# optimization opportunities
206+
# wasm2js does not yet support EH or stack switching, and
207+
# enabling them can reduce optimization opportunities
206208
cmd = shared.WASM2JS + ['split.wast', '-all',
207-
'--disable-exception-handling']
209+
'--disable-exception-handling',
210+
'--disable-stack-switching']
208211
if opt:
209212
cmd += ['-O']
210213
if 'emscripten' in basename:
@@ -225,7 +228,7 @@ def update_wasm2js_tests():
225228
asserts_expected_file = os.path.join(shared.options.binaryen_test, 'wasm2js', asserts)
226229
traps_expected_file = os.path.join(shared.options.binaryen_test, 'wasm2js', traps)
227230

228-
cmd = shared.WASM2JS + [os.path.join(shared.get_test_dir('wasm2js'), wasm), '--allow-asserts', '-all', '--disable-exception-handling']
231+
cmd = shared.WASM2JS + [os.path.join(shared.get_test_dir('wasm2js'), wasm), '--allow-asserts', '-all', '--disable-exception-handling', '--disable-stack-switching']
229232
out = support.run_command(cmd)
230233
with open(asserts_expected_file, 'w') as o:
231234
o.write(out)

src/binaryen-c.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6687,6 +6687,10 @@ BinaryenSideEffects BinaryenSideEffectDanglingPop(void) {
66876687
return static_cast<BinaryenSideEffects>(
66886688
EffectAnalyzer::SideEffects::DanglingPop);
66896689
}
6690+
BinaryenSideEffects BinaryenSideEffectSuspends(void) {
6691+
return static_cast<BinaryenSideEffects>(
6692+
EffectAnalyzer::SideEffects::Suspends);
6693+
}
66906694
BinaryenSideEffects BinaryenSideEffectAny(void) {
66916695
return static_cast<BinaryenSideEffects>(EffectAnalyzer::SideEffects::Any);
66926696
}

src/binaryen-c.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3669,6 +3669,7 @@ BINARYEN_API BinaryenSideEffects BinaryenSideEffectTrapsNeverHappen(void);
36693669
BINARYEN_API BinaryenSideEffects BinaryenSideEffectIsAtomic(void);
36703670
BINARYEN_API BinaryenSideEffects BinaryenSideEffectThrows(void);
36713671
BINARYEN_API BinaryenSideEffects BinaryenSideEffectDanglingPop(void);
3672+
BINARYEN_API BinaryenSideEffects BinaryenSideEffectSuspends(void);
36723673
BINARYEN_API BinaryenSideEffects BinaryenSideEffectAny(void);
36733674

36743675
BINARYEN_API BinaryenSideEffects BinaryenExpressionGetSideEffects(

src/ir/effects.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ std::ostream& operator<<(std::ostream& o, const EffectAnalyzer& effects) {
9696
if (effects.throws_) {
9797
o << "throws_\n";
9898
}
99+
if (effects.suspends) {
100+
o << "suspends_\n";
101+
}
99102
if (effects.tryDepth) {
100103
o << "tryDepth\n";
101104
}

src/ir/effects.h

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
#include "ir/intrinsics.h"
2424
#include "pass.h"
2525
#include "support/name.h"
26-
#include "support/utilities.h"
2726
#include "wasm-traversal.h"
2827
#include "wasm-type.h"
2928
#include "wasm.h"
@@ -44,8 +43,8 @@ class EffectAnalyzer {
4443
readsMutableArray(false), writesArray(false),
4544
readsSharedMutableArray(false), writesSharedArray(false), trap(false),
4645
implicitTrap(false), throws_(false), danglingPop(false),
47-
mayNotReturn(false), hasReturnCallThrow(false), module(module),
48-
features(module.features) {}
46+
mayNotReturn(false), hasReturnCallThrow(false), suspends(false),
47+
module(module), features(module.features) {}
4948

5049
EffectAnalyzer(const PassOptions& passOptions,
5150
const Module& module,
@@ -138,6 +137,8 @@ class EffectAnalyzer {
138137
// more here.)
139138
bool hasReturnCallThrow : 1;
140139

140+
bool suspends : 1;
141+
141142
const Module& module;
142143
FeatureSet features;
143144

@@ -228,15 +229,17 @@ class EffectAnalyzer {
228229
return calls || readsSharedMutableArray || writesSharedArray;
229230
}
230231
bool throws() const { return throws_ || !delegateTargets.empty(); }
232+
231233
// Check whether this may transfer control flow to somewhere outside of this
232-
// expression (aside from just flowing out normally). That includes a break
233-
// or a throw (if the throw is not known to be caught inside this expression;
234+
// expression (aside from just flowing out normally). That includes a break,
235+
// a throw (if the throw is not known to be caught inside this expression;
234236
// note that if the throw is not caught in this expression then it might be
235237
// caught in this function but outside of this expression, or it might not be
236238
// caught in the function at all, which would mean control flow cannot be
237-
// transferred inside the function, but this expression does not know that).
239+
// transferred inside the function, but this expression does not know that),
240+
// or a suspension.
238241
bool transfersControlFlow() const {
239-
return branchesOut || throws() || hasExternalBreakTargets();
242+
return branchesOut || suspends || throws() || hasExternalBreakTargets();
240243
}
241244

242245
// Changes something in globally-stored state.
@@ -480,6 +483,7 @@ class EffectAnalyzer {
480483
danglingPop = danglingPop || other.danglingPop;
481484
mayNotReturn = mayNotReturn || other.mayNotReturn;
482485
hasReturnCallThrow = hasReturnCallThrow || other.hasReturnCallThrow;
486+
suspends = suspends || other.suspends;
483487
readOrder = std::max(readOrder, other.readOrder);
484488
writeOrder = std::max(writeOrder, other.writeOrder);
485489

@@ -1271,8 +1275,9 @@ class EffectAnalyzer {
12711275
parent.calls = true;
12721276
}
12731277
void visitSuspend(Suspend* curr) {
1274-
// Similar to resume/call: Suspending means that we execute arbitrary
1275-
// other code before we may resume here.
1278+
// Suspending transfers control to an enclosing handler and executes
1279+
// arbitrary other code before we may resume here.
1280+
parent.suspends = true;
12761281
parent.calls = true;
12771282
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
12781283
parent.throws_ = true;
@@ -1370,6 +1375,11 @@ class EffectAnalyzer {
13701375
parent.throws_ = true;
13711376
}
13721377
}
1378+
// If stack switching is enabled and we don't have global effects
1379+
// information, assume that the call target may suspend.
1380+
if (parent.features.hasStackSwitching()) {
1381+
parent.suspends = true;
1382+
}
13731383
}
13741384
};
13751385

@@ -1407,7 +1417,8 @@ class EffectAnalyzer {
14071417
Throws = 1 << 12,
14081418
DanglingPop = 1 << 13,
14091419
TrapsNeverHappen = 1 << 14,
1410-
Any = (1 << 15) - 1
1420+
Suspends = 1 << 15,
1421+
Any = (1 << 16) - 1
14111422
};
14121423
uint32_t getSideEffects() const {
14131424
uint32_t effects = 0;
@@ -1459,12 +1470,15 @@ class EffectAnalyzer {
14591470
if (danglingPop) {
14601471
effects |= SideEffects::DanglingPop;
14611472
}
1473+
if (suspends) {
1474+
effects |= SideEffects::Suspends;
1475+
}
14621476
return effects;
14631477
}
14641478

1465-
// Ignores all forms of control flow transfers: breaks, returns, and
1466-
// exceptions. (Note that traps are not considered relevant here - a trap does
1467-
// not just transfer control flow, but can be seen as halting the entire
1479+
// Ignores all forms of control flow transfers: breaks, returns, exceptions,
1480+
// and suspensions. (Note that traps are not considered relevant here - a trap
1481+
// does not just transfer control flow, but can be seen as halting the entire
14681482
// program.)
14691483
//
14701484
// This function matches transfersControlFlow(), that is, after calling this
@@ -1474,6 +1488,7 @@ class EffectAnalyzer {
14741488
breakTargets.clear();
14751489
throws_ = false;
14761490
delegateTargets.clear();
1491+
suspends = false;
14771492
assert(!transfersControlFlow());
14781493
}
14791494

src/js/binaryen.js-post.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,7 @@ function initializeConstants() {
666666
'Throws',
667667
'DanglingPop',
668668
'TrapsNeverHappen',
669+
'Suspends',
669670
'Any'
670671
].forEach(name => {
671672
Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name]();

src/passes/GlobalEffects.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,12 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
138138
// below.
139139
funcInfo.effects->calls = false;
140140

141-
// Clear throws as well, as we are "forgetting" calls right now, and
142-
// want to forget their throwing effect as well. If we see something
143-
// else that throws, below, then we'll note that there.
141+
// Clear throws and suspends as well, as we are "forgetting" calls right
142+
// now, and want to forget their throwing and suspending effects as
143+
// well. If we see something else that throws or suspends, below, then
144+
// we'll note that there.
144145
funcInfo.effects->throws_ = false;
146+
funcInfo.effects->suspends = false;
145147

146148
struct CallScanner
147149
: public PostWalker<CallScanner,
@@ -179,12 +181,16 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
179181
assert(options.worldMode == WorldMode::Open);
180182
funcInfo.effects = std::nullopt;
181183
} else {
182-
// No call here, but update throwing if we see it. (Only do so,
183-
// however, if we have effects; if we cleared it - see before -
184-
// then we assume the worst anyhow, and have nothing to update.)
184+
// No call here, but update throwing and suspending if we see it.
185+
// (Only do so, however, if we have effects; if we cleared it -
186+
// see before - then we assume the worst anyhow, and have nothing
187+
// to update.)
185188
if (effects.throws_ && funcInfo.effects) {
186189
funcInfo.effects->throws_ = true;
187190
}
191+
if (effects.suspends && funcInfo.effects) {
192+
funcInfo.effects->suspends = true;
193+
}
188194
}
189195
}
190196
};

test/binaryen.js/sideffects.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ console.log("SideEffects.IsAtomic=" + binaryen.SideEffects.IsAtomic);
1414
console.log("SideEffects.Throws=" + binaryen.SideEffects.Throws);
1515
console.log("SideEffects.DanglingPop=" + binaryen.SideEffects.DanglingPop);
1616
console.log("SideEffects.TrapsNeverHappen=" + binaryen.SideEffects.TrapsNeverHappen);
17+
console.log("SideEffects.Suspends=" + binaryen.SideEffects.Suspends);
1718
console.log("SideEffects.Any=" + binaryen.SideEffects.Any);
1819

1920
var module = new binaryen.Module();
@@ -114,7 +115,7 @@ assert(
114115
);
115116

116117
// If exception handling feature is enabled, calls can throw
117-
module.setFeatures(binaryen.Features.All);
118+
module.setFeatures(binaryen.Features.ExceptionHandling);
118119
assert(
119120
binaryen.getSideEffects(
120121
module.call("test", [], binaryen.i32),
@@ -124,6 +125,28 @@ assert(
124125
(binaryen.SideEffects.Calls | binaryen.SideEffects.Throws)
125126
);
126127

128+
// If stack switching feature is enabled, calls can suspend
129+
module.setFeatures(binaryen.Features.StackSwitching);
130+
assert(
131+
binaryen.getSideEffects(
132+
module.call("test", [], binaryen.i32),
133+
module
134+
)
135+
==
136+
(binaryen.SideEffects.Calls | binaryen.SideEffects.Suspends)
137+
);
138+
139+
// If all features are enabled, calls can throw and suspend
140+
module.setFeatures(binaryen.Features.All);
141+
assert(
142+
binaryen.getSideEffects(
143+
module.call("test", [], binaryen.i32),
144+
module
145+
)
146+
==
147+
(binaryen.SideEffects.Calls | binaryen.SideEffects.Throws | binaryen.SideEffects.Suspends)
148+
);
149+
127150
assert(
128151
binaryen.getSideEffects(
129152
module.drop(module.i32.pop()),

test/binaryen.js/sideffects.js.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ SideEffects.IsAtomic=2048
1414
SideEffects.Throws=4096
1515
SideEffects.DanglingPop=8192
1616
SideEffects.TrapsNeverHappen=16384
17-
SideEffects.Any=32767
17+
SideEffects.Suspends=32768
18+
SideEffects.Any=65535

test/gtest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ set(unittest_SOURCES
1515
delta_debugging.cpp
1616
dfa_minimization.cpp
1717
disjoint_sets.cpp
18+
effects.cpp
1819
graph.cpp
1920
int128.cpp
2021
leaves.cpp

0 commit comments

Comments
 (0)