-
Notifications
You must be signed in to change notification settings - Fork 788
Refactor expression runner so it can be used via the C and JS APIs #2702
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
132cb1d
bbe1dc5
3f3c02d
461576d
f5e3837
2ccc81d
30bd10c
ca3ed47
a853db9
66d23f1
35d09c2
f57cbdc
63d7520
5432156
ec0a93d
1ceb5b5
d145cfc
153d10f
7d51f30
cabf038
ed62e30
803cd22
597c5fa
b4ca977
dcbab6a
4c8fc7c
8586c57
1184f58
17e5de0
84c27ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -155,14 +155,15 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> { | |
// whether it computes down to a concrete value, where it is not necessary | ||
// to preserve side effects like those of a `local.tee`. | ||
DEFAULT = 0, | ||
// Be very careful to preserve any side effects, like those of a | ||
// `local.tee`, for example when we are going to replace the expression | ||
// afterwards. | ||
// Be very careful to preserve any side effects. For example, if we are | ||
// intending to replace the expression with a constant afterwards, even if | ||
// we can technically evaluate down to a constant, we still cannot replace | ||
// the expression if it also sets a local, which must be preserved in this | ||
// scenario so subsequent code keeps functioning. | ||
PRESERVE_SIDEEFFECTS = 1 << 0, | ||
// Traverse through function calls, attempting to compute their concrete | ||
// value. Must not be used in function-parallel scenarios, where the called | ||
// function might or might not have been optimized already to something we | ||
// can traverse successfully, in turn leading to non-deterministic behavior. | ||
// function might be concurrently modified, leading to undefined behavior. | ||
TRAVERSE_CALLS = 1 << 1 | ||
}; | ||
|
||
|
@@ -220,6 +221,9 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> { | |
: module(module), flags(flags), maxDepth(maxDepth), | ||
maxLoopIterations(maxLoopIterations) {} | ||
|
||
struct NonconstantException { | ||
}; // TODO: use a flow with a special name, as this is likely very slow | ||
|
||
// Gets the module this runner is operating on. | ||
Module* getModule() { return module; } | ||
|
||
|
@@ -1256,16 +1260,16 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> { | |
NOTE_ENTER("LocalSet"); | ||
NOTE_EVAL1(curr->index); | ||
if (!(flags & FlagValues::PRESERVE_SIDEEFFECTS)) { | ||
// If we are evaluating and not replacing the expression, see if there is | ||
// a value flowing through a tee. | ||
// If we are evaluating and not replacing the expression, remember the | ||
// constant value set, if any, and see if there is a value flowing through | ||
// a tee. | ||
auto setFlow = visit(curr->value); | ||
dcodeIO marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (curr->type.isConcrete()) { | ||
assert(curr->isTee()); | ||
return setFlow; | ||
} | ||
// Otherwise remember the constant value set, if any, for subsequent gets. | ||
if (!setFlow.breaking()) { | ||
setLocalValue(curr->index, setFlow.values); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't there be subsequent gets if this is a tee, too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, updated the code accordingly |
||
if (curr->type.isConcrete()) { | ||
assert(curr->isTee()); | ||
return setFlow; | ||
} | ||
return Flow(); | ||
} | ||
} | ||
|
@@ -2532,22 +2536,6 @@ class ModuleInstance | |
: ModuleInstanceBase(wasm, externalInterface) {} | ||
}; | ||
|
||
// Expression runner exposed by the C-API | ||
class CExpressionRunner final : public ExpressionRunner<CExpressionRunner> { | ||
public: | ||
CExpressionRunner(Module* module, | ||
CExpressionRunner::Flags flags, | ||
Index maxDepth, | ||
Index maxLoopIterations) | ||
: ExpressionRunner<CExpressionRunner>( | ||
module, flags, maxDepth, maxLoopIterations) {} | ||
|
||
struct NonconstantException { | ||
}; // TODO: use a flow with a special name, as this is likely very slow | ||
|
||
void trap(const char* why) override { throw NonconstantException(); } | ||
}; | ||
|
||
} // namespace wasm | ||
|
||
#endif // wasm_wasm_interpreter_h |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,15 @@ console.log("// ExpressionRunner.Flags.TraverseCalls = " + Flags.TraverseCalls); | |
|
||
binaryen.setAPITracing(true); | ||
|
||
function assertDeepEqual(x, y) { | ||
if (typeof x === "object") { | ||
for (var i in x) assertDeepEqual(x[i], y[i]); | ||
for (i in y) assertDeepEqual(x[i], y[i]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we do |
||
} else { | ||
assert(x === y); | ||
} | ||
} | ||
|
||
var module = new binaryen.Module(); | ||
module.addGlobal("aGlobal", binaryen.i32, true, module.i32.const(0)); | ||
|
||
|
@@ -16,7 +25,14 @@ var expr = runner.runAndDispose( | |
module.i32.const(2) | ||
) | ||
); | ||
assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":3}'); | ||
assertDeepEqual( | ||
binaryen.getExpressionInfo(expr), | ||
{ | ||
id: binaryen.ExpressionIds.Const, | ||
type: binaryen.i32, | ||
value: 3 | ||
} | ||
); | ||
|
||
// Should traverse control structures | ||
runner = new binaryen.ExpressionRunner(module); | ||
|
@@ -30,7 +46,14 @@ expr = runner.runAndDispose( | |
) | ||
), | ||
); | ||
assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":4}'); | ||
assertDeepEqual( | ||
binaryen.getExpressionInfo(expr), | ||
{ | ||
id: binaryen.ExpressionIds.Const, | ||
type: binaryen.i32, | ||
value: 4 | ||
} | ||
); | ||
|
||
// Should be unable to evaluate a local if not explicitly specified | ||
runner = new binaryen.ExpressionRunner(module); | ||
|
@@ -57,7 +80,14 @@ expr = runner.runAndDispose( | |
module.i32.const(1) | ||
) | ||
); | ||
assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":5}'); | ||
assertDeepEqual( | ||
binaryen.getExpressionInfo(expr), | ||
{ | ||
id: binaryen.ExpressionIds.Const, | ||
type: binaryen.i32, | ||
value: 5 | ||
} | ||
); | ||
|
||
// Should preserve any side-effects if explicitly requested | ||
runner = new binaryen.ExpressionRunner(module, Flags.PreserveSideeffects); | ||
|
@@ -83,7 +113,14 @@ expr = runner.runAndDispose( | |
], binaryen.i32) | ||
) | ||
); | ||
assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":6}'); | ||
assertDeepEqual( | ||
binaryen.getExpressionInfo(expr), | ||
{ | ||
id: binaryen.ExpressionIds.Const, | ||
type: binaryen.i32, | ||
value: 6 | ||
} | ||
); | ||
|
||
// Should pick up explicitly preset values | ||
runner = new binaryen.ExpressionRunner(module, Flags.PreserveSideeffects); | ||
|
@@ -95,7 +132,14 @@ expr = runner.runAndDispose( | |
module.global.get("aGlobal", binaryen.i32) | ||
) | ||
); | ||
assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":7}'); | ||
assertDeepEqual( | ||
binaryen.getExpressionInfo(expr), | ||
{ | ||
id: binaryen.ExpressionIds.Const, | ||
type: binaryen.i32, | ||
value: 7 | ||
} | ||
); | ||
|
||
// Should traverse into (simple) functions if requested | ||
runner = new binaryen.ExpressionRunner(module, Flags.TraverseCalls); | ||
|
@@ -120,7 +164,14 @@ expr = runner.runAndDispose( | |
module.local.get(0, binaryen.i32) | ||
) | ||
); | ||
assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":8}'); | ||
assertDeepEqual( | ||
binaryen.getExpressionInfo(expr), | ||
{ | ||
id: binaryen.ExpressionIds.Const, | ||
type: binaryen.i32, | ||
value: 8 | ||
} | ||
); | ||
|
||
// Should not attempt to traverse into functions if not explicitly set | ||
runner = new binaryen.ExpressionRunner(module); | ||
|
@@ -153,4 +204,5 @@ expr = runner.runAndDispose( | |
); | ||
assert(expr === 0); | ||
|
||
module.dispose(); | ||
binaryen.setAPITracing(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be
static
, too. You could get extra fancy by making both of these helpersstatic
variables inside ofnoteExpressionRunner
to limit their scope, but I'll leave that up to you. OTOH, it would probably be better to just say we don't support making more than max size_t expression runners and get rid of all this logic, especially since it is literally impossible to have than many expression runners recorded inexpressionRunners
.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly thinking in terms of a very long lived process using Binaryen, let's say where modules are being created as-a-service. While we can't store max size_t in the structure, we might at some point overflow, where the likely scenario is that this is just fine, yet guarding for not reusing something left over (i.e. from a module created and never disposed) seems like a good precaution to have. Unlikely that someone will do this with tracing enabled, ofc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha, I had missed that the
ExpressionRunner
s were removed from theexpressionRunners
map when they were destroyed 👍