Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions scripts/test/wasm2js.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,11 @@ def test_wasm2js_output():
for module, asserts in support.split_wast(t):
support.write_wast('split.wast', module, asserts)

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

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

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

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

cmd = shared.WASM2JS + [os.path.join(shared.get_test_dir('wasm2js'), wasm), '--allow-asserts', '-all', '--disable-exception-handling']
cmd = shared.WASM2JS + [os.path.join(shared.get_test_dir('wasm2js'), wasm), '--allow-asserts', '-all', '--disable-exception-handling', '--disable-stack-switching']
out = support.run_command(cmd)
with open(asserts_expected_file, 'w') as o:
o.write(out)
Expand Down
4 changes: 4 additions & 0 deletions src/binaryen-c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6687,6 +6687,10 @@ BinaryenSideEffects BinaryenSideEffectDanglingPop(void) {
return static_cast<BinaryenSideEffects>(
EffectAnalyzer::SideEffects::DanglingPop);
}
BinaryenSideEffects BinaryenSideEffectSuspends(void) {
return static_cast<BinaryenSideEffects>(
EffectAnalyzer::SideEffects::Suspends);
}
BinaryenSideEffects BinaryenSideEffectAny(void) {
return static_cast<BinaryenSideEffects>(EffectAnalyzer::SideEffects::Any);
}
Expand Down
1 change: 1 addition & 0 deletions src/binaryen-c.h
Original file line number Diff line number Diff line change
Expand Up @@ -3669,6 +3669,7 @@ BINARYEN_API BinaryenSideEffects BinaryenSideEffectTrapsNeverHappen(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectIsAtomic(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectThrows(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectDanglingPop(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectSuspends(void);
BINARYEN_API BinaryenSideEffects BinaryenSideEffectAny(void);

BINARYEN_API BinaryenSideEffects BinaryenExpressionGetSideEffects(
Expand Down
3 changes: 3 additions & 0 deletions src/ir/effects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ std::ostream& operator<<(std::ostream& o, const EffectAnalyzer& effects) {
if (effects.throws_) {
o << "throws_\n";
}
if (effects.suspends) {
o << "suspends_\n";
}
if (effects.tryDepth) {
o << "tryDepth\n";
}
Expand Down
41 changes: 28 additions & 13 deletions src/ir/effects.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
#include "ir/intrinsics.h"
#include "pass.h"
#include "support/name.h"
#include "support/utilities.h"
#include "wasm-traversal.h"
#include "wasm-type.h"
#include "wasm.h"
Expand All @@ -44,8 +43,8 @@ class EffectAnalyzer {
readsMutableArray(false), writesArray(false),
readsSharedMutableArray(false), writesSharedArray(false), trap(false),
implicitTrap(false), throws_(false), danglingPop(false),
mayNotReturn(false), hasReturnCallThrow(false), module(module),
features(module.features) {}
mayNotReturn(false), hasReturnCallThrow(false), suspends(false),
module(module), features(module.features) {}

EffectAnalyzer(const PassOptions& passOptions,
const Module& module,
Expand Down Expand Up @@ -138,6 +137,8 @@ class EffectAnalyzer {
// more here.)
bool hasReturnCallThrow : 1;

bool suspends : 1;

const Module& module;
FeatureSet features;

Expand Down Expand Up @@ -228,15 +229,17 @@ class EffectAnalyzer {
return calls || readsSharedMutableArray || writesSharedArray;
}
bool throws() const { return throws_ || !delegateTargets.empty(); }

// Check whether this may transfer control flow to somewhere outside of this
// expression (aside from just flowing out normally). That includes a break
// or a throw (if the throw is not known to be caught inside this expression;
// expression (aside from just flowing out normally). That includes a break,
// a throw (if the throw is not known to be caught inside this expression;
// note that if the throw is not caught in this expression then it might be
// caught in this function but outside of this expression, or it might not be
// caught in the function at all, which would mean control flow cannot be
// transferred inside the function, but this expression does not know that).
// transferred inside the function, but this expression does not know that),
// or a suspension.
bool transfersControlFlow() const {
return branchesOut || throws() || hasExternalBreakTargets();
return branchesOut || suspends || throws() || hasExternalBreakTargets();
}

// Changes something in globally-stored state.
Expand Down Expand Up @@ -480,6 +483,7 @@ class EffectAnalyzer {
danglingPop = danglingPop || other.danglingPop;
mayNotReturn = mayNotReturn || other.mayNotReturn;
hasReturnCallThrow = hasReturnCallThrow || other.hasReturnCallThrow;
suspends = suspends || other.suspends;
readOrder = std::max(readOrder, other.readOrder);
writeOrder = std::max(writeOrder, other.writeOrder);

Expand Down Expand Up @@ -1271,8 +1275,9 @@ class EffectAnalyzer {
parent.calls = true;
}
void visitSuspend(Suspend* curr) {
// Similar to resume/call: Suspending means that we execute arbitrary
// other code before we may resume here.
// Suspending transfers control to an enclosing handler and executes
// arbitrary other code before we may resume here.
parent.suspends = true;
parent.calls = true;
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0) {
parent.throws_ = true;
Expand Down Expand Up @@ -1370,6 +1375,11 @@ class EffectAnalyzer {
parent.throws_ = true;
}
}
// If stack switching is enabled and we don't have global effects
// information, assume that the call target may suspend.
if (parent.features.hasStackSwitching()) {
parent.suspends = true;
}
}
};

Expand Down Expand Up @@ -1407,7 +1417,8 @@ class EffectAnalyzer {
Throws = 1 << 12,
DanglingPop = 1 << 13,
TrapsNeverHappen = 1 << 14,
Any = (1 << 15) - 1
Suspends = 1 << 15,
Any = (1 << 16) - 1
};
uint32_t getSideEffects() const {
uint32_t effects = 0;
Expand Down Expand Up @@ -1459,12 +1470,15 @@ class EffectAnalyzer {
if (danglingPop) {
effects |= SideEffects::DanglingPop;
}
if (suspends) {
effects |= SideEffects::Suspends;
}
return effects;
}

// Ignores all forms of control flow transfers: breaks, returns, and
// exceptions. (Note that traps are not considered relevant here - a trap does
// not just transfer control flow, but can be seen as halting the entire
// Ignores all forms of control flow transfers: breaks, returns, exceptions,
// and suspensions. (Note that traps are not considered relevant here - a trap
// does not just transfer control flow, but can be seen as halting the entire
// program.)
//
// This function matches transfersControlFlow(), that is, after calling this
Expand All @@ -1474,6 +1488,7 @@ class EffectAnalyzer {
breakTargets.clear();
throws_ = false;
delegateTargets.clear();
suspends = false;
assert(!transfersControlFlow());
}

Expand Down
1 change: 1 addition & 0 deletions src/js/binaryen.js-post.js
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,7 @@ function initializeConstants() {
'Throws',
'DanglingPop',
'TrapsNeverHappen',
'Suspends',
'Any'
].forEach(name => {
Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name]();
Expand Down
18 changes: 12 additions & 6 deletions src/passes/GlobalEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,12 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
// below.
funcInfo.effects->calls = false;

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

struct CallScanner
: public PostWalker<CallScanner,
Expand Down Expand Up @@ -179,12 +181,16 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
assert(options.worldMode == WorldMode::Open);
funcInfo.effects = std::nullopt;
} else {
// No call here, but update throwing if we see it. (Only do so,
// however, if we have effects; if we cleared it - see before -
// then we assume the worst anyhow, and have nothing to update.)
// No call here, but update throwing and suspending if we see it.
// (Only do so, however, if we have effects; if we cleared it -
// see before - then we assume the worst anyhow, and have nothing
// to update.)
if (effects.throws_ && funcInfo.effects) {
funcInfo.effects->throws_ = true;
}
if (effects.suspends && funcInfo.effects) {
funcInfo.effects->suspends = true;
}
}
}
};
Expand Down
25 changes: 24 additions & 1 deletion test/binaryen.js/sideffects.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ console.log("SideEffects.IsAtomic=" + binaryen.SideEffects.IsAtomic);
console.log("SideEffects.Throws=" + binaryen.SideEffects.Throws);
console.log("SideEffects.DanglingPop=" + binaryen.SideEffects.DanglingPop);
console.log("SideEffects.TrapsNeverHappen=" + binaryen.SideEffects.TrapsNeverHappen);
console.log("SideEffects.Suspends=" + binaryen.SideEffects.Suspends);
console.log("SideEffects.Any=" + binaryen.SideEffects.Any);

var module = new binaryen.Module();
Expand Down Expand Up @@ -114,7 +115,7 @@ assert(
);

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

// If stack switching feature is enabled, calls can suspend
module.setFeatures(binaryen.Features.StackSwitching);
assert(
binaryen.getSideEffects(
module.call("test", [], binaryen.i32),
module
)
==
(binaryen.SideEffects.Calls | binaryen.SideEffects.Suspends)
);

// If all features are enabled, calls can throw and suspend
module.setFeatures(binaryen.Features.All);
assert(
binaryen.getSideEffects(
module.call("test", [], binaryen.i32),
module
)
==
(binaryen.SideEffects.Calls | binaryen.SideEffects.Throws | binaryen.SideEffects.Suspends)
);

assert(
binaryen.getSideEffects(
module.drop(module.i32.pop()),
Expand Down
3 changes: 2 additions & 1 deletion test/binaryen.js/sideffects.js.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ SideEffects.IsAtomic=2048
SideEffects.Throws=4096
SideEffects.DanglingPop=8192
SideEffects.TrapsNeverHappen=16384
SideEffects.Any=32767
SideEffects.Suspends=32768
SideEffects.Any=65535
1 change: 1 addition & 0 deletions test/gtest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ set(unittest_SOURCES
delta_debugging.cpp
dfa_minimization.cpp
disjoint_sets.cpp
effects.cpp
graph.cpp
int128.cpp
leaves.cpp
Expand Down
Loading
Loading