From 132cb1db05a56be097ff8537539b86d6c986a59f Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 18 Mar 2020 17:03:31 +0100 Subject: [PATCH 01/27] Derive standalone expression runner from precompute pass --- src/binaryen-c.cpp | 36 +++++++- src/binaryen-c.h | 22 +++++ src/js/binaryen.js-post.js | 11 +++ src/passes/Precompute.cpp | 108 ++--------------------- src/wasm-interpreter.h | 101 +++++++++++++++++++++ test/binaryen.js/expressionrunner.js | 36 ++++++++ test/binaryen.js/expressionrunner.js.txt | 43 +++++++++ 7 files changed, 254 insertions(+), 103 deletions(-) create mode 100644 test/binaryen.js/expressionrunner.js create mode 100644 test/binaryen.js/expressionrunner.js.txt diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 79202eeaa95..70c05750eaf 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4812,6 +4812,39 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, return BinaryenExpressionRef(ret); } +// +// ========= ExpressionRunner ========= +// + +ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, + BinaryenIndex maxDepth) { + if (tracing) { + std::cout << " the_runner = ExpressionRunnerCreate(the_module, " << maxDepth << ");\n"; + } + auto* wasm = (Module*)module; + GetValues getValues; + return ExpressionRunnerRef( + new StandaloneExpressionRunner(wasm, getValues, false, maxDepth)); +} + +BinaryenExpressionRef +ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, + BinaryenExpressionRef expr) { + if (tracing) { + std::cout << " ExpressionRunnerRunAndDispose(the_runner, expressions[" << expressions[expr] << "]);\n"; + } + auto* R = (StandaloneExpressionRunner*)runner; + auto flow = R->visit(expr); + Expression* ret; + if (flow.breaking() || flow.values.empty()) { + ret = nullptr; + } else { + ret = flow.getConstExpression(*R->getModule()); + } + delete R; + return BinaryenExpressionRef(ret); +} + // // ========= Other APIs ========= // @@ -4832,7 +4865,8 @@ void BinaryenSetAPITracing(int on) { " std::map exports;\n" " std::map relooperBlocks;\n" " BinaryenModuleRef the_module = NULL;\n" - " RelooperRef the_relooper = NULL;\n"; + " RelooperRef the_relooper = NULL;\n" + " ExpressionRunnerRef the_runner = NULL;\n"; } else { std::cout << " return 0;\n"; std::cout << "}\n"; diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 443df56234e..bc288ceded5 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1618,6 +1618,28 @@ BINARYEN_API void RelooperAddBranchForSwitch(RelooperBlockRef from, BINARYEN_API BinaryenExpressionRef RelooperRenderAndDispose( RelooperRef relooper, RelooperBlockRef entry, BinaryenIndex labelHelper); +// +// ========= ExpressionRunner ========== +// + +#ifdef __cplusplus +namespace wasm { +class StandaloneExpressionRunner; +} // namespace wasm +typedef wasm::StandaloneExpressionRunner* ExpressionRunnerRef; +#else +typedef StandaloneExpressionRunner* ExpressionRunnerRef; +#endif + +// Creates an ExpressionRunner instance +BINARYEN_API ExpressionRunnerRef +ExpressionRunnerCreate(BinaryenModuleRef module, BinaryenIndex maxDepth); + +// Runs the expression and returns the constant value expression it evaluates +// to, if any. Otherwise returns `NULL`. Also disposes the runner. +BINARYEN_API BinaryenExpressionRef ExpressionRunnerRunAndDispose( + ExpressionRunnerRef runner, BinaryenExpressionRef expr); + // // ========= Other APIs ========= // diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 39e23633ba1..602da4cbdbb 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -2390,6 +2390,17 @@ Module['Relooper'] = function(module) { }; }; +// 'ExpressionRunner' interface +Module['ExpressionRunner'] = function(module, maxDepth) { + if (typeof maxDepth === "undefined") maxDepth = 50; // default used by precompute + var runner = Module['_ExpressionRunnerCreate'](module['ptr'], maxDepth); + this['ptr'] = runner; + + this['runAndDispose'] = function(expr) { + return Module['_ExpressionRunnerRunAndDispose'](runner, expr); + }; +}; + function getAllNested(ref, numFn, getFn) { var num = numFn(ref); var ret = new Array(num); diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 0a4a691f27c..4c295e6b3f9 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -39,107 +39,11 @@ namespace wasm { -static const Name NOTPRECOMPUTABLE_FLOW("Binaryen|notprecomputable"); - // Limit evaluation depth for 2 reasons: first, it is highly unlikely // that we can do anything useful to precompute a hugely nested expression // (we should succed at smaller parts of it first). Second, a low limit is // helpful to avoid platform differences in native stack sizes. -static const Index MAX_DEPTH = 50; - -typedef std::unordered_map GetValues; - -// Precomputes an expression. Errors if we hit anything that can't be -// precomputed. -class PrecomputingExpressionRunner - : public ExpressionRunner { - Module* module; - - // map gets to constant values, if they are known to be constant - GetValues& getValues; - - // Whether we are trying to precompute down to an expression (which we can do - // on say 5 + 6) or to a value (which we can't do on a local.tee that flows a - // 7 through it). When we want to replace the expression, we can only do so - // when it has no side effects. When we don't care about replacing the - // expression, we just want to know if it will contain a known constant. - bool replaceExpression; - -public: - PrecomputingExpressionRunner(Module* module, - GetValues& getValues, - bool replaceExpression) - : ExpressionRunner(MAX_DEPTH), module(module), - getValues(getValues), replaceExpression(replaceExpression) {} - - struct NonstandaloneException { - }; // TODO: use a flow with a special name, as this is likely very slow - - Flow visitLoop(Loop* curr) { - // loops might be infinite, so must be careful - // but we can't tell if non-infinite, since we don't have state, so loops - // are just impossible to optimize for now - return Flow(NOTPRECOMPUTABLE_FLOW); - } - - Flow visitCall(Call* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitCallIndirect(CallIndirect* curr) { - return Flow(NOTPRECOMPUTABLE_FLOW); - } - Flow visitLocalGet(LocalGet* curr) { - auto iter = getValues.find(curr); - if (iter != getValues.end()) { - auto values = iter->second; - if (values.isConcrete()) { - return Flow(std::move(values)); - } - } - return Flow(NOTPRECOMPUTABLE_FLOW); - } - Flow visitLocalSet(LocalSet* curr) { - // If we don't need to replace the whole expression, see if there - // is a value flowing through a tee. - if (!replaceExpression) { - if (curr->type.isConcrete()) { - assert(curr->isTee()); - return visit(curr->value); - } - } - return Flow(NOTPRECOMPUTABLE_FLOW); - } - Flow visitGlobalGet(GlobalGet* curr) { - auto* global = module->getGlobal(curr->name); - if (!global->imported() && !global->mutable_) { - return visit(global->init); - } - return Flow(NOTPRECOMPUTABLE_FLOW); - } - Flow visitGlobalSet(GlobalSet* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitLoad(Load* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitStore(Store* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitAtomicRMW(AtomicRMW* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { - return Flow(NOTPRECOMPUTABLE_FLOW); - } - Flow visitAtomicWait(AtomicWait* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitAtomicNotify(AtomicNotify* curr) { - return Flow(NOTPRECOMPUTABLE_FLOW); - } - Flow visitSIMDLoad(SIMDLoad* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitMemoryInit(MemoryInit* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitDataDrop(DataDrop* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitMemoryCopy(MemoryCopy* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitMemoryFill(MemoryFill* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitHost(Host* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitTry(Try* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitThrow(Throw* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitRethrow(Rethrow* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitBrOnExn(BrOnExn* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitPush(Push* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - Flow visitPop(Pop* curr) { return Flow(NOTPRECOMPUTABLE_FLOW); } - - void trap(const char* why) override { throw NonstandaloneException(); } -}; +static const Index PRECOMPUTE_MAX_DEPTH = 50; struct Precompute : public WalkerPass< @@ -192,7 +96,7 @@ struct Precompute return; } if (flow.breaking()) { - if (flow.breakTo == NOTPRECOMPUTABLE_FLOW) { + if (flow.breakTo == NONSTANDALONE_FLOW) { return; } if (flow.breakTo == RETURN_FLOW) { @@ -268,11 +172,11 @@ struct Precompute // (that we can replace the expression with if replaceExpression is set). Flow precomputeExpression(Expression* curr, bool replaceExpression = true) { try { - return PrecomputingExpressionRunner( - getModule(), getValues, replaceExpression) + return StandaloneExpressionRunner( + getModule(), getValues, replaceExpression, PRECOMPUTE_MAX_DEPTH) .visit(curr); - } catch (PrecomputingExpressionRunner::NonstandaloneException&) { - return Flow(NOTPRECOMPUTABLE_FLOW); + } catch (StandaloneExpressionRunner::NonstandaloneException&) { + return Flow(NONSTANDALONE_FLOW); } } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5051385a2c5..6b2685fcdad 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2228,6 +2228,107 @@ class ModuleInstance : ModuleInstanceBase(wasm, externalInterface) {} }; +static const Name NONSTANDALONE_FLOW("Binaryen|nonstandalone"); + +typedef std::unordered_map GetValues; + +// Evaluates a standalone expression. Errors if we hit anything that can't be +// evaluated. +class StandaloneExpressionRunner + : public ExpressionRunner { + Module* module; + + // map gets to constant values, if they are known to be constant + GetValues& getValues; + + // Whether we are trying to precompute down to an expression (which we can do + // on say 5 + 6) or to a value (which we can't do on a local.tee that flows a + // 7 through it). When we want to replace the expression, we can only do so + // when it has no side effects. When we don't care about replacing the + // expression, we just want to know if it will contain a known constant. + bool replaceExpression; + +public: + StandaloneExpressionRunner(Module* module, + GetValues& getValues, + bool replaceExpression, + Index maxDepth) + : ExpressionRunner(maxDepth), module(module), + getValues(getValues), replaceExpression(replaceExpression) {} + + virtual ~StandaloneExpressionRunner() {} + + struct NonstandaloneException { + }; // TODO: use a flow with a special name, as this is likely very slow + + Module* getModule() { return module; } + + Flow visitLoop(Loop* curr) { + // loops might be infinite, so must be careful + // but we can't tell if non-infinite, since we don't have state, so loops + // are just impossible to optimize for now + return Flow(NONSTANDALONE_FLOW); + } + + Flow visitCall(Call* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitCallIndirect(CallIndirect* curr) { + return Flow(NONSTANDALONE_FLOW); + } + Flow visitLocalGet(LocalGet* curr) { + auto iter = getValues.find(curr); + if (iter != getValues.end()) { + auto values = iter->second; + if (values.isConcrete()) { + return Flow(std::move(values)); + } + } + return Flow(NONSTANDALONE_FLOW); + } + Flow visitLocalSet(LocalSet* curr) { + // If we don't need to replace the whole expression, see if there + // is a value flowing through a tee. + if (!replaceExpression) { + if (curr->type.isConcrete()) { + assert(curr->isTee()); + return visit(curr->value); + } + } + return Flow(NONSTANDALONE_FLOW); + } + Flow visitGlobalGet(GlobalGet* curr) { + auto* global = module->getGlobal(curr->name); + if (!global->imported() && !global->mutable_) { + return visit(global->init); + } + return Flow(NONSTANDALONE_FLOW); + } + Flow visitGlobalSet(GlobalSet* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitLoad(Load* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitStore(Store* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitAtomicRMW(AtomicRMW* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { + return Flow(NONSTANDALONE_FLOW); + } + Flow visitAtomicWait(AtomicWait* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitAtomicNotify(AtomicNotify* curr) { + return Flow(NONSTANDALONE_FLOW); + } + Flow visitSIMDLoad(SIMDLoad* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitMemoryInit(MemoryInit* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitDataDrop(DataDrop* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitMemoryCopy(MemoryCopy* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitMemoryFill(MemoryFill* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitHost(Host* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitTry(Try* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitThrow(Throw* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitRethrow(Rethrow* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitBrOnExn(BrOnExn* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitPush(Push* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitPop(Pop* curr) { return Flow(NONSTANDALONE_FLOW); } + + void trap(const char* why) override { throw NonstandaloneException(); } +}; + } // namespace wasm #endif // wasm_wasm_interpreter_h diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js new file mode 100644 index 00000000000..79d5ce8fff5 --- /dev/null +++ b/test/binaryen.js/expressionrunner.js @@ -0,0 +1,36 @@ +binaryen.setAPITracing(true); + +var module = new binaryen.Module(); + +var runner = new binaryen.ExpressionRunner(module); +var expr = runner.runAndDispose( + module.i32.add( + module.i32.const(1), + module.i32.const(2) + ) +); +assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":3}'); + +runner = new binaryen.ExpressionRunner(module); +expr = runner.runAndDispose( + module.i32.add( + module.i32.const(1), + module.if( + module.i32.const(0), + module.i32.const(0), + module.i32.const(3) + ) + ), +); +assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":4}'); + +runner = new binaryen.ExpressionRunner(module); +expr = runner.runAndDispose( + module.i32.add( + module.local.get(0, binaryen.i32), + module.i32.const(1) + ) +); +assert(expr === 0); + +binaryen.setAPITracing(false); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt new file mode 100644 index 00000000000..85c20d341e6 --- /dev/null +++ b/test/binaryen.js/expressionrunner.js.txt @@ -0,0 +1,43 @@ +// beginning a Binaryen API trace +#include +#include +#include "binaryen-c.h" +int main() { + std::map expressions; + std::map functions; + std::map globals; + std::map events; + std::map exports; + std::map relooperBlocks; + BinaryenModuleRef the_module = NULL; + RelooperRef the_relooper = NULL; + ExpressionRunnerRef the_runner = NULL; + the_module = BinaryenModuleCreate(); + expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); + the_runner = ExpressionRunnerCreate(the_module, 50); + expressions[1] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[2] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); + expressions[3] = BinaryenBinary(the_module, 0, expressions[1], expressions[2]); + ExpressionRunnerRunAndDispose(the_runner, expressions[3]); + BinaryenExpressionGetId(expressions[0]); + BinaryenExpressionGetType(expressions[0]); + BinaryenConstGetValueI32(expressions[0]); + the_runner = ExpressionRunnerCreate(the_module, 50); + expressions[5] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[6] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); + expressions[7] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); + expressions[8] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); + expressions[9] = BinaryenIf(the_module, expressions[6], expressions[7], expressions[8]); + expressions[10] = BinaryenBinary(the_module, 0, expressions[5], expressions[9]); + ExpressionRunnerRunAndDispose(the_runner, expressions[10]); + BinaryenExpressionGetId(expressions[0]); + BinaryenExpressionGetType(expressions[0]); + BinaryenConstGetValueI32(expressions[0]); + the_runner = ExpressionRunnerCreate(the_module, 50); + expressions[12] = BinaryenLocalGet(the_module, 0, 2); + expressions[13] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[14] = BinaryenBinary(the_module, 0, expressions[12], expressions[13]); + ExpressionRunnerRunAndDispose(the_runner, expressions[14]); + return 0; +} +// ending a Binaryen API trace From bbe1dc5c6dd73943791968d1fb6f5bc3758580f3 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 18 Mar 2020 17:24:36 +0100 Subject: [PATCH 02/27] handle traps, format --- src/binaryen-c.cpp | 21 ++++++++++++--------- src/passes/Precompute.cpp | 4 ++-- test/binaryen.js/expressionrunner.js | 6 ++++++ test/binaryen.js/expressionrunner.js.txt | 3 +++ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 70c05750eaf..d9572a9b385 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4819,7 +4819,8 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, BinaryenIndex maxDepth) { if (tracing) { - std::cout << " the_runner = ExpressionRunnerCreate(the_module, " << maxDepth << ");\n"; + std::cout << " the_runner = ExpressionRunnerCreate(the_module, " + << maxDepth << ");\n"; } auto* wasm = (Module*)module; GetValues getValues; @@ -4831,18 +4832,20 @@ BinaryenExpressionRef ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, BinaryenExpressionRef expr) { if (tracing) { - std::cout << " ExpressionRunnerRunAndDispose(the_runner, expressions[" << expressions[expr] << "]);\n"; + std::cout << " ExpressionRunnerRunAndDispose(the_runner, expressions[" + << expressions[expr] << "]);\n"; } auto* R = (StandaloneExpressionRunner*)runner; - auto flow = R->visit(expr); - Expression* ret; - if (flow.breaking() || flow.values.empty()) { - ret = nullptr; - } else { - ret = flow.getConstExpression(*R->getModule()); + Expression* ret = nullptr; + try { + auto flow = R->visit(expr); + if (!flow.breaking() && !flow.values.empty()) { + ret = flow.getConstExpression(*R->getModule()); + } + } catch (StandaloneExpressionRunner::NonstandaloneException&) { } delete R; - return BinaryenExpressionRef(ret); + return ret; } // diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 4c295e6b3f9..37a878279a9 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -43,7 +43,7 @@ namespace wasm { // that we can do anything useful to precompute a hugely nested expression // (we should succed at smaller parts of it first). Second, a low limit is // helpful to avoid platform differences in native stack sizes. -static const Index PRECOMPUTE_MAX_DEPTH = 50; +static const Index MAX_DEPTH = 50; struct Precompute : public WalkerPass< @@ -173,7 +173,7 @@ struct Precompute Flow precomputeExpression(Expression* curr, bool replaceExpression = true) { try { return StandaloneExpressionRunner( - getModule(), getValues, replaceExpression, PRECOMPUTE_MAX_DEPTH) + getModule(), getValues, replaceExpression, MAX_DEPTH) .visit(curr); } catch (StandaloneExpressionRunner::NonstandaloneException&) { return Flow(NONSTANDALONE_FLOW); diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index 79d5ce8fff5..a1e764a0416 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -33,4 +33,10 @@ expr = runner.runAndDispose( ); assert(expr === 0); +runner = new binaryen.ExpressionRunner(module); +expr = runner.runAndDispose( + module.unreachable() +); +assert(expr === 0); + binaryen.setAPITracing(false); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index 85c20d341e6..ebf8fc493bb 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -38,6 +38,9 @@ int main() { expressions[13] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[14] = BinaryenBinary(the_module, 0, expressions[12], expressions[13]); ExpressionRunnerRunAndDispose(the_runner, expressions[14]); + the_runner = ExpressionRunnerCreate(the_module, 50); + expressions[15] = BinaryenUnreachable(the_module); + ExpressionRunnerRunAndDispose(the_runner, expressions[15]); return 0; } // ending a Binaryen API trace From 3f3c02d7f39c938c742398619e469f5d2d22bab1 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 18 Mar 2020 18:13:40 +0100 Subject: [PATCH 03/27] fix? --- src/binaryen-c.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/binaryen-c.h b/src/binaryen-c.h index bc288ceded5..7eecb1404f5 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1626,9 +1626,9 @@ BINARYEN_API BinaryenExpressionRef RelooperRenderAndDispose( namespace wasm { class StandaloneExpressionRunner; } // namespace wasm -typedef wasm::StandaloneExpressionRunner* ExpressionRunnerRef; +typedef class wasm::StandaloneExpressionRunner* ExpressionRunnerRef; #else -typedef StandaloneExpressionRunner* ExpressionRunnerRef; +typedef struct StandaloneExpressionRunner* ExpressionRunnerRef; #endif // Creates an ExpressionRunner instance From 461576dcd6937f487583f310855ee7bdb0a4ab74 Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 18 Mar 2020 19:28:48 +0100 Subject: [PATCH 04/27] update tests --- test/binaryen.js/custom-section.js.txt | 1 + test/binaryen.js/inlining-options.js.txt | 1 + test/binaryen.js/kitchen-sink.js.txt | 1 + test/binaryen.js/low-memory-unused.js.txt | 1 + test/binaryen.js/pass-arguments.js.txt | 1 + test/example/c-api-kitchen-sink.txt | 1 + 6 files changed, 6 insertions(+) diff --git a/test/binaryen.js/custom-section.js.txt b/test/binaryen.js/custom-section.js.txt index e4e8e85d6a9..5543ee69e9c 100644 --- a/test/binaryen.js/custom-section.js.txt +++ b/test/binaryen.js/custom-section.js.txt @@ -11,6 +11,7 @@ int main() { std::map relooperBlocks; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; + ExpressionRunnerRef the_runner = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); { diff --git a/test/binaryen.js/inlining-options.js.txt b/test/binaryen.js/inlining-options.js.txt index 4993fd0d611..6adbd5583db 100644 --- a/test/binaryen.js/inlining-options.js.txt +++ b/test/binaryen.js/inlining-options.js.txt @@ -11,6 +11,7 @@ int main() { std::map relooperBlocks; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; + ExpressionRunnerRef the_runner = NULL; BinaryenGetAlwaysInlineMaxSize(); // alwaysInlineMaxSize=2 BinaryenSetAlwaysInlineMaxSize(11); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 66a4d518576..a52fc420bb6 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -11,6 +11,7 @@ int main() { std::map relooperBlocks; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; + ExpressionRunnerRef the_runner = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); BinaryenAddEvent(the_module, "a-event", 0, 2, 0); diff --git a/test/binaryen.js/low-memory-unused.js.txt b/test/binaryen.js/low-memory-unused.js.txt index 13850128ca3..17058bbff78 100644 --- a/test/binaryen.js/low-memory-unused.js.txt +++ b/test/binaryen.js/low-memory-unused.js.txt @@ -55,6 +55,7 @@ int main() { std::map relooperBlocks; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; + ExpressionRunnerRef the_runner = NULL; BinaryenSetLowMemoryUnused(1); BinaryenGetLowMemoryUnused(); return 0; diff --git a/test/binaryen.js/pass-arguments.js.txt b/test/binaryen.js/pass-arguments.js.txt index f877b22488f..084189c21fb 100644 --- a/test/binaryen.js/pass-arguments.js.txt +++ b/test/binaryen.js/pass-arguments.js.txt @@ -11,6 +11,7 @@ int main() { std::map relooperBlocks; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; + ExpressionRunnerRef the_runner = NULL; BinaryenGetPassArgument("theKey"); BinaryenSetPassArgument("theKey", "theValue"); BinaryenGetPassArgument("theKey"); diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 7dd24c1ebe6..af6d0bee632 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -11,6 +11,7 @@ int main() { std::map relooperBlocks; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; + ExpressionRunnerRef the_runner = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); expressions[1] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); From f5e383740f253f67cb4e215884454bcbed0d37c8 Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 19 Mar 2020 17:16:59 +0100 Subject: [PATCH 05/27] refactor replaceExpresion to an enum --- src/binaryen-c.cpp | 17 +++++++++--- src/binaryen-c.h | 12 ++++++++- src/js/binaryen.js-post.js | 10 +++++-- src/passes/Precompute.cpp | 14 ++++++---- src/wasm-interpreter.h | 27 ++++++++++++------- test/binaryen.js/expressionrunner.js | 33 +++++++++++++++++++++--- test/binaryen.js/expressionrunner.js.txt | 23 ++++++++++++++--- 7 files changed, 106 insertions(+), 30 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index d9572a9b385..5cd2008153a 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4816,16 +4816,25 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, // ========= ExpressionRunner ========= // +ExpressionRunnerIntent ExpressionRunnerIntentEvaluate() { + return StandaloneExpressionRunner::Intent::EVALUATE; +} + +ExpressionRunnerIntent ExpressionRunnerIntentReplaceExpression() { + return StandaloneExpressionRunner::Intent::REPLACE_EXPRESSION; +} + ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, + ExpressionRunnerIntent intent, BinaryenIndex maxDepth) { if (tracing) { - std::cout << " the_runner = ExpressionRunnerCreate(the_module, " - << maxDepth << ");\n"; + std::cout << " the_runner = ExpressionRunnerCreate(the_module, " << intent + << ", " << maxDepth << ");\n"; } auto* wasm = (Module*)module; GetValues getValues; - return ExpressionRunnerRef( - new StandaloneExpressionRunner(wasm, getValues, false, maxDepth)); + return ExpressionRunnerRef(new StandaloneExpressionRunner( + wasm, getValues, StandaloneExpressionRunner::Intent(intent), maxDepth)); } BinaryenExpressionRef diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 7eecb1404f5..f97e2c3b81c 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1631,9 +1631,19 @@ typedef class wasm::StandaloneExpressionRunner* ExpressionRunnerRef; typedef struct StandaloneExpressionRunner* ExpressionRunnerRef; #endif +typedef uint32_t ExpressionRunnerIntent; + +// Intent is to evaluate the expression, so we can ignore some side effects. +BINARYEN_API ExpressionRunnerIntent ExpressionRunnerIntentEvaluate(); + +// Intent is to replace the expression, so side effects must be retained. +BINARYEN_API ExpressionRunnerIntent ExpressionRunnerIntentReplaceExpression(); + // Creates an ExpressionRunner instance BINARYEN_API ExpressionRunnerRef -ExpressionRunnerCreate(BinaryenModuleRef module, BinaryenIndex maxDepth); +ExpressionRunnerCreate(BinaryenModuleRef module, + ExpressionRunnerIntent intent, + BinaryenIndex maxDepth); // Runs the expression and returns the constant value expression it evaluates // to, if any. Otherwise returns `NULL`. Also disposes the runner. diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 602da4cbdbb..c81f5f3f61b 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -477,6 +477,12 @@ function initializeConstants() { ].forEach(function(name) { Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name](); }); + + // ExpressionRunner intents + Module['ExpressionRunner']['Intent'] = { + 'Evaluate': Module['_ExpressionRunnerIntentEvaluate'](), + 'ReplaceExpression': Module['_ExpressionRunnerIntentReplaceExpression']() + }; } // 'Module' interface @@ -2391,9 +2397,9 @@ Module['Relooper'] = function(module) { }; // 'ExpressionRunner' interface -Module['ExpressionRunner'] = function(module, maxDepth) { +Module['ExpressionRunner'] = function(module, intent, maxDepth) { if (typeof maxDepth === "undefined") maxDepth = 50; // default used by precompute - var runner = Module['_ExpressionRunnerCreate'](module['ptr'], maxDepth); + var runner = Module['_ExpressionRunnerCreate'](module['ptr'], intent, maxDepth); this['ptr'] = runner; this['runAndDispose'] = function(expr) { diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 37a878279a9..c8f553565ba 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -170,10 +170,13 @@ struct Precompute private: // Precompute an expression, returning a flow, which may be a constant // (that we can replace the expression with if replaceExpression is set). - Flow precomputeExpression(Expression* curr, bool replaceExpression = true) { + Flow precomputeExpression( + Expression* curr, + StandaloneExpressionRunner::Intent intent = + StandaloneExpressionRunner::Intent::REPLACE_EXPRESSION) { try { return StandaloneExpressionRunner( - getModule(), getValues, replaceExpression, MAX_DEPTH) + getModule(), getValues, intent, MAX_DEPTH) .visit(curr); } catch (StandaloneExpressionRunner::NonstandaloneException&) { return Flow(NONSTANDALONE_FLOW); @@ -188,9 +191,10 @@ struct Precompute // will have value 1 which we can optimize here, but in precomputeExpression // we could not do anything. Literals precomputeValue(Expression* curr) { - // Note that we set replaceExpression to false, as we just care about - // the value here. - Flow flow = precomputeExpression(curr, false /* replaceExpression */); + // Note that we do not intent to replace the expression, as we just care + // about the value here. + Flow flow = + precomputeExpression(curr, StandaloneExpressionRunner::Intent::EVALUATE); if (flow.breaking()) { return {}; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6b2685fcdad..482072e8175 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2241,20 +2241,27 @@ class StandaloneExpressionRunner // map gets to constant values, if they are known to be constant GetValues& getValues; - // Whether we are trying to precompute down to an expression (which we can do - // on say 5 + 6) or to a value (which we can't do on a local.tee that flows a - // 7 through it). When we want to replace the expression, we can only do so - // when it has no side effects. When we don't care about replacing the - // expression, we just want to know if it will contain a known constant. - bool replaceExpression; - public: + // Whether we are trying to precompute down to an expression (which we can + // do on say 5 + 6) or to a value (which we can't do on a local.tee that + // flows a 7 through it). When we want to replace the expression, we can + // only do so when it has no side effects. When we don't care about + // replacing the expression, we just want to know if it will contain a known + // constant. + enum Intent { + // Intent is to evaluate the expression, so we can ignore some side effects + EVALUATE, + // Intent is to replace the expression, so side effects must be retained + REPLACE_EXPRESSION + }; + Intent intent; + StandaloneExpressionRunner(Module* module, GetValues& getValues, - bool replaceExpression, + Intent intent, Index maxDepth) : ExpressionRunner(maxDepth), module(module), - getValues(getValues), replaceExpression(replaceExpression) {} + getValues(getValues), intent(intent) {} virtual ~StandaloneExpressionRunner() {} @@ -2287,7 +2294,7 @@ class StandaloneExpressionRunner Flow visitLocalSet(LocalSet* curr) { // If we don't need to replace the whole expression, see if there // is a value flowing through a tee. - if (!replaceExpression) { + if (intent == Intent::EVALUATE) { if (curr->type.isConcrete()) { assert(curr->isTee()); return visit(curr->value); diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index a1e764a0416..04dd1d6d847 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -1,8 +1,10 @@ binaryen.setAPITracing(true); var module = new binaryen.Module(); +var Intent = binaryen.ExpressionRunner.Intent; -var runner = new binaryen.ExpressionRunner(module); +// Should evaluate down to a constant +var runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); var expr = runner.runAndDispose( module.i32.add( module.i32.const(1), @@ -11,7 +13,8 @@ var expr = runner.runAndDispose( ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":3}'); -runner = new binaryen.ExpressionRunner(module); +// Should traverse control structures +runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); expr = runner.runAndDispose( module.i32.add( module.i32.const(1), @@ -24,7 +27,8 @@ expr = runner.runAndDispose( ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":4}'); -runner = new binaryen.ExpressionRunner(module); +// Should be unable to evaluate a local +runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); expr = runner.runAndDispose( module.i32.add( module.local.get(0, binaryen.i32), @@ -33,10 +37,31 @@ expr = runner.runAndDispose( ); assert(expr === 0); -runner = new binaryen.ExpressionRunner(module); +// Should handle traps +runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); expr = runner.runAndDispose( module.unreachable() ); assert(expr === 0); +// Should ignore some side-effects if the intent is to evaluate the expression +runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); +expr = runner.runAndDispose( + module.i32.add( + module.local.tee(0, module.i32.const(4), binaryen.i32), + module.i32.const(1) + ) +); +assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":5}'); + +// Should keep side-effects if the intent is to replace the expression +runner = new binaryen.ExpressionRunner(module, Intent.ReplaceExpression); +expr = runner.runAndDispose( + module.i32.add( + module.local.tee(0, module.i32.const(4), binaryen.i32), + module.i32.const(1) + ) +); +assert(expr === 0); + binaryen.setAPITracing(false); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index ebf8fc493bb..e57875c4b53 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -14,7 +14,7 @@ int main() { ExpressionRunnerRef the_runner = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); - the_runner = ExpressionRunnerCreate(the_module, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 50); expressions[1] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[2] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); expressions[3] = BinaryenBinary(the_module, 0, expressions[1], expressions[2]); @@ -22,7 +22,7 @@ int main() { BinaryenExpressionGetId(expressions[0]); BinaryenExpressionGetType(expressions[0]); BinaryenConstGetValueI32(expressions[0]); - the_runner = ExpressionRunnerCreate(the_module, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 50); expressions[5] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[6] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); expressions[7] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); @@ -33,14 +33,29 @@ int main() { BinaryenExpressionGetId(expressions[0]); BinaryenExpressionGetType(expressions[0]); BinaryenConstGetValueI32(expressions[0]); - the_runner = ExpressionRunnerCreate(the_module, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 50); expressions[12] = BinaryenLocalGet(the_module, 0, 2); expressions[13] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[14] = BinaryenBinary(the_module, 0, expressions[12], expressions[13]); ExpressionRunnerRunAndDispose(the_runner, expressions[14]); - the_runner = ExpressionRunnerCreate(the_module, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 50); expressions[15] = BinaryenUnreachable(the_module); ExpressionRunnerRunAndDispose(the_runner, expressions[15]); + the_runner = ExpressionRunnerCreate(the_module, 0, 50); + expressions[16] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + expressions[17] = BinaryenLocalTee(the_module, 0, expressions[16], 2); + expressions[18] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[19] = BinaryenBinary(the_module, 0, expressions[17], expressions[18]); + ExpressionRunnerRunAndDispose(the_runner, expressions[19]); + BinaryenExpressionGetId(expressions[0]); + BinaryenExpressionGetType(expressions[0]); + BinaryenConstGetValueI32(expressions[0]); + the_runner = ExpressionRunnerCreate(the_module, 1, 50); + expressions[21] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + expressions[22] = BinaryenLocalTee(the_module, 0, expressions[21], 2); + expressions[23] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[24] = BinaryenBinary(the_module, 0, expressions[22], expressions[23]); + ExpressionRunnerRunAndDispose(the_runner, expressions[24]); return 0; } // ending a Binaryen API trace From 2ccc81db5a901120493d62c89b777c7bc8d960aa Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 20 Mar 2020 00:22:40 +0100 Subject: [PATCH 06/27] fix expressions[0] in tracing, track temporary local values --- src/binaryen-c.cpp | 16 +++++++--- src/wasm-interpreter.h | 19 ++++++++++++ test/binaryen.js/expressionrunner.js | 13 ++++++++ test/binaryen.js/expressionrunner.js.txt | 38 ++++++++++++++++-------- 4 files changed, 70 insertions(+), 16 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 5cd2008153a..9b27135b808 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4840,10 +4840,6 @@ ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, BinaryenExpressionRef ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, BinaryenExpressionRef expr) { - if (tracing) { - std::cout << " ExpressionRunnerRunAndDispose(the_runner, expressions[" - << expressions[expr] << "]);\n"; - } auto* R = (StandaloneExpressionRunner*)runner; Expression* ret = nullptr; try { @@ -4853,6 +4849,18 @@ ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, } } catch (StandaloneExpressionRunner::NonstandaloneException&) { } + + if (tracing) { + if (ret != nullptr) { + auto id = noteExpression(ret); + std::cout << " expressions[" << id << "] = "; + } else { + std::cout << " "; + } + std::cout << "ExpressionRunnerRunAndDispose(the_runner, expressions[" + << expressions[expr] << "]);\n"; + } + delete R; return ret; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 482072e8175..6624634e928 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2231,6 +2231,7 @@ class ModuleInstance static const Name NONSTANDALONE_FLOW("Binaryen|nonstandalone"); typedef std::unordered_map GetValues; +typedef std::unordered_map SetValues; // Evaluates a standalone expression. Errors if we hit anything that can't be // evaluated. @@ -2240,6 +2241,8 @@ class StandaloneExpressionRunner // map gets to constant values, if they are known to be constant GetValues& getValues; + // map local indexes to set expressions, keeping track of temporary values + SetValues setValues; public: // Whether we are trying to precompute down to an expression (which we can @@ -2282,6 +2285,7 @@ class StandaloneExpressionRunner return Flow(NONSTANDALONE_FLOW); } Flow visitLocalGet(LocalGet* curr) { + // Check if we already know the exact constant value auto iter = getValues.find(curr); if (iter != getValues.end()) { auto values = iter->second; @@ -2289,6 +2293,11 @@ class StandaloneExpressionRunner return Flow(std::move(values)); } } + // Otherwise check if a constant value has been set herein + auto iter2 = setValues.find(curr->index); + if (iter2 != setValues.end()) { + return Flow(std::move(iter2->second)); + } return Flow(NONSTANDALONE_FLOW); } Flow visitLocalSet(LocalSet* curr) { @@ -2299,6 +2308,16 @@ class StandaloneExpressionRunner assert(curr->isTee()); return visit(curr->value); } + // If the set value is constant, remember it for subsequent gets + auto setFlow = visit(curr->value); + if (!setFlow.breaking()) { + auto values = setFlow.values; + if (values.isConcrete()) { + setValues[curr->index] = std::move(values); + return Flow(); + } + } + setValues.erase(curr->index); } return Flow(NONSTANDALONE_FLOW); } diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index 04dd1d6d847..d2232853604 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -64,4 +64,17 @@ expr = runner.runAndDispose( ); assert(expr === 0); +// Should work with temporary locals if the intent is to evaluate the expression +runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); +expr = runner.runAndDispose( + module.i32.add( + module.block(null, [ + module.local.set(0, module.i32.const(5)), + module.local.get(0, binaryen.i32) + ], binaryen.i32), + module.i32.const(1) + ) +); +assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":6}'); + binaryen.setAPITracing(false); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index e57875c4b53..485b6c89411 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -18,10 +18,10 @@ int main() { expressions[1] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[2] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); expressions[3] = BinaryenBinary(the_module, 0, expressions[1], expressions[2]); - ExpressionRunnerRunAndDispose(the_runner, expressions[3]); - BinaryenExpressionGetId(expressions[0]); - BinaryenExpressionGetType(expressions[0]); - BinaryenConstGetValueI32(expressions[0]); + expressions[4] = ExpressionRunnerRunAndDispose(the_runner, expressions[3]); + BinaryenExpressionGetId(expressions[4]); + BinaryenExpressionGetType(expressions[4]); + BinaryenConstGetValueI32(expressions[4]); the_runner = ExpressionRunnerCreate(the_module, 0, 50); expressions[5] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[6] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); @@ -29,10 +29,10 @@ int main() { expressions[8] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); expressions[9] = BinaryenIf(the_module, expressions[6], expressions[7], expressions[8]); expressions[10] = BinaryenBinary(the_module, 0, expressions[5], expressions[9]); - ExpressionRunnerRunAndDispose(the_runner, expressions[10]); - BinaryenExpressionGetId(expressions[0]); - BinaryenExpressionGetType(expressions[0]); - BinaryenConstGetValueI32(expressions[0]); + expressions[11] = ExpressionRunnerRunAndDispose(the_runner, expressions[10]); + BinaryenExpressionGetId(expressions[11]); + BinaryenExpressionGetType(expressions[11]); + BinaryenConstGetValueI32(expressions[11]); the_runner = ExpressionRunnerCreate(the_module, 0, 50); expressions[12] = BinaryenLocalGet(the_module, 0, 2); expressions[13] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); @@ -46,16 +46,30 @@ int main() { expressions[17] = BinaryenLocalTee(the_module, 0, expressions[16], 2); expressions[18] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[19] = BinaryenBinary(the_module, 0, expressions[17], expressions[18]); - ExpressionRunnerRunAndDispose(the_runner, expressions[19]); - BinaryenExpressionGetId(expressions[0]); - BinaryenExpressionGetType(expressions[0]); - BinaryenConstGetValueI32(expressions[0]); + expressions[20] = ExpressionRunnerRunAndDispose(the_runner, expressions[19]); + BinaryenExpressionGetId(expressions[20]); + BinaryenExpressionGetType(expressions[20]); + BinaryenConstGetValueI32(expressions[20]); the_runner = ExpressionRunnerCreate(the_module, 1, 50); expressions[21] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); expressions[22] = BinaryenLocalTee(the_module, 0, expressions[21], 2); expressions[23] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[24] = BinaryenBinary(the_module, 0, expressions[22], expressions[23]); ExpressionRunnerRunAndDispose(the_runner, expressions[24]); + the_runner = ExpressionRunnerCreate(the_module, 0, 50); + expressions[25] = BinaryenConst(the_module, BinaryenLiteralInt32(5)); + expressions[26] = BinaryenLocalSet(the_module, 0, expressions[25]); + expressions[27] = BinaryenLocalGet(the_module, 0, 2); + { + BinaryenExpressionRef children[] = { expressions[26], expressions[27] }; + expressions[28] = BinaryenBlock(the_module, NULL, children, 2, 2); + } + expressions[29] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[30] = BinaryenBinary(the_module, 0, expressions[28], expressions[29]); + expressions[31] = ExpressionRunnerRunAndDispose(the_runner, expressions[30]); + BinaryenExpressionGetId(expressions[31]); + BinaryenExpressionGetType(expressions[31]); + BinaryenConstGetValueI32(expressions[31]); return 0; } // ending a Binaryen API trace From 30bd10c102c703a8b1209ff189fd138cc3d9f246 Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 20 Mar 2020 14:08:05 +0100 Subject: [PATCH 07/27] simplify --- src/binaryen-c.cpp | 4 ++-- src/js/binaryen.js-post.js | 2 +- src/passes/Precompute.cpp | 7 +++---- src/wasm-interpreter.h | 13 +++++++------ test/binaryen.js/expressionrunner.js | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 9b27135b808..60eb2280ffa 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4820,8 +4820,8 @@ ExpressionRunnerIntent ExpressionRunnerIntentEvaluate() { return StandaloneExpressionRunner::Intent::EVALUATE; } -ExpressionRunnerIntent ExpressionRunnerIntentReplaceExpression() { - return StandaloneExpressionRunner::Intent::REPLACE_EXPRESSION; +ExpressionRunnerIntent ExpressionRunnerIntentReplace() { + return StandaloneExpressionRunner::Intent::REPLACE; } ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index c81f5f3f61b..c61455ac738 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -481,7 +481,7 @@ function initializeConstants() { // ExpressionRunner intents Module['ExpressionRunner']['Intent'] = { 'Evaluate': Module['_ExpressionRunnerIntentEvaluate'](), - 'ReplaceExpression': Module['_ExpressionRunnerIntentReplaceExpression']() + 'Replace': Module['_ExpressionRunnerIntentReplace']() }; } diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index c8f553565ba..1bca951da1a 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -170,10 +170,9 @@ struct Precompute private: // Precompute an expression, returning a flow, which may be a constant // (that we can replace the expression with if replaceExpression is set). - Flow precomputeExpression( - Expression* curr, - StandaloneExpressionRunner::Intent intent = - StandaloneExpressionRunner::Intent::REPLACE_EXPRESSION) { + Flow precomputeExpression(Expression* curr, + StandaloneExpressionRunner::Intent intent = + StandaloneExpressionRunner::Intent::REPLACE) { try { return StandaloneExpressionRunner( getModule(), getValues, intent, MAX_DEPTH) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6624634e928..9aa67c5a1d7 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2255,7 +2255,7 @@ class StandaloneExpressionRunner // Intent is to evaluate the expression, so we can ignore some side effects EVALUATE, // Intent is to replace the expression, so side effects must be retained - REPLACE_EXPRESSION + REPLACE }; Intent intent; @@ -2285,7 +2285,7 @@ class StandaloneExpressionRunner return Flow(NONSTANDALONE_FLOW); } Flow visitLocalGet(LocalGet* curr) { - // Check if we already know the exact constant value + // Check if we already know the constant value from pass context. auto iter = getValues.find(curr); if (iter != getValues.end()) { auto values = iter->second; @@ -2293,7 +2293,8 @@ class StandaloneExpressionRunner return Flow(std::move(values)); } } - // Otherwise check if a constant value has been set herein + // Otherwise check if a constant value has been set in the context of this + // runner. auto iter2 = setValues.find(curr->index); if (iter2 != setValues.end()) { return Flow(std::move(iter2->second)); @@ -2301,14 +2302,14 @@ class StandaloneExpressionRunner return Flow(NONSTANDALONE_FLOW); } Flow visitLocalSet(LocalSet* curr) { - // If we don't need to replace the whole expression, see if there - // is a value flowing through a tee. if (intent == Intent::EVALUATE) { + // If we are evaluating and not replacing the expression, see if there is + // a value flowing through a tee. if (curr->type.isConcrete()) { assert(curr->isTee()); return visit(curr->value); } - // If the set value is constant, remember it for subsequent gets + // Otherwise remember the constant value, if any, for subsequent gets. auto setFlow = visit(curr->value); if (!setFlow.breaking()) { auto values = setFlow.values; diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index d2232853604..f6e01f117e7 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -55,7 +55,7 @@ expr = runner.runAndDispose( assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":5}'); // Should keep side-effects if the intent is to replace the expression -runner = new binaryen.ExpressionRunner(module, Intent.ReplaceExpression); +runner = new binaryen.ExpressionRunner(module, Intent.Replace); expr = runner.runAndDispose( module.i32.add( module.local.tee(0, module.i32.const(4), binaryen.i32), From ca3ed4722124a909cfe89f97db5b959cc6d69a4f Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 20 Mar 2020 15:17:15 +0100 Subject: [PATCH 08/27] implement preset local/global values --- src/binaryen-c.cpp | 25 +++++ src/binaryen-c.h | 12 ++- src/js/binaryen.js-post.js | 8 ++ src/wasm-interpreter.h | 78 ++++++++++++---- test/binaryen.js/expressionrunner.js | 22 ++++- test/binaryen.js/expressionrunner.js.txt | 112 +++++++++++++---------- 6 files changed, 190 insertions(+), 67 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 60eb2280ffa..dab65fa21b5 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4837,6 +4837,31 @@ ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, wasm, getValues, StandaloneExpressionRunner::Intent(intent), maxDepth)); } +int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, + BinaryenIndex index, + BinaryenExpressionRef value) { + if (tracing) { + std::cout << " ExpressionRunnerSetLocalValue(the_runner, " + << index << ", expressions[" << expressions[value] << "]);\n"; + } + + auto* R = (StandaloneExpressionRunner*)runner; + return R->setLocalValue(index, value); +} + +int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, + const char* name, + BinaryenExpressionRef value) { + if (tracing) { + std::cout << " ExpressionRunnerSetGlobalValue(the_runner, "; + traceNameOrNULL(name); + std::cout << ", expressions[" << expressions[value] << "]);\n"; + } + + auto* R = (StandaloneExpressionRunner*)runner; + return R->setGlobalValue(name, value); +} + BinaryenExpressionRef ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, BinaryenExpressionRef expr) { diff --git a/src/binaryen-c.h b/src/binaryen-c.h index f97e2c3b81c..a953f1f43ee 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1637,7 +1637,7 @@ typedef uint32_t ExpressionRunnerIntent; BINARYEN_API ExpressionRunnerIntent ExpressionRunnerIntentEvaluate(); // Intent is to replace the expression, so side effects must be retained. -BINARYEN_API ExpressionRunnerIntent ExpressionRunnerIntentReplaceExpression(); +BINARYEN_API ExpressionRunnerIntent ExpressionRunnerIntentReplace(); // Creates an ExpressionRunner instance BINARYEN_API ExpressionRunnerRef @@ -1645,6 +1645,16 @@ ExpressionRunnerCreate(BinaryenModuleRef module, ExpressionRunnerIntent intent, BinaryenIndex maxDepth); +// Sets the value of a local. +BINARYEN_API int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, + BinaryenIndex index, + BinaryenExpressionRef value); + +// Sets the value of a global. +BINARYEN_API int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, + const char* name, + BinaryenExpressionRef value); + // Runs the expression and returns the constant value expression it evaluates // to, if any. Otherwise returns `NULL`. Also disposes the runner. BINARYEN_API BinaryenExpressionRef ExpressionRunnerRunAndDispose( diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index c61455ac738..39504f3c6ee 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -2402,6 +2402,14 @@ Module['ExpressionRunner'] = function(module, intent, maxDepth) { var runner = Module['_ExpressionRunnerCreate'](module['ptr'], intent, maxDepth); this['ptr'] = runner; + this['setLocalValue'] = function(index, valueExpr) { + return Boolean(Module['_ExpressionRunnerSetLocalValue'](runner, index, valueExpr)); + }; + this['setGlobalValue'] = function(name, valueExpr) { + return preserveStack(function() { + return Boolean(Module['_ExpressionRunnerSetGlobalValue'](runner, strToStack(name), valueExpr)); + }); + }; this['runAndDispose'] = function(expr) { return Module['_ExpressionRunnerRunAndDispose'](runner, expr); }; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 9aa67c5a1d7..5f8e7b29aab 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2231,7 +2231,6 @@ class ModuleInstance static const Name NONSTANDALONE_FLOW("Binaryen|nonstandalone"); typedef std::unordered_map GetValues; -typedef std::unordered_map SetValues; // Evaluates a standalone expression. Errors if we hit anything that can't be // evaluated. @@ -2241,8 +2240,10 @@ class StandaloneExpressionRunner // map gets to constant values, if they are known to be constant GetValues& getValues; - // map local indexes to set expressions, keeping track of temporary values - SetValues setValues; + // map local indexes to values set in the context of this runner + std::unordered_map setLocalValues; + // map global names to values set in the context of this runner + std::unordered_map setGlobalValues; public: // Whether we are trying to precompute down to an expression (which we can @@ -2273,6 +2274,40 @@ class StandaloneExpressionRunner Module* getModule() { return module; } + bool setLocalValue(Index index, Literals values) { + if (values.isConcrete()) { + setLocalValues[index] = std::move(values); + return true; + } + setLocalValues.erase(index); + return false; + } + + bool setLocalValue(Index index, Expression* expr) { + auto setFlow = visit(expr); + if (!setFlow.breaking()) { + return setLocalValue(index, setFlow.values); + } + return false; + } + + bool setGlobalValue(Name name, Literals values) { + if (values.isConcrete()) { + setGlobalValues[name] = std::move(values); + return true; + } + setGlobalValues.erase(name); + return false; + } + + bool setGlobalValue(Name name, Expression* expr) { + auto setFlow = visit(expr); + if (!setFlow.breaking()) { + return setGlobalValue(name, setFlow.values); + } + return false; + } + Flow visitLoop(Loop* curr) { // loops might be infinite, so must be careful // but we can't tell if non-infinite, since we don't have state, so loops @@ -2293,10 +2328,9 @@ class StandaloneExpressionRunner return Flow(std::move(values)); } } - // Otherwise check if a constant value has been set in the context of this - // runner. - auto iter2 = setValues.find(curr->index); - if (iter2 != setValues.end()) { + // Check if a constant value has been set in the context of this runner. + auto iter2 = setLocalValues.find(curr->index); + if (iter2 != setLocalValues.end()) { return Flow(std::move(iter2->second)); } return Flow(NONSTANDALONE_FLOW); @@ -2309,27 +2343,37 @@ class StandaloneExpressionRunner assert(curr->isTee()); return visit(curr->value); } - // Otherwise remember the constant value, if any, for subsequent gets. - auto setFlow = visit(curr->value); - if (!setFlow.breaking()) { - auto values = setFlow.values; - if (values.isConcrete()) { - setValues[curr->index] = std::move(values); - return Flow(); - } + // Otherwise remember the constant value set, if any, for subsequent gets. + if (setLocalValue(curr->index, curr->value)) { + return Flow(); } - setValues.erase(curr->index); } return Flow(NONSTANDALONE_FLOW); } Flow visitGlobalGet(GlobalGet* curr) { auto* global = module->getGlobal(curr->name); + // Check if the global has an immutable value anyway if (!global->imported() && !global->mutable_) { return visit(global->init); } + // Check if a constant value has been set in the context of this runner. + auto iter = setGlobalValues.find(curr->name); + if (iter != setGlobalValues.end()) { + return Flow(std::move(iter->second)); + } + return Flow(NONSTANDALONE_FLOW); + } + Flow visitGlobalSet(GlobalSet* curr) { + if (intent == Intent::EVALUATE) { + // If we are evaluating and not replacing the expression, remember the + // constant value set, if any, for subsequent gets. + assert(module->getGlobal(curr->name)->mutable_); + if (setGlobalValue(curr->name, curr->value)) { + return Flow(); + } + } return Flow(NONSTANDALONE_FLOW); } - Flow visitGlobalSet(GlobalSet* curr) { return Flow(NONSTANDALONE_FLOW); } Flow visitLoad(Load* curr) { return Flow(NONSTANDALONE_FLOW); } Flow visitStore(Store* curr) { return Flow(NONSTANDALONE_FLOW); } Flow visitAtomicRMW(AtomicRMW* curr) { return Flow(NONSTANDALONE_FLOW); } diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index f6e01f117e7..7bf4393e1e3 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -1,6 +1,7 @@ binaryen.setAPITracing(true); var module = new binaryen.Module(); +module.addGlobal("aGlobal", binaryen.i32, true, module.i32.const(0)); var Intent = binaryen.ExpressionRunner.Intent; // Should evaluate down to a constant @@ -64,17 +65,32 @@ expr = runner.runAndDispose( ); assert(expr === 0); -// Should work with temporary locals if the intent is to evaluate the expression +// Should work with temporary values if the intent is to evaluate the expression runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); expr = runner.runAndDispose( module.i32.add( module.block(null, [ - module.local.set(0, module.i32.const(5)), + module.local.set(0, module.i32.const(2)), module.local.get(0, binaryen.i32) ], binaryen.i32), - module.i32.const(1) + module.block(null, [ + module.global.set("aGlobal", module.i32.const(4)), + module.global.get("aGlobal", binaryen.i32) + ], binaryen.i32) ) ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":6}'); +// Should pick up explicitly preset values +runner = new binaryen.ExpressionRunner(module, Intent.Replace); +assert(runner.setLocalValue(0, module.i32.const(3))); +assert(runner.setGlobalValue("aGlobal", module.i32.const(4))); +expr = runner.runAndDispose( + module.i32.add( + module.local.get(0, binaryen.i32), + module.global.get("aGlobal", binaryen.i32) + ) +); +assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":7}'); + binaryen.setAPITracing(false); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index 485b6c89411..d86d98addc8 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -14,62 +14,82 @@ int main() { ExpressionRunnerRef the_runner = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); + expressions[1] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); + globals[0] = BinaryenAddGlobal(the_module, "aGlobal", 2, 1, expressions[1]); the_runner = ExpressionRunnerCreate(the_module, 0, 50); - expressions[1] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); - expressions[2] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); - expressions[3] = BinaryenBinary(the_module, 0, expressions[1], expressions[2]); - expressions[4] = ExpressionRunnerRunAndDispose(the_runner, expressions[3]); - BinaryenExpressionGetId(expressions[4]); - BinaryenExpressionGetType(expressions[4]); - BinaryenConstGetValueI32(expressions[4]); + expressions[2] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[3] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); + expressions[4] = BinaryenBinary(the_module, 0, expressions[2], expressions[3]); + expressions[5] = ExpressionRunnerRunAndDispose(the_runner, expressions[4]); + BinaryenExpressionGetId(expressions[5]); + BinaryenExpressionGetType(expressions[5]); + BinaryenConstGetValueI32(expressions[5]); the_runner = ExpressionRunnerCreate(the_module, 0, 50); - expressions[5] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); - expressions[6] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); + expressions[6] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[7] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); - expressions[8] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); - expressions[9] = BinaryenIf(the_module, expressions[6], expressions[7], expressions[8]); - expressions[10] = BinaryenBinary(the_module, 0, expressions[5], expressions[9]); - expressions[11] = ExpressionRunnerRunAndDispose(the_runner, expressions[10]); - BinaryenExpressionGetId(expressions[11]); - BinaryenExpressionGetType(expressions[11]); - BinaryenConstGetValueI32(expressions[11]); + expressions[8] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); + expressions[9] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); + expressions[10] = BinaryenIf(the_module, expressions[7], expressions[8], expressions[9]); + expressions[11] = BinaryenBinary(the_module, 0, expressions[6], expressions[10]); + expressions[12] = ExpressionRunnerRunAndDispose(the_runner, expressions[11]); + BinaryenExpressionGetId(expressions[12]); + BinaryenExpressionGetType(expressions[12]); + BinaryenConstGetValueI32(expressions[12]); the_runner = ExpressionRunnerCreate(the_module, 0, 50); - expressions[12] = BinaryenLocalGet(the_module, 0, 2); - expressions[13] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); - expressions[14] = BinaryenBinary(the_module, 0, expressions[12], expressions[13]); - ExpressionRunnerRunAndDispose(the_runner, expressions[14]); - the_runner = ExpressionRunnerCreate(the_module, 0, 50); - expressions[15] = BinaryenUnreachable(the_module); + expressions[13] = BinaryenLocalGet(the_module, 0, 2); + expressions[14] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[15] = BinaryenBinary(the_module, 0, expressions[13], expressions[14]); ExpressionRunnerRunAndDispose(the_runner, expressions[15]); the_runner = ExpressionRunnerCreate(the_module, 0, 50); - expressions[16] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); - expressions[17] = BinaryenLocalTee(the_module, 0, expressions[16], 2); - expressions[18] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); - expressions[19] = BinaryenBinary(the_module, 0, expressions[17], expressions[18]); - expressions[20] = ExpressionRunnerRunAndDispose(the_runner, expressions[19]); - BinaryenExpressionGetId(expressions[20]); - BinaryenExpressionGetType(expressions[20]); - BinaryenConstGetValueI32(expressions[20]); + expressions[16] = BinaryenUnreachable(the_module); + ExpressionRunnerRunAndDispose(the_runner, expressions[16]); + the_runner = ExpressionRunnerCreate(the_module, 0, 50); + expressions[17] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + expressions[18] = BinaryenLocalTee(the_module, 0, expressions[17], 2); + expressions[19] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[20] = BinaryenBinary(the_module, 0, expressions[18], expressions[19]); + expressions[21] = ExpressionRunnerRunAndDispose(the_runner, expressions[20]); + BinaryenExpressionGetId(expressions[21]); + BinaryenExpressionGetType(expressions[21]); + BinaryenConstGetValueI32(expressions[21]); the_runner = ExpressionRunnerCreate(the_module, 1, 50); - expressions[21] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); - expressions[22] = BinaryenLocalTee(the_module, 0, expressions[21], 2); - expressions[23] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); - expressions[24] = BinaryenBinary(the_module, 0, expressions[22], expressions[23]); - ExpressionRunnerRunAndDispose(the_runner, expressions[24]); + expressions[22] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + expressions[23] = BinaryenLocalTee(the_module, 0, expressions[22], 2); + expressions[24] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[25] = BinaryenBinary(the_module, 0, expressions[23], expressions[24]); + ExpressionRunnerRunAndDispose(the_runner, expressions[25]); the_runner = ExpressionRunnerCreate(the_module, 0, 50); - expressions[25] = BinaryenConst(the_module, BinaryenLiteralInt32(5)); - expressions[26] = BinaryenLocalSet(the_module, 0, expressions[25]); - expressions[27] = BinaryenLocalGet(the_module, 0, 2); + expressions[26] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); + expressions[27] = BinaryenLocalSet(the_module, 0, expressions[26]); + expressions[28] = BinaryenLocalGet(the_module, 0, 2); { - BinaryenExpressionRef children[] = { expressions[26], expressions[27] }; - expressions[28] = BinaryenBlock(the_module, NULL, children, 2, 2); + BinaryenExpressionRef children[] = { expressions[27], expressions[28] }; + expressions[29] = BinaryenBlock(the_module, NULL, children, 2, 2); } - expressions[29] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); - expressions[30] = BinaryenBinary(the_module, 0, expressions[28], expressions[29]); - expressions[31] = ExpressionRunnerRunAndDispose(the_runner, expressions[30]); - BinaryenExpressionGetId(expressions[31]); - BinaryenExpressionGetType(expressions[31]); - BinaryenConstGetValueI32(expressions[31]); + expressions[30] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + expressions[31] = BinaryenGlobalSet(the_module, "aGlobal", expressions[30]); + expressions[32] = BinaryenGlobalGet(the_module, "aGlobal", 2); + { + BinaryenExpressionRef children[] = { expressions[31], expressions[32] }; + expressions[33] = BinaryenBlock(the_module, NULL, children, 2, 2); + } + expressions[34] = BinaryenBinary(the_module, 0, expressions[29], expressions[33]); + expressions[35] = ExpressionRunnerRunAndDispose(the_runner, expressions[34]); + BinaryenExpressionGetId(expressions[35]); + BinaryenExpressionGetType(expressions[35]); + BinaryenConstGetValueI32(expressions[35]); + the_runner = ExpressionRunnerCreate(the_module, 1, 50); + expressions[36] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); + ExpressionRunnerSetLocalValue(the_runner, 0, expressions[36]); + expressions[37] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + ExpressionRunnerSetGlobalValue(the_runner, "aGlobal", expressions[37]); + expressions[38] = BinaryenLocalGet(the_module, 0, 2); + expressions[39] = BinaryenGlobalGet(the_module, "aGlobal", 2); + expressions[40] = BinaryenBinary(the_module, 0, expressions[38], expressions[39]); + expressions[41] = ExpressionRunnerRunAndDispose(the_runner, expressions[40]); + BinaryenExpressionGetId(expressions[41]); + BinaryenExpressionGetType(expressions[41]); + BinaryenConstGetValueI32(expressions[41]); return 0; } // ending a Binaryen API trace From a853db906fa65cc4fb9bed30d38f2e01a0c8b646 Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 20 Mar 2020 15:48:37 +0100 Subject: [PATCH 09/27] could need some format on save --- src/binaryen-c.cpp | 4 ++-- src/binaryen-c.h | 6 ++++-- src/wasm-interpreter.h | 12 ++++++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index dab65fa21b5..e1a5d07e7fa 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4841,8 +4841,8 @@ int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, BinaryenIndex index, BinaryenExpressionRef value) { if (tracing) { - std::cout << " ExpressionRunnerSetLocalValue(the_runner, " - << index << ", expressions[" << expressions[value] << "]);\n"; + std::cout << " ExpressionRunnerSetLocalValue(the_runner, " << index + << ", expressions[" << expressions[value] << "]);\n"; } auto* R = (StandaloneExpressionRunner*)runner; diff --git a/src/binaryen-c.h b/src/binaryen-c.h index a953f1f43ee..3cbdc607124 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1645,12 +1645,14 @@ ExpressionRunnerCreate(BinaryenModuleRef module, ExpressionRunnerIntent intent, BinaryenIndex maxDepth); -// Sets the value of a local. +// Sets a known local value to use. Order matters if expressions have side +// effects. Returns `true` if the expression actually evaluates to a constant. BINARYEN_API int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, BinaryenIndex index, BinaryenExpressionRef value); -// Sets the value of a global. +// Sets a known global value to use. Order matters if expressions have side +// effects. Returns `true` if the expression actually evaluates to a constant. BINARYEN_API int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, const char* name, BinaryenExpressionRef value); diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5f8e7b29aab..3d258a66032 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2274,6 +2274,8 @@ class StandaloneExpressionRunner Module* getModule() { return module; } + // Sets a known local value to use. Returns `true` if the value is actually + // constant. bool setLocalValue(Index index, Literals values) { if (values.isConcrete()) { setLocalValues[index] = std::move(values); @@ -2283,14 +2285,18 @@ class StandaloneExpressionRunner return false; } + // Sets a known local value to use. Order matters if expressions have side + // effects. Returns `true` if the expression actually evaluates to a constant. bool setLocalValue(Index index, Expression* expr) { auto setFlow = visit(expr); if (!setFlow.breaking()) { - return setLocalValue(index, setFlow.values); + return setLocalValue(index, std::move(setFlow.values)); } return false; } + // Sets a known global value to use. Returns `true` if the value is actually + // constant. bool setGlobalValue(Name name, Literals values) { if (values.isConcrete()) { setGlobalValues[name] = std::move(values); @@ -2300,10 +2306,12 @@ class StandaloneExpressionRunner return false; } + // Sets a known global value to use. Order matters if expressions have side + // effects. Returns `true` if the expression actually evaluates to a constant. bool setGlobalValue(Name name, Expression* expr) { auto setFlow = visit(expr); if (!setFlow.breaking()) { - return setGlobalValue(name, setFlow.values); + return setGlobalValue(name, std::move(setFlow.values)); } return false; } From 66d23f1c9e03fca6f12232fcce3979965406151c Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 20 Mar 2020 18:24:47 +0100 Subject: [PATCH 10/27] address comments --- src/binaryen-c.cpp | 25 ++--- src/binaryen-c.h | 24 ++-- src/js/binaryen.js-post.js | 12 +- src/passes/Precompute.cpp | 22 ++-- src/wasm-interpreter.h | 159 ++++++++++++++------------- test/binaryen.js/expressionrunner.js | 28 ++--- 6 files changed, 140 insertions(+), 130 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index e1a5d07e7fa..eef4776051d 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4816,25 +4816,24 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, // ========= ExpressionRunner ========= // -ExpressionRunnerIntent ExpressionRunnerIntentEvaluate() { - return StandaloneExpressionRunner::Intent::EVALUATE; +ExpressionRunnerMode ExpressionRunnerModeEvaluate() { + return ContextAwareExpressionRunner::Mode::EVALUATE; } -ExpressionRunnerIntent ExpressionRunnerIntentReplace() { - return StandaloneExpressionRunner::Intent::REPLACE; +ExpressionRunnerMode ExpressionRunnerModeReplace() { + return ContextAwareExpressionRunner::Mode::REPLACE; } ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, - ExpressionRunnerIntent intent, + ExpressionRunnerMode mode, BinaryenIndex maxDepth) { if (tracing) { - std::cout << " the_runner = ExpressionRunnerCreate(the_module, " << intent + std::cout << " the_runner = ExpressionRunnerCreate(the_module, " << mode << ", " << maxDepth << ");\n"; } auto* wasm = (Module*)module; - GetValues getValues; - return ExpressionRunnerRef(new StandaloneExpressionRunner( - wasm, getValues, StandaloneExpressionRunner::Intent(intent), maxDepth)); + return ExpressionRunnerRef(new ContextAwareExpressionRunner( + wasm, ContextAwareExpressionRunner::Mode(mode), maxDepth)); } int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, @@ -4845,7 +4844,7 @@ int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, << ", expressions[" << expressions[value] << "]);\n"; } - auto* R = (StandaloneExpressionRunner*)runner; + auto* R = (ContextAwareExpressionRunner*)runner; return R->setLocalValue(index, value); } @@ -4858,21 +4857,21 @@ int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, std::cout << ", expressions[" << expressions[value] << "]);\n"; } - auto* R = (StandaloneExpressionRunner*)runner; + auto* R = (ContextAwareExpressionRunner*)runner; return R->setGlobalValue(name, value); } BinaryenExpressionRef ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, BinaryenExpressionRef expr) { - auto* R = (StandaloneExpressionRunner*)runner; + auto* R = (ContextAwareExpressionRunner*)runner; Expression* ret = nullptr; try { auto flow = R->visit(expr); if (!flow.breaking() && !flow.values.empty()) { ret = flow.getConstExpression(*R->getModule()); } - } catch (StandaloneExpressionRunner::NonstandaloneException&) { + } catch (ContextAwareExpressionRunner::NonconstantException&) { } if (tracing) { diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 3cbdc607124..0199ffdfd7d 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1624,26 +1624,26 @@ BINARYEN_API BinaryenExpressionRef RelooperRenderAndDispose( #ifdef __cplusplus namespace wasm { -class StandaloneExpressionRunner; +class ContextAwareExpressionRunner; } // namespace wasm -typedef class wasm::StandaloneExpressionRunner* ExpressionRunnerRef; +typedef class wasm::ContextAwareExpressionRunner* ExpressionRunnerRef; #else -typedef struct StandaloneExpressionRunner* ExpressionRunnerRef; +typedef struct ContextAwareExpressionRunner* ExpressionRunnerRef; #endif -typedef uint32_t ExpressionRunnerIntent; +typedef uint32_t ExpressionRunnerMode; -// Intent is to evaluate the expression, so we can ignore some side effects. -BINARYEN_API ExpressionRunnerIntent ExpressionRunnerIntentEvaluate(); +// Just evaluate the expression, so we can ignore some side effects like those +// of a `local.tee`, but not others like traps. +BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeEvaluate(); -// Intent is to replace the expression, so side effects must be retained. -BINARYEN_API ExpressionRunnerIntent ExpressionRunnerIntentReplace(); +// We are going to replace the expression afterwards, so side effects including +// those of `local.tee`s for example must be retained. +BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeReplace(); // Creates an ExpressionRunner instance -BINARYEN_API ExpressionRunnerRef -ExpressionRunnerCreate(BinaryenModuleRef module, - ExpressionRunnerIntent intent, - BinaryenIndex maxDepth); +BINARYEN_API ExpressionRunnerRef ExpressionRunnerCreate( + BinaryenModuleRef module, ExpressionRunnerMode mode, BinaryenIndex maxDepth); // Sets a known local value to use. Order matters if expressions have side // effects. Returns `true` if the expression actually evaluates to a constant. diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 39504f3c6ee..ab4deea7307 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -478,10 +478,10 @@ function initializeConstants() { Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name](); }); - // ExpressionRunner intents - Module['ExpressionRunner']['Intent'] = { - 'Evaluate': Module['_ExpressionRunnerIntentEvaluate'](), - 'Replace': Module['_ExpressionRunnerIntentReplace']() + // ExpressionRunner modes + Module['ExpressionRunner']['Mode'] = { + 'Evaluate': Module['_ExpressionRunnerModeEvaluate'](), + 'Replace': Module['_ExpressionRunnerModeReplace']() }; } @@ -2397,9 +2397,9 @@ Module['Relooper'] = function(module) { }; // 'ExpressionRunner' interface -Module['ExpressionRunner'] = function(module, intent, maxDepth) { +Module['ExpressionRunner'] = function(module, mode, maxDepth) { if (typeof maxDepth === "undefined") maxDepth = 50; // default used by precompute - var runner = Module['_ExpressionRunnerCreate'](module['ptr'], intent, maxDepth); + var runner = Module['_ExpressionRunnerCreate'](module['ptr'], mode, maxDepth); this['ptr'] = runner; this['setLocalValue'] = function(index, valueExpr) { diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 1bca951da1a..4cb9f2639c7 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -39,6 +39,10 @@ namespace wasm { +// Special name also used by ContextAwareExpressionRunner to indicate a +// non-constant flow. +static const Name NONCONSTANT_FLOW("Binaryen|nonconstant"); + // Limit evaluation depth for 2 reasons: first, it is highly unlikely // that we can do anything useful to precompute a hugely nested expression // (we should succed at smaller parts of it first). Second, a low limit is @@ -56,7 +60,7 @@ struct Precompute Precompute(bool propagate) : propagate(propagate) {} - GetValues getValues; + ContextAwareExpressionRunner::GetValues getValues; bool worked; @@ -96,7 +100,7 @@ struct Precompute return; } if (flow.breaking()) { - if (flow.breakTo == NONSTANDALONE_FLOW) { + if (flow.breakTo == NONCONSTANT_FLOW) { return; } if (flow.breakTo == RETURN_FLOW) { @@ -171,14 +175,14 @@ struct Precompute // Precompute an expression, returning a flow, which may be a constant // (that we can replace the expression with if replaceExpression is set). Flow precomputeExpression(Expression* curr, - StandaloneExpressionRunner::Intent intent = - StandaloneExpressionRunner::Intent::REPLACE) { + ContextAwareExpressionRunner::Mode mode = + ContextAwareExpressionRunner::Mode::REPLACE) { try { - return StandaloneExpressionRunner( - getModule(), getValues, intent, MAX_DEPTH) + return ContextAwareExpressionRunner( + getModule(), mode, MAX_DEPTH, &getValues) .visit(curr); - } catch (StandaloneExpressionRunner::NonstandaloneException&) { - return Flow(NONSTANDALONE_FLOW); + } catch (ContextAwareExpressionRunner::NonconstantException&) { + return Flow(NONCONSTANT_FLOW); } } @@ -193,7 +197,7 @@ struct Precompute // Note that we do not intent to replace the expression, as we just care // about the value here. Flow flow = - precomputeExpression(curr, StandaloneExpressionRunner::Intent::EVALUATE); + precomputeExpression(curr, ContextAwareExpressionRunner::Mode::EVALUATE); if (flow.breaking()) { return {}; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 3d258a66032..abe2d4ba27e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2228,57 +2228,67 @@ class ModuleInstance : ModuleInstanceBase(wasm, externalInterface) {} }; -static const Name NONSTANDALONE_FLOW("Binaryen|nonstandalone"); - -typedef std::unordered_map GetValues; - -// Evaluates a standalone expression. Errors if we hit anything that can't be -// evaluated. -class StandaloneExpressionRunner - : public ExpressionRunner { +// Evaluates an expression given its surrounding context. Errors if we hit +// anything that can't be evaluated down to a constant. +class ContextAwareExpressionRunner + : public ExpressionRunner { Module* module; - // map gets to constant values, if they are known to be constant - GetValues& getValues; - // map local indexes to values set in the context of this runner - std::unordered_map setLocalValues; - // map global names to values set in the context of this runner - std::unordered_map setGlobalValues; - public: + // map of `local.get`s to their respective values. + typedef std::unordered_map GetValues; + // Whether we are trying to precompute down to an expression (which we can // do on say 5 + 6) or to a value (which we can't do on a local.tee that // flows a 7 through it). When we want to replace the expression, we can // only do so when it has no side effects. When we don't care about // replacing the expression, we just want to know if it will contain a known // constant. - enum Intent { - // Intent is to evaluate the expression, so we can ignore some side effects + enum Mode { + // Just evaluate the expression, so we can ignore some side effects like + // those of a `local.tee`, but not others like traps. EVALUATE, - // Intent is to replace the expression, so side effects must be retained + // We are going to replace the expression afterwards, so side effects + // including those of `local.tee`s for example must be retained. REPLACE }; - Intent intent; - - StandaloneExpressionRunner(Module* module, - GetValues& getValues, - Intent intent, - Index maxDepth) - : ExpressionRunner(maxDepth), module(module), - getValues(getValues), intent(intent) {} - virtual ~StandaloneExpressionRunner() {} + // special name indicating a flow not evaluating to a constant + const Name NONCONSTANT_FLOW = "Binaryen|nonconstant"; - struct NonstandaloneException { + // special exception indicating a flow not evaluating to a constant + struct NonconstantException { }; // TODO: use a flow with a special name, as this is likely very slow +private: + // map of local indexes to values set in the context of this runner + std::unordered_map setLocalValues; + // map of global names to values set in the context of this runner + std::unordered_map setGlobalValues; + // optional reference to the (possibly large) map of gets to constant values + // in a full pass context + GetValues* getValues; + // Whether we are just evaluating or also going to replace the expression + // afterwards. + Mode mode; + +public: + ContextAwareExpressionRunner(Module* module, + Mode mode, + Index maxDepth, + GetValues* getValues = nullptr) + : ExpressionRunner(maxDepth), module(module), + getValues(getValues), mode(mode) {} + + virtual ~ContextAwareExpressionRunner() {} + Module* getModule() { return module; } // Sets a known local value to use. Returns `true` if the value is actually // constant. - bool setLocalValue(Index index, Literals values) { + bool setLocalValue(Index index, Literals& values) { if (values.isConcrete()) { - setLocalValues[index] = std::move(values); + setLocalValues[index] = values; return true; } setLocalValues.erase(index); @@ -2290,16 +2300,16 @@ class StandaloneExpressionRunner bool setLocalValue(Index index, Expression* expr) { auto setFlow = visit(expr); if (!setFlow.breaking()) { - return setLocalValue(index, std::move(setFlow.values)); + return setLocalValue(index, setFlow.values); } return false; } // Sets a known global value to use. Returns `true` if the value is actually // constant. - bool setGlobalValue(Name name, Literals values) { + bool setGlobalValue(Name name, Literals& values) { if (values.isConcrete()) { - setGlobalValues[name] = std::move(values); + setGlobalValues[name] = values; return true; } setGlobalValues.erase(name); @@ -2311,7 +2321,7 @@ class StandaloneExpressionRunner bool setGlobalValue(Name name, Expression* expr) { auto setFlow = visit(expr); if (!setFlow.breaking()) { - return setGlobalValue(name, std::move(setFlow.values)); + return setGlobalValue(name, setFlow.values); } return false; } @@ -2320,31 +2330,30 @@ class StandaloneExpressionRunner // loops might be infinite, so must be careful // but we can't tell if non-infinite, since we don't have state, so loops // are just impossible to optimize for now - return Flow(NONSTANDALONE_FLOW); - } - - Flow visitCall(Call* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitCallIndirect(CallIndirect* curr) { - return Flow(NONSTANDALONE_FLOW); + return Flow(NONCONSTANT_FLOW); } + Flow visitCall(Call* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitCallIndirect(CallIndirect* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitLocalGet(LocalGet* curr) { // Check if we already know the constant value from pass context. - auto iter = getValues.find(curr); - if (iter != getValues.end()) { - auto values = iter->second; - if (values.isConcrete()) { - return Flow(std::move(values)); + if (getValues != nullptr) { + auto iter = getValues->find(curr); + if (iter != getValues->end()) { + auto values = iter->second; + if (values.isConcrete()) { + return Flow(std::move(values)); + } } } // Check if a constant value has been set in the context of this runner. - auto iter2 = setLocalValues.find(curr->index); - if (iter2 != setLocalValues.end()) { - return Flow(std::move(iter2->second)); + auto iter = setLocalValues.find(curr->index); + if (iter != setLocalValues.end()) { + return Flow(std::move(iter->second)); } - return Flow(NONSTANDALONE_FLOW); + return Flow(NONCONSTANT_FLOW); } Flow visitLocalSet(LocalSet* curr) { - if (intent == Intent::EVALUATE) { + if (mode == Mode::EVALUATE) { // If we are evaluating and not replacing the expression, see if there is // a value flowing through a tee. if (curr->type.isConcrete()) { @@ -2356,7 +2365,7 @@ class StandaloneExpressionRunner return Flow(); } } - return Flow(NONSTANDALONE_FLOW); + return Flow(NONCONSTANT_FLOW); } Flow visitGlobalGet(GlobalGet* curr) { auto* global = module->getGlobal(curr->name); @@ -2369,10 +2378,10 @@ class StandaloneExpressionRunner if (iter != setGlobalValues.end()) { return Flow(std::move(iter->second)); } - return Flow(NONSTANDALONE_FLOW); + return Flow(NONCONSTANT_FLOW); } Flow visitGlobalSet(GlobalSet* curr) { - if (intent == Intent::EVALUATE) { + if (mode == Mode::EVALUATE) { // If we are evaluating and not replacing the expression, remember the // constant value set, if any, for subsequent gets. assert(module->getGlobal(curr->name)->mutable_); @@ -2380,32 +2389,30 @@ class StandaloneExpressionRunner return Flow(); } } - return Flow(NONSTANDALONE_FLOW); + return Flow(NONCONSTANT_FLOW); } - Flow visitLoad(Load* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitStore(Store* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitAtomicRMW(AtomicRMW* curr) { return Flow(NONSTANDALONE_FLOW); } + Flow visitLoad(Load* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitStore(Store* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitAtomicRMW(AtomicRMW* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { - return Flow(NONSTANDALONE_FLOW); - } - Flow visitAtomicWait(AtomicWait* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitAtomicNotify(AtomicNotify* curr) { - return Flow(NONSTANDALONE_FLOW); + return Flow(NONCONSTANT_FLOW); } - Flow visitSIMDLoad(SIMDLoad* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitMemoryInit(MemoryInit* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitDataDrop(DataDrop* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitMemoryCopy(MemoryCopy* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitMemoryFill(MemoryFill* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitHost(Host* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitTry(Try* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitThrow(Throw* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitRethrow(Rethrow* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitBrOnExn(BrOnExn* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitPush(Push* curr) { return Flow(NONSTANDALONE_FLOW); } - Flow visitPop(Pop* curr) { return Flow(NONSTANDALONE_FLOW); } - - void trap(const char* why) override { throw NonstandaloneException(); } + Flow visitAtomicWait(AtomicWait* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitAtomicNotify(AtomicNotify* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitSIMDLoad(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitMemoryInit(MemoryInit* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitDataDrop(DataDrop* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitMemoryCopy(MemoryCopy* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitMemoryFill(MemoryFill* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitHost(Host* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitTry(Try* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitThrow(Throw* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitRethrow(Rethrow* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitBrOnExn(BrOnExn* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitPush(Push* curr) { return Flow(NONCONSTANT_FLOW); } + Flow visitPop(Pop* curr) { return Flow(NONCONSTANT_FLOW); } + + void trap(const char* why) override { throw NonconstantException(); } }; } // namespace wasm diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index 7bf4393e1e3..eb7465264ad 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -2,10 +2,10 @@ binaryen.setAPITracing(true); var module = new binaryen.Module(); module.addGlobal("aGlobal", binaryen.i32, true, module.i32.const(0)); -var Intent = binaryen.ExpressionRunner.Intent; +var Mode = binaryen.ExpressionRunner.Mode; // Should evaluate down to a constant -var runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); +var runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); var expr = runner.runAndDispose( module.i32.add( module.i32.const(1), @@ -15,7 +15,7 @@ var expr = runner.runAndDispose( assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":3}'); // Should traverse control structures -runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); +runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); expr = runner.runAndDispose( module.i32.add( module.i32.const(1), @@ -28,8 +28,8 @@ expr = runner.runAndDispose( ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":4}'); -// Should be unable to evaluate a local -runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); +// Should be unable to evaluate a local if not explicitly specified +runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); expr = runner.runAndDispose( module.i32.add( module.local.get(0, binaryen.i32), @@ -38,15 +38,15 @@ expr = runner.runAndDispose( ); assert(expr === 0); -// Should handle traps -runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); +// Should handle traps properly +runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); expr = runner.runAndDispose( module.unreachable() ); assert(expr === 0); -// Should ignore some side-effects if the intent is to evaluate the expression -runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); +// Should ignore `local.tee` side-effects if just evaluating the expression +runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); expr = runner.runAndDispose( module.i32.add( module.local.tee(0, module.i32.const(4), binaryen.i32), @@ -55,8 +55,8 @@ expr = runner.runAndDispose( ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":5}'); -// Should keep side-effects if the intent is to replace the expression -runner = new binaryen.ExpressionRunner(module, Intent.Replace); +// Should keep all side-effects if we are going to replace the expression +runner = new binaryen.ExpressionRunner(module, Mode.Replace); expr = runner.runAndDispose( module.i32.add( module.local.tee(0, module.i32.const(4), binaryen.i32), @@ -65,8 +65,8 @@ expr = runner.runAndDispose( ); assert(expr === 0); -// Should work with temporary values if the intent is to evaluate the expression -runner = new binaryen.ExpressionRunner(module, Intent.Evaluate); +// Should work with temporary values if just evaluating the expression +runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); expr = runner.runAndDispose( module.i32.add( module.block(null, [ @@ -82,7 +82,7 @@ expr = runner.runAndDispose( assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":6}'); // Should pick up explicitly preset values -runner = new binaryen.ExpressionRunner(module, Intent.Replace); +runner = new binaryen.ExpressionRunner(module, Mode.Replace); assert(runner.setLocalValue(0, module.i32.const(3))); assert(runner.setGlobalValue("aGlobal", module.i32.const(4))); expr = runner.runAndDispose( From 35d09c2652ff584fb426101edb7dec2fec6f1b68 Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 20 Mar 2020 20:04:45 +0100 Subject: [PATCH 11/27] more documentation for getValues --- src/wasm-interpreter.h | 44 +++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index abe2d4ba27e..7853eefd9bc 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2253,20 +2253,21 @@ class ContextAwareExpressionRunner REPLACE }; - // special name indicating a flow not evaluating to a constant + // Special name indicating a flow not evaluating to a constant const Name NONCONSTANT_FLOW = "Binaryen|nonconstant"; - // special exception indicating a flow not evaluating to a constant + // Special exception indicating a flow not evaluating to a constant struct NonconstantException { }; // TODO: use a flow with a special name, as this is likely very slow private: - // map of local indexes to values set in the context of this runner - std::unordered_map setLocalValues; - // map of global names to values set in the context of this runner - std::unordered_map setGlobalValues; - // optional reference to the (possibly large) map of gets to constant values - // in a full pass context + // Map of local indexes to values set in the context of this runner + std::unordered_map localValues; + // Map of global names to values set in the context of this runner + std::unordered_map globalValues; + // Optional reference to a map of gets to constant values, for example where a + // pass did already compute these. Not applicable in C-API usage and possibly + // large, hence a reference to the respective map (must not be mutated). GetValues* getValues; // Whether we are just evaluating or also going to replace the expression // afterwards. @@ -2288,10 +2289,10 @@ class ContextAwareExpressionRunner // constant. bool setLocalValue(Index index, Literals& values) { if (values.isConcrete()) { - setLocalValues[index] = values; + localValues[index] = values; return true; } - setLocalValues.erase(index); + localValues.erase(index); return false; } @@ -2309,10 +2310,10 @@ class ContextAwareExpressionRunner // constant. bool setGlobalValue(Name name, Literals& values) { if (values.isConcrete()) { - setGlobalValues[name] = values; + globalValues[name] = values; return true; } - setGlobalValues.erase(name); + globalValues.erase(name); return false; } @@ -2335,7 +2336,15 @@ class ContextAwareExpressionRunner Flow visitCall(Call* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitCallIndirect(CallIndirect* curr) { return Flow(NONCONSTANT_FLOW); } Flow visitLocalGet(LocalGet* curr) { - // Check if we already know the constant value from pass context. + // Check if a constant value has been set in the context of this runner. + auto iter = localValues.find(curr->index); + if (iter != localValues.end()) { + return Flow(std::move(iter->second)); + } + // If not the case, see if the calling pass did compute the value of this + // specific `local.get` in an earlier step already. This is a fallback + // targeting the precompute pass specifically, which already did this work, + // but is not applicable when the runner is used via the C-API for example. if (getValues != nullptr) { auto iter = getValues->find(curr); if (iter != getValues->end()) { @@ -2345,11 +2354,6 @@ class ContextAwareExpressionRunner } } } - // Check if a constant value has been set in the context of this runner. - auto iter = setLocalValues.find(curr->index); - if (iter != setLocalValues.end()) { - return Flow(std::move(iter->second)); - } return Flow(NONCONSTANT_FLOW); } Flow visitLocalSet(LocalSet* curr) { @@ -2374,8 +2378,8 @@ class ContextAwareExpressionRunner return visit(global->init); } // Check if a constant value has been set in the context of this runner. - auto iter = setGlobalValues.find(curr->name); - if (iter != setGlobalValues.end()) { + auto iter = globalValues.find(curr->name); + if (iter != globalValues.end()) { return Flow(std::move(iter->second)); } return Flow(NONCONSTANT_FLOW); From f57cbdcd578b015d45122c5e764355871ff7304c Mon Sep 17 00:00:00 2001 From: dcode Date: Sat, 21 Mar 2020 01:05:59 +0100 Subject: [PATCH 12/27] refactor runner to its own cpp file --- src/passes/Precompute.cpp | 21 +- src/wasm-interpreter.h | 201 +++++----------- src/wasm/CMakeLists.txt | 1 + ...terpreter-ContextAwareExpressionRunner.cpp | 226 ++++++++++++++++++ 4 files changed, 294 insertions(+), 155 deletions(-) create mode 100644 src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 4cb9f2639c7..f7d6110c10f 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -39,9 +39,9 @@ namespace wasm { -// Special name also used by ContextAwareExpressionRunner to indicate a -// non-constant flow. -static const Name NONCONSTANT_FLOW("Binaryen|nonconstant"); +using GetValues = ContextAwareExpressionRunner::GetValues; +using Mode = ContextAwareExpressionRunner::Mode; +using NonconstantException = ContextAwareExpressionRunner::NonconstantException; // Limit evaluation depth for 2 reasons: first, it is highly unlikely // that we can do anything useful to precompute a hugely nested expression @@ -60,7 +60,7 @@ struct Precompute Precompute(bool propagate) : propagate(propagate) {} - ContextAwareExpressionRunner::GetValues getValues; + GetValues getValues; bool worked; @@ -100,7 +100,7 @@ struct Precompute return; } if (flow.breaking()) { - if (flow.breakTo == NONCONSTANT_FLOW) { + if (flow.breakTo == ContextAwareExpressionRunner::NONCONSTANT_FLOW) { return; } if (flow.breakTo == RETURN_FLOW) { @@ -174,15 +174,13 @@ struct Precompute private: // Precompute an expression, returning a flow, which may be a constant // (that we can replace the expression with if replaceExpression is set). - Flow precomputeExpression(Expression* curr, - ContextAwareExpressionRunner::Mode mode = - ContextAwareExpressionRunner::Mode::REPLACE) { + Flow precomputeExpression(Expression* curr, Mode mode = Mode::REPLACE) { try { return ContextAwareExpressionRunner( getModule(), mode, MAX_DEPTH, &getValues) .visit(curr); - } catch (ContextAwareExpressionRunner::NonconstantException&) { - return Flow(NONCONSTANT_FLOW); + } catch (NonconstantException&) { + return Flow(ContextAwareExpressionRunner::NONCONSTANT_FLOW); } } @@ -196,8 +194,7 @@ struct Precompute Literals precomputeValue(Expression* curr) { // Note that we do not intent to replace the expression, as we just care // about the value here. - Flow flow = - precomputeExpression(curr, ContextAwareExpressionRunner::Mode::EVALUATE); + Flow flow = precomputeExpression(curr, Mode::EVALUATE); if (flow.breaking()) { return {}; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 7853eefd9bc..08d39746a9b 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2232,10 +2232,9 @@ class ModuleInstance // anything that can't be evaluated down to a constant. class ContextAwareExpressionRunner : public ExpressionRunner { - Module* module; public: - // map of `local.get`s to their respective values. + // Map of `local.get`s to their respective values. typedef std::unordered_map GetValues; // Whether we are trying to precompute down to an expression (which we can @@ -2253,170 +2252,86 @@ class ContextAwareExpressionRunner REPLACE }; - // Special name indicating a flow not evaluating to a constant - const Name NONCONSTANT_FLOW = "Binaryen|nonconstant"; + // Special break target indicating a flow not evaluating to a constant + static const Name NONCONSTANT_FLOW; // Special exception indicating a flow not evaluating to a constant struct NonconstantException { }; // TODO: use a flow with a special name, as this is likely very slow -private: - // Map of local indexes to values set in the context of this runner - std::unordered_map localValues; - // Map of global names to values set in the context of this runner - std::unordered_map globalValues; - // Optional reference to a map of gets to constant values, for example where a - // pass did already compute these. Not applicable in C-API usage and possibly - // large, hence a reference to the respective map (must not be mutated). - GetValues* getValues; - // Whether we are just evaluating or also going to replace the expression - // afterwards. - Mode mode; - -public: ContextAwareExpressionRunner(Module* module, Mode mode, Index maxDepth, - GetValues* getValues = nullptr) - : ExpressionRunner(maxDepth), module(module), - getValues(getValues), mode(mode) {} + GetValues* getValues = nullptr); - virtual ~ContextAwareExpressionRunner() {} + virtual ~ContextAwareExpressionRunner(); + // ^ Necessary because we `delete` the instance in binaryen-c.cpp in + // ExpressionRunnerRunAndDispose. - Module* getModule() { return module; } + // Gets the module this runner belongs to. + Module* getModule(); // Sets a known local value to use. Returns `true` if the value is actually // constant. - bool setLocalValue(Index index, Literals& values) { - if (values.isConcrete()) { - localValues[index] = values; - return true; - } - localValues.erase(index); - return false; - } + bool setLocalValue(Index index, Literals& values); // Sets a known local value to use. Order matters if expressions have side // effects. Returns `true` if the expression actually evaluates to a constant. - bool setLocalValue(Index index, Expression* expr) { - auto setFlow = visit(expr); - if (!setFlow.breaking()) { - return setLocalValue(index, setFlow.values); - } - return false; - } + bool setLocalValue(Index index, Expression* expr); // Sets a known global value to use. Returns `true` if the value is actually // constant. - bool setGlobalValue(Name name, Literals& values) { - if (values.isConcrete()) { - globalValues[name] = values; - return true; - } - globalValues.erase(name); - return false; - } + bool setGlobalValue(Name name, Literals& values); // Sets a known global value to use. Order matters if expressions have side // effects. Returns `true` if the expression actually evaluates to a constant. - bool setGlobalValue(Name name, Expression* expr) { - auto setFlow = visit(expr); - if (!setFlow.breaking()) { - return setGlobalValue(name, setFlow.values); - } - return false; - } + bool setGlobalValue(Name name, Expression* expr); + + Flow visitLoop(Loop* curr); + Flow visitCall(Call* curr); + Flow visitCallIndirect(CallIndirect* curr); + Flow visitLocalGet(LocalGet* curr); + Flow visitLocalSet(LocalSet* curr); + Flow visitGlobalGet(GlobalGet* curr); + Flow visitGlobalSet(GlobalSet* curr); + Flow visitLoad(Load* curr); + Flow visitStore(Store* curr); + Flow visitAtomicRMW(AtomicRMW* curr); + Flow visitAtomicCmpxchg(AtomicCmpxchg* curr); + Flow visitAtomicWait(AtomicWait* curr); + Flow visitAtomicNotify(AtomicNotify* curr); + Flow visitSIMDLoad(SIMDLoad* curr); + Flow visitMemoryInit(MemoryInit* curr); + Flow visitDataDrop(DataDrop* curr); + Flow visitMemoryCopy(MemoryCopy* curr); + Flow visitMemoryFill(MemoryFill* curr); + Flow visitHost(Host* curr); + Flow visitTry(Try* curr); + Flow visitThrow(Throw* curr); + Flow visitRethrow(Rethrow* curr); + Flow visitBrOnExn(BrOnExn* curr); + Flow visitPush(Push* curr); + Flow visitPop(Pop* curr); + void trap(const char* why) override; - Flow visitLoop(Loop* curr) { - // loops might be infinite, so must be careful - // but we can't tell if non-infinite, since we don't have state, so loops - // are just impossible to optimize for now - return Flow(NONCONSTANT_FLOW); - } - Flow visitCall(Call* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitCallIndirect(CallIndirect* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitLocalGet(LocalGet* curr) { - // Check if a constant value has been set in the context of this runner. - auto iter = localValues.find(curr->index); - if (iter != localValues.end()) { - return Flow(std::move(iter->second)); - } - // If not the case, see if the calling pass did compute the value of this - // specific `local.get` in an earlier step already. This is a fallback - // targeting the precompute pass specifically, which already did this work, - // but is not applicable when the runner is used via the C-API for example. - if (getValues != nullptr) { - auto iter = getValues->find(curr); - if (iter != getValues->end()) { - auto values = iter->second; - if (values.isConcrete()) { - return Flow(std::move(values)); - } - } - } - return Flow(NONCONSTANT_FLOW); - } - Flow visitLocalSet(LocalSet* curr) { - if (mode == Mode::EVALUATE) { - // If we are evaluating and not replacing the expression, see if there is - // a value flowing through a tee. - if (curr->type.isConcrete()) { - assert(curr->isTee()); - return visit(curr->value); - } - // Otherwise remember the constant value set, if any, for subsequent gets. - if (setLocalValue(curr->index, curr->value)) { - return Flow(); - } - } - return Flow(NONCONSTANT_FLOW); - } - Flow visitGlobalGet(GlobalGet* curr) { - auto* global = module->getGlobal(curr->name); - // Check if the global has an immutable value anyway - if (!global->imported() && !global->mutable_) { - return visit(global->init); - } - // Check if a constant value has been set in the context of this runner. - auto iter = globalValues.find(curr->name); - if (iter != globalValues.end()) { - return Flow(std::move(iter->second)); - } - return Flow(NONCONSTANT_FLOW); - } - Flow visitGlobalSet(GlobalSet* curr) { - if (mode == Mode::EVALUATE) { - // If we are evaluating and not replacing the expression, remember the - // constant value set, if any, for subsequent gets. - assert(module->getGlobal(curr->name)->mutable_); - if (setGlobalValue(curr->name, curr->value)) { - return Flow(); - } - } - return Flow(NONCONSTANT_FLOW); - } - Flow visitLoad(Load* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitStore(Store* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitAtomicRMW(AtomicRMW* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { - return Flow(NONCONSTANT_FLOW); - } - Flow visitAtomicWait(AtomicWait* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitAtomicNotify(AtomicNotify* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitSIMDLoad(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitMemoryInit(MemoryInit* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitDataDrop(DataDrop* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitMemoryCopy(MemoryCopy* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitMemoryFill(MemoryFill* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitHost(Host* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitTry(Try* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitThrow(Throw* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitRethrow(Rethrow* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitBrOnExn(BrOnExn* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitPush(Push* curr) { return Flow(NONCONSTANT_FLOW); } - Flow visitPop(Pop* curr) { return Flow(NONCONSTANT_FLOW); } - - void trap(const char* why) override { throw NonconstantException(); } +private: + // Module this runner belongs to. + Module* module; + + // Map of local indexes to values set in the context of this runner + std::unordered_map localValues; + + // Map of global names to values set in the context of this runner + std::unordered_map globalValues; + + // Optional reference to a map of gets to constant values, for example where a + // pass did already compute these. Not applicable in C-API usage and possibly + // large, hence a reference to the respective map (must not be mutated). + GetValues* getValues; + + // Whether we are just evaluating or also going to replace the expression + // afterwards. + Mode mode; }; } // namespace wasm diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt index 916fbfb6361..ac3b574e864 100644 --- a/src/wasm/CMakeLists.txt +++ b/src/wasm/CMakeLists.txt @@ -6,6 +6,7 @@ set(wasm_SOURCES wasm-emscripten.cpp wasm-debug.cpp wasm-interpreter.cpp + wasm-interpreter-ContextAwareExpressionRunner.cpp wasm-io.cpp wasm-s-parser.cpp wasm-stack.cpp diff --git a/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp b/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp new file mode 100644 index 00000000000..1befff0c737 --- /dev/null +++ b/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp @@ -0,0 +1,226 @@ +/* + * Copyright 2020 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wasm-interpreter.h" + +namespace wasm { + +const Name ContextAwareExpressionRunner::NONCONSTANT_FLOW = + "Binaryen|nonconstant"; + +ContextAwareExpressionRunner::ContextAwareExpressionRunner(Module* module, + Mode mode, + Index maxDepth, + GetValues* getValues) + : ExpressionRunner(maxDepth), module(module), + getValues(getValues), mode(mode) {} + +ContextAwareExpressionRunner::~ContextAwareExpressionRunner() {} + +Module* ContextAwareExpressionRunner::getModule() { return module; } + +bool ContextAwareExpressionRunner::setLocalValue(Index index, + Literals& values) { + if (values.isConcrete()) { + localValues[index] = values; + return true; + } + localValues.erase(index); + return false; +} + +bool ContextAwareExpressionRunner::setLocalValue(Index index, + Expression* expr) { + auto setFlow = visit(expr); + if (!setFlow.breaking()) { + return setLocalValue(index, setFlow.values); + } + return false; +} + +bool ContextAwareExpressionRunner::setGlobalValue(Name name, Literals& values) { + if (values.isConcrete()) { + globalValues[name] = values; + return true; + } + globalValues.erase(name); + return false; +} + +bool ContextAwareExpressionRunner::setGlobalValue(Name name, Expression* expr) { + auto setFlow = visit(expr); + if (!setFlow.breaking()) { + return setGlobalValue(name, setFlow.values); + } + return false; +} + +Flow ContextAwareExpressionRunner::visitLoop(Loop* curr) { + // loops might be infinite, so must be careful + // but we can't tell if non-infinite, since we don't have state, so loops + // are just impossible to optimize for now + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitCall(Call* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitCallIndirect(CallIndirect* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitLocalGet(LocalGet* curr) { + // Check if a constant value has been set in the context of this runner. + auto iter = localValues.find(curr->index); + if (iter != localValues.end()) { + return Flow(std::move(iter->second)); + } + // If not the case, see if the calling pass did compute the value of this + // specific `local.get` in an earlier step already. This is a fallback + // targeting the precompute pass specifically, which already did this work, + // but is not applicable when the runner is used via the C-API for example. + if (getValues != nullptr) { + auto iter = getValues->find(curr); + if (iter != getValues->end()) { + auto values = iter->second; + if (values.isConcrete()) { + return Flow(std::move(values)); + } + } + } + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitLocalSet(LocalSet* curr) { + if (mode == Mode::EVALUATE) { + // If we are evaluating and not replacing the expression, see if there is + // a value flowing through a tee. + if (curr->type.isConcrete()) { + assert(curr->isTee()); + return visit(curr->value); + } + // Otherwise remember the constant value set, if any, for subsequent gets. + if (setLocalValue(curr->index, curr->value)) { + return Flow(); + } + } + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitGlobalGet(GlobalGet* curr) { + auto* global = module->getGlobal(curr->name); + // Check if the global has an immutable value anyway + if (!global->imported() && !global->mutable_) { + return visit(global->init); + } + // Check if a constant value has been set in the context of this runner. + auto iter = globalValues.find(curr->name); + if (iter != globalValues.end()) { + return Flow(std::move(iter->second)); + } + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitGlobalSet(GlobalSet* curr) { + if (mode == Mode::EVALUATE) { + // If we are evaluating and not replacing the expression, remember the + // constant value set, if any, for subsequent gets. + assert(module->getGlobal(curr->name)->mutable_); + if (setGlobalValue(curr->name, curr->value)) { + return Flow(); + } + } + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitLoad(Load* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitStore(Store* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitAtomicRMW(AtomicRMW* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitAtomicCmpxchg(AtomicCmpxchg* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitAtomicWait(AtomicWait* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitAtomicNotify(AtomicNotify* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitSIMDLoad(SIMDLoad* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitMemoryInit(MemoryInit* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitDataDrop(DataDrop* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitMemoryCopy(MemoryCopy* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitMemoryFill(MemoryFill* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitHost(Host* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitTry(Try* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitThrow(Throw* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitRethrow(Rethrow* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitBrOnExn(BrOnExn* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitPush(Push* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitPop(Pop* curr) { + return Flow(NONCONSTANT_FLOW); +} + +void ContextAwareExpressionRunner::trap(const char* why) { + throw NonconstantException(); +} + +} // namespace wasm From 63d7520eac10a240b0c04508196924505333b662 Mon Sep 17 00:00:00 2001 From: dcode Date: Sat, 21 Mar 2020 18:08:21 +0100 Subject: [PATCH 13/27] traverse into simple functions --- src/wasm-interpreter.h | 13 +++- ...terpreter-ContextAwareExpressionRunner.cpp | 61 +++++++++++++++---- test/binaryen.js/expressionrunner.js | 21 +++++++ test/binaryen.js/expressionrunner.js.txt | 28 +++++++++ test/emcc_O2_hello_world.fromasm | 8 +-- test/emcc_O2_hello_world.fromasm.clamp | 8 +-- test/emcc_O2_hello_world.fromasm.imprecise | 33 +++++----- test/emcc_hello_world.fromasm | 34 +++++------ test/emcc_hello_world.fromasm.clamp | 34 +++++------ test/emcc_hello_world.fromasm.imprecise | 34 +++++------ test/memorygrowth.fromasm | 8 +-- test/memorygrowth.fromasm.clamp | 8 +-- test/memorygrowth.fromasm.imprecise | 19 +++--- test/passes/dae-optimizing.txt | 2 +- test/unit.fromasm.clamp | 5 -- 15 files changed, 190 insertions(+), 126 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 08d39746a9b..cbcbec30c2a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -154,6 +154,10 @@ class ExpressionRunner : public OverriddenVisitor { Index depth = 0; + // Trap instead of aborting if we hit an invalid expression. Useful where + // we are only interested in valid expressions before validation. + bool trapIfInvalid = false; + Flow generateArguments(const ExpressionList& operands, LiteralList& arguments) { NOTE_ENTER_("generateArguments"); @@ -181,14 +185,17 @@ class ExpressionRunner : public OverriddenVisitor { if (!ret.breaking()) { Type type = ret.getType(); if (type.isConcrete() || curr->type.isConcrete()) { -#if 1 // def WASM_INTERPRETER_DEBUG if (!Type::isSubType(type, curr->type)) { + if (trapIfInvalid) { + trap("unexpected type"); + } +#if 1 // def WASM_INTERPRETER_DEBUG std::cerr << "expected " << curr->type << ", seeing " << type << " from\n" << curr << '\n'; - } #endif - assert(Type::isSubType(type, curr->type)); + assert(false); + } } } depth--; diff --git a/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp b/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp index 1befff0c737..b73865f9370 100644 --- a/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp +++ b/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp @@ -26,7 +26,10 @@ ContextAwareExpressionRunner::ContextAwareExpressionRunner(Module* module, Index maxDepth, GetValues* getValues) : ExpressionRunner(maxDepth), module(module), - getValues(getValues), mode(mode) {} + getValues(getValues), mode(mode) { + // Trap instead of aborting if we hit an invalid expression. + trapIfInvalid = true; +} ContextAwareExpressionRunner::~ContextAwareExpressionRunner() {} @@ -76,6 +79,34 @@ Flow ContextAwareExpressionRunner::visitLoop(Loop* curr) { } Flow ContextAwareExpressionRunner::visitCall(Call* curr) { + // Traverse into functions using the same mode, which we can also do + // when replacing as long as the function does not have any side effects. + // Might yield something useful for simple functions like `clamp`, sometimes + // even if arguments are only partially constant or not constant at all. + // Note that we are not a validator, so we skip calls to functions that do not + // exist yet or where the signature does not match. + auto* func = module->getFunctionOrNull(curr->target); + if (func != nullptr && !func->imported()) { + if (func->sig.results.isConcrete()) { + auto numOperands = curr->operands.size(); + if (numOperands == func->getNumParams()) { + ContextAwareExpressionRunner runner(module, mode, maxDepth); + runner.depth = depth + 1; + for (Index i = 0; i < numOperands; ++i) { + auto argFlow = visit(curr->operands[i]); + if (!argFlow.breaking() && argFlow.values.isConcrete()) { + runner.localValues[i] = std::move(argFlow.values); + } + } + auto retFlow = runner.visit(func->body); + if (retFlow.breakTo == RETURN_FLOW) { + return Flow(std::move(retFlow.values)); + } else if (!retFlow.breaking()) { + return retFlow; + } + } + } + } return Flow(NONCONSTANT_FLOW); } @@ -122,15 +153,17 @@ Flow ContextAwareExpressionRunner::visitLocalSet(LocalSet* curr) { } Flow ContextAwareExpressionRunner::visitGlobalGet(GlobalGet* curr) { - auto* global = module->getGlobal(curr->name); - // Check if the global has an immutable value anyway - if (!global->imported() && !global->mutable_) { - return visit(global->init); - } - // Check if a constant value has been set in the context of this runner. - auto iter = globalValues.find(curr->name); - if (iter != globalValues.end()) { - return Flow(std::move(iter->second)); + auto* global = module->getGlobalOrNull(curr->name); + if (global != nullptr) { + // Check if the global has an immutable value anyway + if (!global->imported() && !global->mutable_) { + return visit(global->init); + } + // Check if a constant value has been set in the context of this runner. + auto iter = globalValues.find(curr->name); + if (iter != globalValues.end()) { + return Flow(std::move(iter->second)); + } } return Flow(NONCONSTANT_FLOW); } @@ -139,9 +172,11 @@ Flow ContextAwareExpressionRunner::visitGlobalSet(GlobalSet* curr) { if (mode == Mode::EVALUATE) { // If we are evaluating and not replacing the expression, remember the // constant value set, if any, for subsequent gets. - assert(module->getGlobal(curr->name)->mutable_); - if (setGlobalValue(curr->name, curr->value)) { - return Flow(); + auto* global = module->getGlobalOrNull(curr->name); + if (global != nullptr && global->mutable_) { + if (setGlobalValue(curr->name, curr->value)) { + return Flow(); + } } } return Flow(NONCONSTANT_FLOW); diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index eb7465264ad..8d1887c3f18 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -93,4 +93,25 @@ expr = runner.runAndDispose( ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":7}'); +// Should traverse into simple functions +runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); +module.addFunction("add", binaryen.createType([ binaryen.i32, binaryen.i32 ]), binaryen.i32, [], + module.block(null, [ + module.i32.add( + module.local.get(0, binaryen.i32), + module.local.get(1, binaryen.i32) + ) + ], binaryen.i32) +); +expr = runner.runAndDispose( + module.i32.add( + module.i32.const(1), + module.call("add", [ + module.i32.const(3), + module.i32.const(4) + ], binaryen.i32) + ) +); +assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":8}'); + binaryen.setAPITracing(false); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index d86d98addc8..13a26535d35 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -90,6 +90,34 @@ int main() { BinaryenExpressionGetId(expressions[41]); BinaryenExpressionGetType(expressions[41]); BinaryenConstGetValueI32(expressions[41]); + the_runner = ExpressionRunnerCreate(the_module, 0, 50); + { + BinaryenType t0[] = {2, 2}; + BinaryenTypeCreate(t0, 2); // 11 + } + expressions[42] = BinaryenLocalGet(the_module, 0, 2); + expressions[43] = BinaryenLocalGet(the_module, 1, 2); + expressions[44] = BinaryenBinary(the_module, 0, expressions[42], expressions[43]); + { + BinaryenExpressionRef children[] = { expressions[44] }; + expressions[45] = BinaryenBlock(the_module, NULL, children, 1, 2); + } + { + BinaryenType varTypes[] = { 0 }; + functions[0] = BinaryenAddFunction(the_module, "add", 11, 2, varTypes, 0, expressions[45]); + } + expressions[46] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[47] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); + expressions[48] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + { + BinaryenExpressionRef operands[] = { expressions[47], expressions[48] }; + expressions[49] = BinaryenCall(the_module, "add", operands, 2, 2); + } + expressions[50] = BinaryenBinary(the_module, 0, expressions[46], expressions[49]); + expressions[51] = ExpressionRunnerRunAndDispose(the_runner, expressions[50]); + BinaryenExpressionGetId(expressions[51]); + BinaryenExpressionGetType(expressions[51]); + BinaryenConstGetValueI32(expressions[51]); return 0; } // ending a Binaryen API trace diff --git a/test/emcc_O2_hello_world.fromasm b/test/emcc_O2_hello_world.fromasm index 751eebe6fef..f382f9a5586 100644 --- a/test/emcc_O2_hello_world.fromasm +++ b/test/emcc_O2_hello_world.fromasm @@ -8768,10 +8768,6 @@ ) ) (func $_fwrite (; 29 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32) - (local $2 i32) - (local.set $2 - (local.get $0) - ) (if (result i32) (block (result i32) (drop @@ -8780,10 +8776,10 @@ ) ) (i32.ne - (local.get $2) + (local.get $0) (local.tee $1 (call $___fwritex - (local.get $2) + (local.get $0) (local.get $1) ) ) diff --git a/test/emcc_O2_hello_world.fromasm.clamp b/test/emcc_O2_hello_world.fromasm.clamp index 751eebe6fef..f382f9a5586 100644 --- a/test/emcc_O2_hello_world.fromasm.clamp +++ b/test/emcc_O2_hello_world.fromasm.clamp @@ -8768,10 +8768,6 @@ ) ) (func $_fwrite (; 29 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32) - (local $2 i32) - (local.set $2 - (local.get $0) - ) (if (result i32) (block (result i32) (drop @@ -8780,10 +8776,10 @@ ) ) (i32.ne - (local.get $2) + (local.get $0) (local.tee $1 (call $___fwritex - (local.get $2) + (local.get $0) (local.get $1) ) ) diff --git a/test/emcc_O2_hello_world.fromasm.imprecise b/test/emcc_O2_hello_world.fromasm.imprecise index bcec604b942..0e00778bab5 100644 --- a/test/emcc_O2_hello_world.fromasm.imprecise +++ b/test/emcc_O2_hello_world.fromasm.imprecise @@ -8553,7 +8553,6 @@ (local $2 i32) (local $3 i32) (local $4 i32) - (local $5 i32) (drop (i32.load offset=76 (local.tee $0 @@ -8567,25 +8566,23 @@ (if (result i32) (i32.lt_s (i32.add - (select - (i32.div_u - (local.tee $4 + (if (result i32) + (i32.ne + (local.tee $1 + (call $_strlen) + ) + (local.tee $3 (call $___fwritex - (local.tee $3 - (local.tee $2 - (call $_strlen) - ) - ) + (local.get $1) (local.get $0) ) ) - (local.get $2) ) - (i32.const 1) - (i32.ne + (i32.div_u (local.get $3) - (local.get $4) + (local.get $1) ) + (i32.const 1) ) (i32.const -1) ) @@ -8602,14 +8599,14 @@ (i32.const 10) ) (block (result i32) - (local.set $5 + (local.set $4 (i32.add (local.get $0) (i32.const 20) ) ) (i32.lt_u - (local.tee $1 + (local.tee $2 (i32.load offset=20 (local.get $0) ) @@ -8623,14 +8620,14 @@ ) (block (i32.store - (local.get $5) + (local.get $4) (i32.add - (local.get $1) + (local.get $2) (i32.const 1) ) ) (i32.store8 - (local.get $1) + (local.get $2) (i32.const 10) ) (br $do-once diff --git a/test/emcc_hello_world.fromasm b/test/emcc_hello_world.fromasm index e860032f9c8..820fa4983c4 100644 --- a/test/emcc_hello_world.fromasm +++ b/test/emcc_hello_world.fromasm @@ -983,7 +983,7 @@ (local $6 i32) (local $7 i32) (local $8 i32) - (local.set $6 + (local.set $7 (global.get $STACKTOP) ) (global.set $STACKTOP @@ -1001,22 +1001,22 @@ ) (local.set $3 (i32.add - (local.get $6) + (local.get $7) (i32.const 120) ) ) (local.set $5 (i32.add (local.tee $4 - (local.get $6) + (local.get $7) ) (i32.const 136) ) ) - (local.set $8 + (local.set $6 (i32.add (local.tee $2 - (local.tee $7 + (local.tee $8 (i32.add (local.get $4) (i32.const 80) @@ -1039,7 +1039,7 @@ (i32.const 4) ) ) - (local.get $8) + (local.get $6) ) ) ) @@ -1056,7 +1056,7 @@ (i32.const 0) (local.get $3) (local.get $4) - (local.get $7) + (local.get $8) ) (i32.const 0) ) @@ -1067,7 +1067,7 @@ (local.get $0) ) ) - (local.set $1 + (local.set $2 (i32.load (local.get $0) ) @@ -1082,7 +1082,7 @@ (i32.store (local.get $0) (i32.and - (local.get $1) + (local.get $2) (i32.const -33) ) ) @@ -1096,11 +1096,11 @@ (local.get $0) (local.get $3) (local.get $4) - (local.get $7) + (local.get $8) ) ) (block - (local.set $2 + (local.set $6 (i32.load offset=44 (local.get $0) ) @@ -1128,16 +1128,16 @@ (i32.const 80) ) ) - (drop + (local.set $1 (call $_printf_core (local.get $0) (local.get $3) (local.get $4) - (local.get $7) + (local.get $8) ) ) (if - (local.get $2) + (local.get $6) (block (drop (call_indirect (type $i32_i32_i32_=>_i32) @@ -1162,7 +1162,7 @@ ) (i32.store offset=44 (local.get $0) - (local.get $2) + (local.get $6) ) (i32.store offset=48 (local.get $0) @@ -1191,7 +1191,7 @@ (local.get $0) ) (i32.and - (local.get $1) + (local.get $2) (i32.const 32) ) ) @@ -1201,7 +1201,7 @@ ) ) (global.set $STACKTOP - (local.get $6) + (local.get $7) ) ) (func $___fwritex (; 37 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (param $2 i32) diff --git a/test/emcc_hello_world.fromasm.clamp b/test/emcc_hello_world.fromasm.clamp index a5be9dceb61..cc2e106f92d 100644 --- a/test/emcc_hello_world.fromasm.clamp +++ b/test/emcc_hello_world.fromasm.clamp @@ -982,7 +982,7 @@ (local $6 i32) (local $7 i32) (local $8 i32) - (local.set $6 + (local.set $7 (global.get $STACKTOP) ) (global.set $STACKTOP @@ -1000,22 +1000,22 @@ ) (local.set $3 (i32.add - (local.get $6) + (local.get $7) (i32.const 120) ) ) (local.set $5 (i32.add (local.tee $4 - (local.get $6) + (local.get $7) ) (i32.const 136) ) ) - (local.set $8 + (local.set $6 (i32.add (local.tee $2 - (local.tee $7 + (local.tee $8 (i32.add (local.get $4) (i32.const 80) @@ -1038,7 +1038,7 @@ (i32.const 4) ) ) - (local.get $8) + (local.get $6) ) ) ) @@ -1055,7 +1055,7 @@ (i32.const 0) (local.get $3) (local.get $4) - (local.get $7) + (local.get $8) ) (i32.const 0) ) @@ -1066,7 +1066,7 @@ (local.get $0) ) ) - (local.set $1 + (local.set $2 (i32.load (local.get $0) ) @@ -1081,7 +1081,7 @@ (i32.store (local.get $0) (i32.and - (local.get $1) + (local.get $2) (i32.const -33) ) ) @@ -1095,11 +1095,11 @@ (local.get $0) (local.get $3) (local.get $4) - (local.get $7) + (local.get $8) ) ) (block - (local.set $2 + (local.set $6 (i32.load offset=44 (local.get $0) ) @@ -1127,16 +1127,16 @@ (i32.const 80) ) ) - (drop + (local.set $1 (call $_printf_core (local.get $0) (local.get $3) (local.get $4) - (local.get $7) + (local.get $8) ) ) (if - (local.get $2) + (local.get $6) (block (drop (call_indirect (type $i32_i32_i32_=>_i32) @@ -1161,7 +1161,7 @@ ) (i32.store offset=44 (local.get $0) - (local.get $2) + (local.get $6) ) (i32.store offset=48 (local.get $0) @@ -1190,7 +1190,7 @@ (local.get $0) ) (i32.and - (local.get $1) + (local.get $2) (i32.const 32) ) ) @@ -1200,7 +1200,7 @@ ) ) (global.set $STACKTOP - (local.get $6) + (local.get $7) ) ) (func $___fwritex (; 36 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (param $2 i32) diff --git a/test/emcc_hello_world.fromasm.imprecise b/test/emcc_hello_world.fromasm.imprecise index b0a91c49e5f..1f4f83da13e 100644 --- a/test/emcc_hello_world.fromasm.imprecise +++ b/test/emcc_hello_world.fromasm.imprecise @@ -975,7 +975,7 @@ (local $6 i32) (local $7 i32) (local $8 i32) - (local.set $6 + (local.set $7 (global.get $STACKTOP) ) (global.set $STACKTOP @@ -993,22 +993,22 @@ ) (local.set $3 (i32.add - (local.get $6) + (local.get $7) (i32.const 120) ) ) (local.set $5 (i32.add (local.tee $4 - (local.get $6) + (local.get $7) ) (i32.const 136) ) ) - (local.set $8 + (local.set $6 (i32.add (local.tee $2 - (local.tee $7 + (local.tee $8 (i32.add (local.get $4) (i32.const 80) @@ -1031,7 +1031,7 @@ (i32.const 4) ) ) - (local.get $8) + (local.get $6) ) ) ) @@ -1048,13 +1048,13 @@ (i32.const 0) (local.get $3) (local.get $4) - (local.get $7) + (local.get $8) ) (i32.const 0) ) (i32.const -1) (block (result i32) - (local.set $1 + (local.set $2 (i32.load (local.get $0) ) @@ -1069,7 +1069,7 @@ (i32.store (local.get $0) (i32.and - (local.get $1) + (local.get $2) (i32.const -33) ) ) @@ -1083,11 +1083,11 @@ (local.get $0) (local.get $3) (local.get $4) - (local.get $7) + (local.get $8) ) ) (block - (local.set $2 + (local.set $6 (i32.load offset=44 (local.get $0) ) @@ -1115,16 +1115,16 @@ (i32.const 80) ) ) - (drop + (local.set $1 (call $_printf_core (local.get $0) (local.get $3) (local.get $4) - (local.get $7) + (local.get $8) ) ) (if - (local.get $2) + (local.get $6) (block (drop (call_indirect (type $i32_i32_i32_=>_i32) @@ -1144,7 +1144,7 @@ ) (i32.store offset=44 (local.get $0) - (local.get $2) + (local.get $6) ) (i32.store offset=48 (local.get $0) @@ -1173,7 +1173,7 @@ (local.get $0) ) (i32.and - (local.get $1) + (local.get $2) (i32.const 32) ) ) @@ -1183,7 +1183,7 @@ ) ) (global.set $STACKTOP - (local.get $6) + (local.get $7) ) ) (func $___fwritex (; 36 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (param $2 i32) diff --git a/test/memorygrowth.fromasm b/test/memorygrowth.fromasm index 14ff6a9e086..b5ecbce4c33 100644 --- a/test/memorygrowth.fromasm +++ b/test/memorygrowth.fromasm @@ -8737,10 +8737,6 @@ ) ) (func $bb (; 26 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32) - (local $2 i32) - (local.set $2 - (local.get $0) - ) (if (result i32) (block (result i32) (drop @@ -8749,13 +8745,13 @@ ) ) (i32.ne - (local.get $2) (local.tee $1 (call $Wa - (local.get $2) + (local.get $0) (local.get $1) ) ) + (local.get $0) ) ) (if (result i32) diff --git a/test/memorygrowth.fromasm.clamp b/test/memorygrowth.fromasm.clamp index 14ff6a9e086..b5ecbce4c33 100644 --- a/test/memorygrowth.fromasm.clamp +++ b/test/memorygrowth.fromasm.clamp @@ -8737,10 +8737,6 @@ ) ) (func $bb (; 26 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32) - (local $2 i32) - (local.set $2 - (local.get $0) - ) (if (result i32) (block (result i32) (drop @@ -8749,13 +8745,13 @@ ) ) (i32.ne - (local.get $2) (local.tee $1 (call $Wa - (local.get $2) + (local.get $0) (local.get $1) ) ) + (local.get $0) ) ) (if (result i32) diff --git a/test/memorygrowth.fromasm.imprecise b/test/memorygrowth.fromasm.imprecise index 0e91ad865ec..4c1ce808d03 100644 --- a/test/memorygrowth.fromasm.imprecise +++ b/test/memorygrowth.fromasm.imprecise @@ -8591,7 +8591,6 @@ (local $0 i32) (local $1 i32) (local $2 i32) - (local $3 i32) (drop (i32.load offset=76 (local.tee $0 @@ -8605,25 +8604,23 @@ (if (result i32) (i32.lt_s (i32.add - (select - (i32.div_u - (local.tee $3 + (if (result i32) + (i32.ne + (local.tee $2 (call $Wa - (local.tee $2 - (local.tee $1 - (call $Za) - ) + (local.tee $1 + (call $Za) ) (local.get $0) ) ) (local.get $1) ) - (i32.const 1) - (i32.ne + (i32.div_u (local.get $2) - (local.get $3) + (local.get $1) ) + (i32.const 1) ) (i32.const -1) ) diff --git a/test/passes/dae-optimizing.txt b/test/passes/dae-optimizing.txt index e20cb2d1c25..5dbcd31740e 100644 --- a/test/passes/dae-optimizing.txt +++ b/test/passes/dae-optimizing.txt @@ -29,7 +29,7 @@ ) (f32.const 1) ) - (call $1) + (f32.const 0) ) ) (i32.const -11) diff --git a/test/unit.fromasm.clamp b/test/unit.fromasm.clamp index d3c3c7b1473..f60bc41605f 100644 --- a/test/unit.fromasm.clamp +++ b/test/unit.fromasm.clamp @@ -1165,11 +1165,6 @@ (local.get $1) ) (func $keepAlive (; 59 ;) (; has Stack IR ;) - (drop - (call $f64-to-int - (f64.const 100) - ) - ) (call_indirect (type $i32_=>_none) (i32.const 0) (i32.const 17) From 5432156a637858028d3e9321eed12600d40d48cb Mon Sep 17 00:00:00 2001 From: dcode Date: Sat, 21 Mar 2020 19:41:22 +0100 Subject: [PATCH 14/27] deal with non-determinism --- src/passes/Precompute.cpp | 5 +- src/wasm-interpreter.h | 14 ++++- ...terpreter-ContextAwareExpressionRunner.cpp | 57 ++++++++++++------- test/emcc_O2_hello_world.fromasm | 8 ++- test/emcc_O2_hello_world.fromasm.clamp | 8 ++- test/emcc_O2_hello_world.fromasm.imprecise | 33 ++++++----- test/emcc_hello_world.fromasm | 34 +++++------ test/emcc_hello_world.fromasm.clamp | 34 +++++------ test/emcc_hello_world.fromasm.imprecise | 34 +++++------ test/memorygrowth.fromasm | 8 ++- test/memorygrowth.fromasm.clamp | 8 ++- test/memorygrowth.fromasm.imprecise | 19 ++++--- test/passes/dae-optimizing.txt | 2 +- test/unit.fromasm.clamp | 5 ++ 14 files changed, 162 insertions(+), 107 deletions(-) diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index f7d6110c10f..8b6b8fe3a16 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -174,7 +174,8 @@ struct Precompute private: // Precompute an expression, returning a flow, which may be a constant // (that we can replace the expression with if replaceExpression is set). - Flow precomputeExpression(Expression* curr, Mode mode = Mode::REPLACE) { + Flow precomputeExpression(Expression* curr, + Mode mode = Mode::REPLACE_DETERMINISTIC) { try { return ContextAwareExpressionRunner( getModule(), mode, MAX_DEPTH, &getValues) @@ -194,7 +195,7 @@ struct Precompute Literals precomputeValue(Expression* curr) { // Note that we do not intent to replace the expression, as we just care // about the value here. - Flow flow = precomputeExpression(curr, Mode::EVALUATE); + Flow flow = precomputeExpression(curr, Mode::EVALUATE_DETERMINISTIC); if (flow.breaking()) { return {}; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index cbcbec30c2a..aa429dbdd5e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2256,7 +2256,13 @@ class ContextAwareExpressionRunner EVALUATE, // We are going to replace the expression afterwards, so side effects // including those of `local.tee`s for example must be retained. - REPLACE + REPLACE, + // Like EVALUATE, excluding potentially non-deterministic traversal in + // function-parallel scenarios. + EVALUATE_DETERMINISTIC, + // Like REPLACE, excluding potentially non-deterministic traversal in + // function-parallel scenarios. + REPLACE_DETERMINISTIC }; // Special break target indicating a flow not evaluating to a constant @@ -2339,6 +2345,12 @@ class ContextAwareExpressionRunner // Whether we are just evaluating or also going to replace the expression // afterwards. Mode mode; + + // Whether `mode` is any of the `EVALUATE` modes. + bool isEvaluate(); + + // Whether `mode` is any of the `DETERMINISTIC` modes. + bool isDeterministic(); }; } // namespace wasm diff --git a/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp b/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp index b73865f9370..b3ea99ca383 100644 --- a/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp +++ b/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp @@ -35,6 +35,15 @@ ContextAwareExpressionRunner::~ContextAwareExpressionRunner() {} Module* ContextAwareExpressionRunner::getModule() { return module; } +bool ContextAwareExpressionRunner::isEvaluate() { + return mode == Mode::EVALUATE || mode == Mode::EVALUATE_DETERMINISTIC; +} + +bool ContextAwareExpressionRunner::isDeterministic() { + return mode == Mode::EVALUATE_DETERMINISTIC || + mode == Mode::REPLACE_DETERMINISTIC; +} + bool ContextAwareExpressionRunner::setLocalValue(Index index, Literals& values) { if (values.isConcrete()) { @@ -83,26 +92,32 @@ Flow ContextAwareExpressionRunner::visitCall(Call* curr) { // when replacing as long as the function does not have any side effects. // Might yield something useful for simple functions like `clamp`, sometimes // even if arguments are only partially constant or not constant at all. - // Note that we are not a validator, so we skip calls to functions that do not - // exist yet or where the signature does not match. - auto* func = module->getFunctionOrNull(curr->target); - if (func != nullptr && !func->imported()) { - if (func->sig.results.isConcrete()) { - auto numOperands = curr->operands.size(); - if (numOperands == func->getNumParams()) { - ContextAwareExpressionRunner runner(module, mode, maxDepth); - runner.depth = depth + 1; - for (Index i = 0; i < numOperands; ++i) { - auto argFlow = visit(curr->operands[i]); - if (!argFlow.breaking() && argFlow.values.isConcrete()) { - runner.localValues[i] = std::move(argFlow.values); + + // Skip traversing into functions if in a function-parallel scenario, where + // functions may or may not have been optimized already to something we can + // traverse successfully. + if (!isDeterministic()) { + // Note that we are not a validator, so we skip calls to functions that do + // not exist yet or where the signature does not match. + auto* func = module->getFunctionOrNull(curr->target); + if (func != nullptr && !func->imported()) { + if (func->sig.results.isConcrete()) { + auto numOperands = curr->operands.size(); + if (numOperands == func->getNumParams()) { + ContextAwareExpressionRunner runner(module, mode, maxDepth); + runner.depth = depth + 1; + for (Index i = 0; i < numOperands; ++i) { + auto argFlow = visit(curr->operands[i]); + if (!argFlow.breaking() && argFlow.values.isConcrete()) { + runner.localValues[i] = std::move(argFlow.values); + } + } + auto retFlow = runner.visit(func->body); + if (retFlow.breakTo == RETURN_FLOW) { + return Flow(std::move(retFlow.values)); + } else if (!retFlow.breaking()) { + return retFlow; } - } - auto retFlow = runner.visit(func->body); - if (retFlow.breakTo == RETURN_FLOW) { - return Flow(std::move(retFlow.values)); - } else if (!retFlow.breaking()) { - return retFlow; } } } @@ -137,7 +152,7 @@ Flow ContextAwareExpressionRunner::visitLocalGet(LocalGet* curr) { } Flow ContextAwareExpressionRunner::visitLocalSet(LocalSet* curr) { - if (mode == Mode::EVALUATE) { + if (isEvaluate()) { // If we are evaluating and not replacing the expression, see if there is // a value flowing through a tee. if (curr->type.isConcrete()) { @@ -169,7 +184,7 @@ Flow ContextAwareExpressionRunner::visitGlobalGet(GlobalGet* curr) { } Flow ContextAwareExpressionRunner::visitGlobalSet(GlobalSet* curr) { - if (mode == Mode::EVALUATE) { + if (isEvaluate()) { // If we are evaluating and not replacing the expression, remember the // constant value set, if any, for subsequent gets. auto* global = module->getGlobalOrNull(curr->name); diff --git a/test/emcc_O2_hello_world.fromasm b/test/emcc_O2_hello_world.fromasm index f382f9a5586..751eebe6fef 100644 --- a/test/emcc_O2_hello_world.fromasm +++ b/test/emcc_O2_hello_world.fromasm @@ -8768,6 +8768,10 @@ ) ) (func $_fwrite (; 29 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32) + (local $2 i32) + (local.set $2 + (local.get $0) + ) (if (result i32) (block (result i32) (drop @@ -8776,10 +8780,10 @@ ) ) (i32.ne - (local.get $0) + (local.get $2) (local.tee $1 (call $___fwritex - (local.get $0) + (local.get $2) (local.get $1) ) ) diff --git a/test/emcc_O2_hello_world.fromasm.clamp b/test/emcc_O2_hello_world.fromasm.clamp index f382f9a5586..751eebe6fef 100644 --- a/test/emcc_O2_hello_world.fromasm.clamp +++ b/test/emcc_O2_hello_world.fromasm.clamp @@ -8768,6 +8768,10 @@ ) ) (func $_fwrite (; 29 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32) + (local $2 i32) + (local.set $2 + (local.get $0) + ) (if (result i32) (block (result i32) (drop @@ -8776,10 +8780,10 @@ ) ) (i32.ne - (local.get $0) + (local.get $2) (local.tee $1 (call $___fwritex - (local.get $0) + (local.get $2) (local.get $1) ) ) diff --git a/test/emcc_O2_hello_world.fromasm.imprecise b/test/emcc_O2_hello_world.fromasm.imprecise index 0e00778bab5..bcec604b942 100644 --- a/test/emcc_O2_hello_world.fromasm.imprecise +++ b/test/emcc_O2_hello_world.fromasm.imprecise @@ -8553,6 +8553,7 @@ (local $2 i32) (local $3 i32) (local $4 i32) + (local $5 i32) (drop (i32.load offset=76 (local.tee $0 @@ -8566,23 +8567,25 @@ (if (result i32) (i32.lt_s (i32.add - (if (result i32) - (i32.ne - (local.tee $1 - (call $_strlen) - ) - (local.tee $3 + (select + (i32.div_u + (local.tee $4 (call $___fwritex - (local.get $1) + (local.tee $3 + (local.tee $2 + (call $_strlen) + ) + ) (local.get $0) ) ) + (local.get $2) ) - (i32.div_u + (i32.const 1) + (i32.ne (local.get $3) - (local.get $1) + (local.get $4) ) - (i32.const 1) ) (i32.const -1) ) @@ -8599,14 +8602,14 @@ (i32.const 10) ) (block (result i32) - (local.set $4 + (local.set $5 (i32.add (local.get $0) (i32.const 20) ) ) (i32.lt_u - (local.tee $2 + (local.tee $1 (i32.load offset=20 (local.get $0) ) @@ -8620,14 +8623,14 @@ ) (block (i32.store - (local.get $4) + (local.get $5) (i32.add - (local.get $2) + (local.get $1) (i32.const 1) ) ) (i32.store8 - (local.get $2) + (local.get $1) (i32.const 10) ) (br $do-once diff --git a/test/emcc_hello_world.fromasm b/test/emcc_hello_world.fromasm index 820fa4983c4..e860032f9c8 100644 --- a/test/emcc_hello_world.fromasm +++ b/test/emcc_hello_world.fromasm @@ -983,7 +983,7 @@ (local $6 i32) (local $7 i32) (local $8 i32) - (local.set $7 + (local.set $6 (global.get $STACKTOP) ) (global.set $STACKTOP @@ -1001,22 +1001,22 @@ ) (local.set $3 (i32.add - (local.get $7) + (local.get $6) (i32.const 120) ) ) (local.set $5 (i32.add (local.tee $4 - (local.get $7) + (local.get $6) ) (i32.const 136) ) ) - (local.set $6 + (local.set $8 (i32.add (local.tee $2 - (local.tee $8 + (local.tee $7 (i32.add (local.get $4) (i32.const 80) @@ -1039,7 +1039,7 @@ (i32.const 4) ) ) - (local.get $6) + (local.get $8) ) ) ) @@ -1056,7 +1056,7 @@ (i32.const 0) (local.get $3) (local.get $4) - (local.get $8) + (local.get $7) ) (i32.const 0) ) @@ -1067,7 +1067,7 @@ (local.get $0) ) ) - (local.set $2 + (local.set $1 (i32.load (local.get $0) ) @@ -1082,7 +1082,7 @@ (i32.store (local.get $0) (i32.and - (local.get $2) + (local.get $1) (i32.const -33) ) ) @@ -1096,11 +1096,11 @@ (local.get $0) (local.get $3) (local.get $4) - (local.get $8) + (local.get $7) ) ) (block - (local.set $6 + (local.set $2 (i32.load offset=44 (local.get $0) ) @@ -1128,16 +1128,16 @@ (i32.const 80) ) ) - (local.set $1 + (drop (call $_printf_core (local.get $0) (local.get $3) (local.get $4) - (local.get $8) + (local.get $7) ) ) (if - (local.get $6) + (local.get $2) (block (drop (call_indirect (type $i32_i32_i32_=>_i32) @@ -1162,7 +1162,7 @@ ) (i32.store offset=44 (local.get $0) - (local.get $6) + (local.get $2) ) (i32.store offset=48 (local.get $0) @@ -1191,7 +1191,7 @@ (local.get $0) ) (i32.and - (local.get $2) + (local.get $1) (i32.const 32) ) ) @@ -1201,7 +1201,7 @@ ) ) (global.set $STACKTOP - (local.get $7) + (local.get $6) ) ) (func $___fwritex (; 37 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (param $2 i32) diff --git a/test/emcc_hello_world.fromasm.clamp b/test/emcc_hello_world.fromasm.clamp index cc2e106f92d..a5be9dceb61 100644 --- a/test/emcc_hello_world.fromasm.clamp +++ b/test/emcc_hello_world.fromasm.clamp @@ -982,7 +982,7 @@ (local $6 i32) (local $7 i32) (local $8 i32) - (local.set $7 + (local.set $6 (global.get $STACKTOP) ) (global.set $STACKTOP @@ -1000,22 +1000,22 @@ ) (local.set $3 (i32.add - (local.get $7) + (local.get $6) (i32.const 120) ) ) (local.set $5 (i32.add (local.tee $4 - (local.get $7) + (local.get $6) ) (i32.const 136) ) ) - (local.set $6 + (local.set $8 (i32.add (local.tee $2 - (local.tee $8 + (local.tee $7 (i32.add (local.get $4) (i32.const 80) @@ -1038,7 +1038,7 @@ (i32.const 4) ) ) - (local.get $6) + (local.get $8) ) ) ) @@ -1055,7 +1055,7 @@ (i32.const 0) (local.get $3) (local.get $4) - (local.get $8) + (local.get $7) ) (i32.const 0) ) @@ -1066,7 +1066,7 @@ (local.get $0) ) ) - (local.set $2 + (local.set $1 (i32.load (local.get $0) ) @@ -1081,7 +1081,7 @@ (i32.store (local.get $0) (i32.and - (local.get $2) + (local.get $1) (i32.const -33) ) ) @@ -1095,11 +1095,11 @@ (local.get $0) (local.get $3) (local.get $4) - (local.get $8) + (local.get $7) ) ) (block - (local.set $6 + (local.set $2 (i32.load offset=44 (local.get $0) ) @@ -1127,16 +1127,16 @@ (i32.const 80) ) ) - (local.set $1 + (drop (call $_printf_core (local.get $0) (local.get $3) (local.get $4) - (local.get $8) + (local.get $7) ) ) (if - (local.get $6) + (local.get $2) (block (drop (call_indirect (type $i32_i32_i32_=>_i32) @@ -1161,7 +1161,7 @@ ) (i32.store offset=44 (local.get $0) - (local.get $6) + (local.get $2) ) (i32.store offset=48 (local.get $0) @@ -1190,7 +1190,7 @@ (local.get $0) ) (i32.and - (local.get $2) + (local.get $1) (i32.const 32) ) ) @@ -1200,7 +1200,7 @@ ) ) (global.set $STACKTOP - (local.get $7) + (local.get $6) ) ) (func $___fwritex (; 36 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (param $2 i32) diff --git a/test/emcc_hello_world.fromasm.imprecise b/test/emcc_hello_world.fromasm.imprecise index 1f4f83da13e..b0a91c49e5f 100644 --- a/test/emcc_hello_world.fromasm.imprecise +++ b/test/emcc_hello_world.fromasm.imprecise @@ -975,7 +975,7 @@ (local $6 i32) (local $7 i32) (local $8 i32) - (local.set $7 + (local.set $6 (global.get $STACKTOP) ) (global.set $STACKTOP @@ -993,22 +993,22 @@ ) (local.set $3 (i32.add - (local.get $7) + (local.get $6) (i32.const 120) ) ) (local.set $5 (i32.add (local.tee $4 - (local.get $7) + (local.get $6) ) (i32.const 136) ) ) - (local.set $6 + (local.set $8 (i32.add (local.tee $2 - (local.tee $8 + (local.tee $7 (i32.add (local.get $4) (i32.const 80) @@ -1031,7 +1031,7 @@ (i32.const 4) ) ) - (local.get $6) + (local.get $8) ) ) ) @@ -1048,13 +1048,13 @@ (i32.const 0) (local.get $3) (local.get $4) - (local.get $8) + (local.get $7) ) (i32.const 0) ) (i32.const -1) (block (result i32) - (local.set $2 + (local.set $1 (i32.load (local.get $0) ) @@ -1069,7 +1069,7 @@ (i32.store (local.get $0) (i32.and - (local.get $2) + (local.get $1) (i32.const -33) ) ) @@ -1083,11 +1083,11 @@ (local.get $0) (local.get $3) (local.get $4) - (local.get $8) + (local.get $7) ) ) (block - (local.set $6 + (local.set $2 (i32.load offset=44 (local.get $0) ) @@ -1115,16 +1115,16 @@ (i32.const 80) ) ) - (local.set $1 + (drop (call $_printf_core (local.get $0) (local.get $3) (local.get $4) - (local.get $8) + (local.get $7) ) ) (if - (local.get $6) + (local.get $2) (block (drop (call_indirect (type $i32_i32_i32_=>_i32) @@ -1144,7 +1144,7 @@ ) (i32.store offset=44 (local.get $0) - (local.get $6) + (local.get $2) ) (i32.store offset=48 (local.get $0) @@ -1173,7 +1173,7 @@ (local.get $0) ) (i32.and - (local.get $2) + (local.get $1) (i32.const 32) ) ) @@ -1183,7 +1183,7 @@ ) ) (global.set $STACKTOP - (local.get $7) + (local.get $6) ) ) (func $___fwritex (; 36 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (param $2 i32) diff --git a/test/memorygrowth.fromasm b/test/memorygrowth.fromasm index b5ecbce4c33..14ff6a9e086 100644 --- a/test/memorygrowth.fromasm +++ b/test/memorygrowth.fromasm @@ -8737,6 +8737,10 @@ ) ) (func $bb (; 26 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32) + (local $2 i32) + (local.set $2 + (local.get $0) + ) (if (result i32) (block (result i32) (drop @@ -8745,13 +8749,13 @@ ) ) (i32.ne + (local.get $2) (local.tee $1 (call $Wa - (local.get $0) + (local.get $2) (local.get $1) ) ) - (local.get $0) ) ) (if (result i32) diff --git a/test/memorygrowth.fromasm.clamp b/test/memorygrowth.fromasm.clamp index b5ecbce4c33..14ff6a9e086 100644 --- a/test/memorygrowth.fromasm.clamp +++ b/test/memorygrowth.fromasm.clamp @@ -8737,6 +8737,10 @@ ) ) (func $bb (; 26 ;) (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32) + (local $2 i32) + (local.set $2 + (local.get $0) + ) (if (result i32) (block (result i32) (drop @@ -8745,13 +8749,13 @@ ) ) (i32.ne + (local.get $2) (local.tee $1 (call $Wa - (local.get $0) + (local.get $2) (local.get $1) ) ) - (local.get $0) ) ) (if (result i32) diff --git a/test/memorygrowth.fromasm.imprecise b/test/memorygrowth.fromasm.imprecise index 4c1ce808d03..0e91ad865ec 100644 --- a/test/memorygrowth.fromasm.imprecise +++ b/test/memorygrowth.fromasm.imprecise @@ -8591,6 +8591,7 @@ (local $0 i32) (local $1 i32) (local $2 i32) + (local $3 i32) (drop (i32.load offset=76 (local.tee $0 @@ -8604,23 +8605,25 @@ (if (result i32) (i32.lt_s (i32.add - (if (result i32) - (i32.ne - (local.tee $2 + (select + (i32.div_u + (local.tee $3 (call $Wa - (local.tee $1 - (call $Za) + (local.tee $2 + (local.tee $1 + (call $Za) + ) ) (local.get $0) ) ) (local.get $1) ) - (i32.div_u + (i32.const 1) + (i32.ne (local.get $2) - (local.get $1) + (local.get $3) ) - (i32.const 1) ) (i32.const -1) ) diff --git a/test/passes/dae-optimizing.txt b/test/passes/dae-optimizing.txt index 5dbcd31740e..e20cb2d1c25 100644 --- a/test/passes/dae-optimizing.txt +++ b/test/passes/dae-optimizing.txt @@ -29,7 +29,7 @@ ) (f32.const 1) ) - (f32.const 0) + (call $1) ) ) (i32.const -11) diff --git a/test/unit.fromasm.clamp b/test/unit.fromasm.clamp index f60bc41605f..d3c3c7b1473 100644 --- a/test/unit.fromasm.clamp +++ b/test/unit.fromasm.clamp @@ -1165,6 +1165,11 @@ (local.get $1) ) (func $keepAlive (; 59 ;) (; has Stack IR ;) + (drop + (call $f64-to-int + (f64.const 100) + ) + ) (call_indirect (type $i32_=>_none) (i32.const 0) (i32.const 17) From ec0a93d3b10a47c80b07efc17d39e1a4932838da Mon Sep 17 00:00:00 2001 From: dcode Date: Sat, 21 Mar 2020 20:23:35 +0100 Subject: [PATCH 15/27] update API, add test --- src/binaryen-c.cpp | 8 ++++++++ src/binaryen-c.h | 8 ++++++++ src/js/binaryen.js-post.js | 4 +++- test/binaryen.js/expressionrunner.js | 20 +++++++++++++++++++- test/binaryen.js/expressionrunner.js.txt | 14 ++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index eef4776051d..3811c1f8431 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4824,6 +4824,14 @@ ExpressionRunnerMode ExpressionRunnerModeReplace() { return ContextAwareExpressionRunner::Mode::REPLACE; } +ExpressionRunnerMode ExpressionRunnerModeEvaluateDeterministic() { + return ContextAwareExpressionRunner::Mode::EVALUATE_DETERMINISTIC; +} + +ExpressionRunnerMode ExpressionRunnerModeReplaceDeterministic() { + return ContextAwareExpressionRunner::Mode::REPLACE_DETERMINISTIC; +} + ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, ExpressionRunnerMode mode, BinaryenIndex maxDepth) { diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 0199ffdfd7d..357d2aa65dd 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1641,6 +1641,14 @@ BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeEvaluate(); // those of `local.tee`s for example must be retained. BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeReplace(); +// Like ExpressionRunnerModeEvaluate, excluding potentially non-deterministic +// traversal in function-parallel scenarios. +BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeEvaluateDeterministic(); + +// Like ExpressionRunnerModeReplaceDeterministic, excluding potentially +// non-deterministic traversal in function-parallel scenarios. +BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeReplaceDeterministic(); + // Creates an ExpressionRunner instance BINARYEN_API ExpressionRunnerRef ExpressionRunnerCreate( BinaryenModuleRef module, ExpressionRunnerMode mode, BinaryenIndex maxDepth); diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index ab4deea7307..246b57555c9 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -481,7 +481,9 @@ function initializeConstants() { // ExpressionRunner modes Module['ExpressionRunner']['Mode'] = { 'Evaluate': Module['_ExpressionRunnerModeEvaluate'](), - 'Replace': Module['_ExpressionRunnerModeReplace']() + 'Replace': Module['_ExpressionRunnerModeReplace'](), + 'EvaluateDeterministic': Module['_ExpressionRunnerModeEvaluateDeterministic'](), + 'ReplaceDeterministic': Module['_ExpressionRunnerModeReplaceDeterministic']() }; } diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index 8d1887c3f18..65249e88f30 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -1,8 +1,13 @@ +var Mode = binaryen.ExpressionRunner.Mode; +console.log("// Mode.Evaluate = " + Mode.Evaluate); +console.log("// Mode.Replace = " + Mode.Replace); +console.log("// Mode.EvaluateDeterministic = " + Mode.EvaluateDeterministic); +console.log("// Mode.ReplaceDeterministic = " + Mode.ReplaceDeterministic); + binaryen.setAPITracing(true); var module = new binaryen.Module(); module.addGlobal("aGlobal", binaryen.i32, true, module.i32.const(0)); -var Mode = binaryen.ExpressionRunner.Mode; // Should evaluate down to a constant var runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); @@ -114,4 +119,17 @@ expr = runner.runAndDispose( ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":8}'); +// Should not attempt to traverse into functions if required to be deterministic +runner = new binaryen.ExpressionRunner(module, Mode.EvaluateDeterministic); +expr = runner.runAndDispose( + module.i32.add( + module.i32.const(1), + module.call("add", [ + module.i32.const(3), + module.i32.const(4) + ], binaryen.i32) + ) +); +assert(expr === 0); + binaryen.setAPITracing(false); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index 13a26535d35..da841726e25 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -1,3 +1,7 @@ +// Mode.Evaluate = 0 +// Mode.Replace = 1 +// Mode.EvaluateDeterministic = 2 +// Mode.ReplaceDeterministic = 3 // beginning a Binaryen API trace #include #include @@ -118,6 +122,16 @@ int main() { BinaryenExpressionGetId(expressions[51]); BinaryenExpressionGetType(expressions[51]); BinaryenConstGetValueI32(expressions[51]); + the_runner = ExpressionRunnerCreate(the_module, 2, 50); + expressions[52] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[53] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); + expressions[54] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + { + BinaryenExpressionRef operands[] = { expressions[53], expressions[54] }; + expressions[55] = BinaryenCall(the_module, "add", operands, 2, 2); + } + expressions[56] = BinaryenBinary(the_module, 0, expressions[52], expressions[55]); + ExpressionRunnerRunAndDispose(the_runner, expressions[56]); return 0; } // ending a Binaryen API trace From 1ceb5b5dc945490045de6a53c7b802c899cb3ba5 Mon Sep 17 00:00:00 2001 From: dcode Date: Sat, 21 Mar 2020 21:23:53 +0100 Subject: [PATCH 16/27] retrigger CI From d145cfccd2dcca7abc71622acd74ce1bc5c680f0 Mon Sep 17 00:00:00 2001 From: dcode Date: Mon, 23 Mar 2020 01:11:14 +0100 Subject: [PATCH 17/27] fix comment --- src/binaryen-c.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 357d2aa65dd..485e8fb65ad 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1645,8 +1645,8 @@ BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeReplace(); // traversal in function-parallel scenarios. BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeEvaluateDeterministic(); -// Like ExpressionRunnerModeReplaceDeterministic, excluding potentially -// non-deterministic traversal in function-parallel scenarios. +// Like ExpressionRunnerModeReplace, excluding potentially non-deterministic +// traversal in function-parallel scenarios. BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeReplaceDeterministic(); // Creates an ExpressionRunner instance From 153d10f8320742cbf3957653c0f79b0e688ce221 Mon Sep 17 00:00:00 2001 From: dcode Date: Tue, 24 Mar 2020 02:41:54 +0100 Subject: [PATCH 18/27] address review comments --- src/binaryen-c.cpp | 24 +- src/binaryen-c.h | 25 +- src/js/binaryen.js-post.js | 18 +- src/passes/Precompute.cpp | 12 +- src/wasm-interpreter.h | 47 ++- src/wasm/CMakeLists.txt | 1 - ...terpreter-ContextAwareExpressionRunner.cpp | 276 ------------------ src/wasm/wasm-interpreter.cpp | 264 +++++++++++++++++ test/binaryen.js/expressionrunner.js | 29 +- test/binaryen.js/expressionrunner.js.txt | 7 +- 10 files changed, 341 insertions(+), 362 deletions(-) delete mode 100644 src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 3811c1f8431..895d0fafc42 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4816,32 +4816,28 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, // ========= ExpressionRunner ========= // -ExpressionRunnerMode ExpressionRunnerModeEvaluate() { - return ContextAwareExpressionRunner::Mode::EVALUATE; +ExpressionRunnerFlags ExpressionRunnerFlagsDefault() { + return ContextAwareExpressionRunner::FlagValues::DEFAULT; } -ExpressionRunnerMode ExpressionRunnerModeReplace() { - return ContextAwareExpressionRunner::Mode::REPLACE; +ExpressionRunnerFlags ExpressionRunnerFlagsReplace() { + return ContextAwareExpressionRunner::FlagValues::REPLACE; } -ExpressionRunnerMode ExpressionRunnerModeEvaluateDeterministic() { - return ContextAwareExpressionRunner::Mode::EVALUATE_DETERMINISTIC; -} - -ExpressionRunnerMode ExpressionRunnerModeReplaceDeterministic() { - return ContextAwareExpressionRunner::Mode::REPLACE_DETERMINISTIC; +ExpressionRunnerFlags ExpressionRunnerFlagsParallel() { + return ContextAwareExpressionRunner::FlagValues::PARALLEL; } ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, - ExpressionRunnerMode mode, + ExpressionRunnerFlags flags, BinaryenIndex maxDepth) { if (tracing) { - std::cout << " the_runner = ExpressionRunnerCreate(the_module, " << mode + std::cout << " the_runner = ExpressionRunnerCreate(the_module, " << flags << ", " << maxDepth << ");\n"; } auto* wasm = (Module*)module; - return ExpressionRunnerRef(new ContextAwareExpressionRunner( - wasm, ContextAwareExpressionRunner::Mode(mode), maxDepth)); + return ExpressionRunnerRef( + new ContextAwareExpressionRunner(wasm, flags, maxDepth)); } int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 485e8fb65ad..f0d58341501 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1631,27 +1631,28 @@ typedef class wasm::ContextAwareExpressionRunner* ExpressionRunnerRef; typedef struct ContextAwareExpressionRunner* ExpressionRunnerRef; #endif -typedef uint32_t ExpressionRunnerMode; +typedef uint32_t ExpressionRunnerFlags; // Just evaluate the expression, so we can ignore some side effects like those // of a `local.tee`, but not others like traps. -BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeEvaluate(); +BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsDefault(); // We are going to replace the expression afterwards, so side effects including // those of `local.tee`s for example must be retained. -BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeReplace(); +BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsReplace(); -// Like ExpressionRunnerModeEvaluate, excluding potentially non-deterministic -// traversal in function-parallel scenarios. -BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeEvaluateDeterministic(); - -// Like ExpressionRunnerModeReplace, excluding potentially non-deterministic -// traversal in function-parallel scenarios. -BINARYEN_API ExpressionRunnerMode ExpressionRunnerModeReplaceDeterministic(); +// We are going to execute the runner in a function-parallel scenario, so we +// cannot perform traversal of nodes that might become modified +// non-deterministically, like function calls, where the called function might +// or might not have been optimized already to something we can traverse +// successfully. +BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsParallel(); // Creates an ExpressionRunner instance -BINARYEN_API ExpressionRunnerRef ExpressionRunnerCreate( - BinaryenModuleRef module, ExpressionRunnerMode mode, BinaryenIndex maxDepth); +BINARYEN_API ExpressionRunnerRef +ExpressionRunnerCreate(BinaryenModuleRef module, + ExpressionRunnerFlags flags, + BinaryenIndex maxDepth); // Sets a known local value to use. Order matters if expressions have side // effects. Returns `true` if the expression actually evaluates to a constant. diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 246b57555c9..5e09caea01d 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -478,12 +478,11 @@ function initializeConstants() { Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name](); }); - // ExpressionRunner modes - Module['ExpressionRunner']['Mode'] = { - 'Evaluate': Module['_ExpressionRunnerModeEvaluate'](), - 'Replace': Module['_ExpressionRunnerModeReplace'](), - 'EvaluateDeterministic': Module['_ExpressionRunnerModeEvaluateDeterministic'](), - 'ReplaceDeterministic': Module['_ExpressionRunnerModeReplaceDeterministic']() + // ExpressionRunner flags + Module['ExpressionRunner']['Flags'] = { + 'Default': Module['_ExpressionRunnerFlagsDefault'](), + 'Replace': Module['_ExpressionRunnerFlagsReplace'](), + 'Parallel': Module['_ExpressionRunnerFlagsParallel']() }; } @@ -2399,9 +2398,10 @@ Module['Relooper'] = function(module) { }; // 'ExpressionRunner' interface -Module['ExpressionRunner'] = function(module, mode, maxDepth) { - if (typeof maxDepth === "undefined") maxDepth = 50; // default used by precompute - var runner = Module['_ExpressionRunnerCreate'](module['ptr'], mode, maxDepth); +Module['ExpressionRunner'] = function(module, flags, maxDepth) { + if (typeof flags === 'undefined') flags = Module['ExpressionRunner']['Flags']['Default']; + if (typeof maxDepth === 'undefined') maxDepth = 50; // default used by precompute + var runner = Module['_ExpressionRunnerCreate'](module['ptr'], flags, maxDepth); this['ptr'] = runner; this['setLocalValue'] = function(index, valueExpr) { diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 8b6b8fe3a16..93f9fb541d2 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -40,7 +40,8 @@ namespace wasm { using GetValues = ContextAwareExpressionRunner::GetValues; -using Mode = ContextAwareExpressionRunner::Mode; +using FlagValues = ContextAwareExpressionRunner::FlagValues; +using Flags = ContextAwareExpressionRunner::Flags; using NonconstantException = ContextAwareExpressionRunner::NonconstantException; // Limit evaluation depth for 2 reasons: first, it is highly unlikely @@ -173,12 +174,13 @@ struct Precompute private: // Precompute an expression, returning a flow, which may be a constant - // (that we can replace the expression with if replaceExpression is set). + // (that we can replace the expression with if the REPLACE flag is set). Flow precomputeExpression(Expression* curr, - Mode mode = Mode::REPLACE_DETERMINISTIC) { + Flags flags = FlagValues::REPLACE | + FlagValues::PARALLEL) { try { return ContextAwareExpressionRunner( - getModule(), mode, MAX_DEPTH, &getValues) + getModule(), flags, MAX_DEPTH, &getValues) .visit(curr); } catch (NonconstantException&) { return Flow(ContextAwareExpressionRunner::NONCONSTANT_FLOW); @@ -195,7 +197,7 @@ struct Precompute Literals precomputeValue(Expression* curr) { // Note that we do not intent to replace the expression, as we just care // about the value here. - Flow flow = precomputeExpression(curr, Mode::EVALUATE_DETERMINISTIC); + Flow flow = precomputeExpression(curr, FlagValues::PARALLEL); if (flow.breaking()) { return {}; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index aa429dbdd5e..4e4cb38dee9 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2237,7 +2237,7 @@ class ModuleInstance // Evaluates an expression given its surrounding context. Errors if we hit // anything that can't be evaluated down to a constant. -class ContextAwareExpressionRunner +class ContextAwareExpressionRunner final : public ExpressionRunner { public: @@ -2250,20 +2250,24 @@ class ContextAwareExpressionRunner // only do so when it has no side effects. When we don't care about // replacing the expression, we just want to know if it will contain a known // constant. - enum Mode { - // Just evaluate the expression, so we can ignore some side effects like - // those of a `local.tee`, but not others like traps. - EVALUATE, + enum FlagValues { + // Just evaluate the expression by default, where we can ignore some side + // effects like those of a `local.tee`, but not others like traps. + DEFAULT = 0, // We are going to replace the expression afterwards, so side effects // including those of `local.tee`s for example must be retained. - REPLACE, - // Like EVALUATE, excluding potentially non-deterministic traversal in - // function-parallel scenarios. - EVALUATE_DETERMINISTIC, - // Like REPLACE, excluding potentially non-deterministic traversal in - // function-parallel scenarios. - REPLACE_DETERMINISTIC + REPLACE = 1 << 0, + // We are going to execute the runner in a function-parallel scenario, so we + // cannot perform traversal of nodes that might become modified + // non-deterministically, like function calls, where the called function + // might or might not have been optimized already to something we can + // traverse successfully. + PARALLEL = 1 << 1 }; + // Flags indicating special requirements, for example whether we are just + // evaluating (default), also going to replace the expression afterwards or + // executing in a function-parallel scenario. See FlagValues. + typedef uint32_t Flags; // Special break target indicating a flow not evaluating to a constant static const Name NONCONSTANT_FLOW; @@ -2273,14 +2277,10 @@ class ContextAwareExpressionRunner }; // TODO: use a flow with a special name, as this is likely very slow ContextAwareExpressionRunner(Module* module, - Mode mode, + Flags flags, Index maxDepth, GetValues* getValues = nullptr); - virtual ~ContextAwareExpressionRunner(); - // ^ Necessary because we `delete` the instance in binaryen-c.cpp in - // ExpressionRunnerRunAndDispose. - // Gets the module this runner belongs to. Module* getModule(); @@ -2342,15 +2342,10 @@ class ContextAwareExpressionRunner // large, hence a reference to the respective map (must not be mutated). GetValues* getValues; - // Whether we are just evaluating or also going to replace the expression - // afterwards. - Mode mode; - - // Whether `mode` is any of the `EVALUATE` modes. - bool isEvaluate(); - - // Whether `mode` is any of the `DETERMINISTIC` modes. - bool isDeterministic(); + // Flags indicating special requirements, for example whether we are just + // evaluating (default), also going to replace the expression afterwards or + // executing in a function-parallel scenario. + Flags flags; }; } // namespace wasm diff --git a/src/wasm/CMakeLists.txt b/src/wasm/CMakeLists.txt index ac3b574e864..916fbfb6361 100644 --- a/src/wasm/CMakeLists.txt +++ b/src/wasm/CMakeLists.txt @@ -6,7 +6,6 @@ set(wasm_SOURCES wasm-emscripten.cpp wasm-debug.cpp wasm-interpreter.cpp - wasm-interpreter-ContextAwareExpressionRunner.cpp wasm-io.cpp wasm-s-parser.cpp wasm-stack.cpp diff --git a/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp b/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp deleted file mode 100644 index b3ea99ca383..00000000000 --- a/src/wasm/wasm-interpreter-ContextAwareExpressionRunner.cpp +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright 2020 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "wasm-interpreter.h" - -namespace wasm { - -const Name ContextAwareExpressionRunner::NONCONSTANT_FLOW = - "Binaryen|nonconstant"; - -ContextAwareExpressionRunner::ContextAwareExpressionRunner(Module* module, - Mode mode, - Index maxDepth, - GetValues* getValues) - : ExpressionRunner(maxDepth), module(module), - getValues(getValues), mode(mode) { - // Trap instead of aborting if we hit an invalid expression. - trapIfInvalid = true; -} - -ContextAwareExpressionRunner::~ContextAwareExpressionRunner() {} - -Module* ContextAwareExpressionRunner::getModule() { return module; } - -bool ContextAwareExpressionRunner::isEvaluate() { - return mode == Mode::EVALUATE || mode == Mode::EVALUATE_DETERMINISTIC; -} - -bool ContextAwareExpressionRunner::isDeterministic() { - return mode == Mode::EVALUATE_DETERMINISTIC || - mode == Mode::REPLACE_DETERMINISTIC; -} - -bool ContextAwareExpressionRunner::setLocalValue(Index index, - Literals& values) { - if (values.isConcrete()) { - localValues[index] = values; - return true; - } - localValues.erase(index); - return false; -} - -bool ContextAwareExpressionRunner::setLocalValue(Index index, - Expression* expr) { - auto setFlow = visit(expr); - if (!setFlow.breaking()) { - return setLocalValue(index, setFlow.values); - } - return false; -} - -bool ContextAwareExpressionRunner::setGlobalValue(Name name, Literals& values) { - if (values.isConcrete()) { - globalValues[name] = values; - return true; - } - globalValues.erase(name); - return false; -} - -bool ContextAwareExpressionRunner::setGlobalValue(Name name, Expression* expr) { - auto setFlow = visit(expr); - if (!setFlow.breaking()) { - return setGlobalValue(name, setFlow.values); - } - return false; -} - -Flow ContextAwareExpressionRunner::visitLoop(Loop* curr) { - // loops might be infinite, so must be careful - // but we can't tell if non-infinite, since we don't have state, so loops - // are just impossible to optimize for now - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitCall(Call* curr) { - // Traverse into functions using the same mode, which we can also do - // when replacing as long as the function does not have any side effects. - // Might yield something useful for simple functions like `clamp`, sometimes - // even if arguments are only partially constant or not constant at all. - - // Skip traversing into functions if in a function-parallel scenario, where - // functions may or may not have been optimized already to something we can - // traverse successfully. - if (!isDeterministic()) { - // Note that we are not a validator, so we skip calls to functions that do - // not exist yet or where the signature does not match. - auto* func = module->getFunctionOrNull(curr->target); - if (func != nullptr && !func->imported()) { - if (func->sig.results.isConcrete()) { - auto numOperands = curr->operands.size(); - if (numOperands == func->getNumParams()) { - ContextAwareExpressionRunner runner(module, mode, maxDepth); - runner.depth = depth + 1; - for (Index i = 0; i < numOperands; ++i) { - auto argFlow = visit(curr->operands[i]); - if (!argFlow.breaking() && argFlow.values.isConcrete()) { - runner.localValues[i] = std::move(argFlow.values); - } - } - auto retFlow = runner.visit(func->body); - if (retFlow.breakTo == RETURN_FLOW) { - return Flow(std::move(retFlow.values)); - } else if (!retFlow.breaking()) { - return retFlow; - } - } - } - } - } - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitCallIndirect(CallIndirect* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitLocalGet(LocalGet* curr) { - // Check if a constant value has been set in the context of this runner. - auto iter = localValues.find(curr->index); - if (iter != localValues.end()) { - return Flow(std::move(iter->second)); - } - // If not the case, see if the calling pass did compute the value of this - // specific `local.get` in an earlier step already. This is a fallback - // targeting the precompute pass specifically, which already did this work, - // but is not applicable when the runner is used via the C-API for example. - if (getValues != nullptr) { - auto iter = getValues->find(curr); - if (iter != getValues->end()) { - auto values = iter->second; - if (values.isConcrete()) { - return Flow(std::move(values)); - } - } - } - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitLocalSet(LocalSet* curr) { - if (isEvaluate()) { - // If we are evaluating and not replacing the expression, see if there is - // a value flowing through a tee. - if (curr->type.isConcrete()) { - assert(curr->isTee()); - return visit(curr->value); - } - // Otherwise remember the constant value set, if any, for subsequent gets. - if (setLocalValue(curr->index, curr->value)) { - return Flow(); - } - } - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitGlobalGet(GlobalGet* curr) { - auto* global = module->getGlobalOrNull(curr->name); - if (global != nullptr) { - // Check if the global has an immutable value anyway - if (!global->imported() && !global->mutable_) { - return visit(global->init); - } - // Check if a constant value has been set in the context of this runner. - auto iter = globalValues.find(curr->name); - if (iter != globalValues.end()) { - return Flow(std::move(iter->second)); - } - } - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitGlobalSet(GlobalSet* curr) { - if (isEvaluate()) { - // If we are evaluating and not replacing the expression, remember the - // constant value set, if any, for subsequent gets. - auto* global = module->getGlobalOrNull(curr->name); - if (global != nullptr && global->mutable_) { - if (setGlobalValue(curr->name, curr->value)) { - return Flow(); - } - } - } - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitLoad(Load* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitStore(Store* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitAtomicRMW(AtomicRMW* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitAtomicCmpxchg(AtomicCmpxchg* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitAtomicWait(AtomicWait* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitAtomicNotify(AtomicNotify* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitSIMDLoad(SIMDLoad* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitMemoryInit(MemoryInit* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitDataDrop(DataDrop* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitMemoryCopy(MemoryCopy* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitMemoryFill(MemoryFill* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitHost(Host* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitTry(Try* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitThrow(Throw* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitRethrow(Rethrow* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitBrOnExn(BrOnExn* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitPush(Push* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow ContextAwareExpressionRunner::visitPop(Pop* curr) { - return Flow(NONCONSTANT_FLOW); -} - -void ContextAwareExpressionRunner::trap(const char* why) { - throw NonconstantException(); -} - -} // namespace wasm diff --git a/src/wasm/wasm-interpreter.cpp b/src/wasm/wasm-interpreter.cpp index ad4b565fe71..327d5130e2e 100644 --- a/src/wasm/wasm-interpreter.cpp +++ b/src/wasm/wasm-interpreter.cpp @@ -1,3 +1,19 @@ +/* + * Copyright 2020 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "wasm-interpreter.h" namespace wasm { @@ -19,4 +35,252 @@ void Indenter::print() { } #endif // WASM_INTERPRETER_DEBUG +// +// ContextAwareExpressionRunner +// + +const Name ContextAwareExpressionRunner::NONCONSTANT_FLOW = + "Binaryen|nonconstant"; + +ContextAwareExpressionRunner::ContextAwareExpressionRunner(Module* module, + Flags flags, + Index maxDepth, + GetValues* getValues) + : ExpressionRunner(maxDepth), module(module), + getValues(getValues), flags(flags) { + // Trap instead of aborting if we hit an invalid expression. + trapIfInvalid = true; +} + +Module* ContextAwareExpressionRunner::getModule() { return module; } + +bool ContextAwareExpressionRunner::setLocalValue(Index index, + Literals& values) { + if (values.isConcrete()) { + localValues[index] = values; + return true; + } + localValues.erase(index); + return false; +} + +bool ContextAwareExpressionRunner::setLocalValue(Index index, + Expression* expr) { + auto setFlow = visit(expr); + if (!setFlow.breaking()) { + return setLocalValue(index, setFlow.values); + } + return false; +} + +bool ContextAwareExpressionRunner::setGlobalValue(Name name, Literals& values) { + if (values.isConcrete()) { + globalValues[name] = values; + return true; + } + globalValues.erase(name); + return false; +} + +bool ContextAwareExpressionRunner::setGlobalValue(Name name, Expression* expr) { + auto setFlow = visit(expr); + if (!setFlow.breaking()) { + return setGlobalValue(name, setFlow.values); + } + return false; +} + +Flow ContextAwareExpressionRunner::visitLoop(Loop* curr) { + // loops might be infinite, so must be careful + // but we can't tell if non-infinite, since we don't have state, so loops + // are just impossible to optimize for now + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitCall(Call* curr) { + // Traverse into functions using the same mode, which we can also do + // when replacing as long as the function does not have any side effects. + // Might yield something useful for simple functions like `clamp`, sometimes + // even if arguments are only partially constant or not constant at all. + + // Only traverse into functions if not in a function-parallel scenario, where + // functions may or may not have been optimized already to something we can + // traverse successfully. + if (!(flags & FlagValues::PARALLEL)) { + // Note that we are not a validator, so we skip calls to functions that do + // not exist yet or where the signature does not match. + auto* func = module->getFunctionOrNull(curr->target); + if (func != nullptr && !func->imported()) { + if (func->sig.results.isConcrete()) { + auto numOperands = curr->operands.size(); + if (numOperands == func->getNumParams()) { + ContextAwareExpressionRunner runner(module, flags, maxDepth); + runner.depth = depth + 1; + for (Index i = 0; i < numOperands; ++i) { + auto argFlow = visit(curr->operands[i]); + if (!argFlow.breaking() && argFlow.values.isConcrete()) { + runner.localValues[i] = std::move(argFlow.values); + } + } + auto retFlow = runner.visit(func->body); + if (retFlow.breakTo == RETURN_FLOW) { + return Flow(std::move(retFlow.values)); + } else if (!retFlow.breaking()) { + return retFlow; + } + } + } + } + } + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitCallIndirect(CallIndirect* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitLocalGet(LocalGet* curr) { + // Check if a constant value has been set in the context of this runner. + auto iter = localValues.find(curr->index); + if (iter != localValues.end()) { + return Flow(std::move(iter->second)); + } + // If not the case, see if the calling pass did compute the value of this + // specific `local.get` in an earlier step already. This is a fallback + // targeting the precompute pass specifically, which already did this work, + // but is not applicable when the runner is used via the C-API for example. + if (getValues != nullptr) { + auto iter = getValues->find(curr); + if (iter != getValues->end()) { + auto values = iter->second; + if (values.isConcrete()) { + return Flow(std::move(values)); + } + } + } + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitLocalSet(LocalSet* curr) { + if (!(flags & FlagValues::REPLACE)) { + // If we are evaluating and not replacing the expression, see if there is + // a value flowing through a tee. + if (curr->type.isConcrete()) { + assert(curr->isTee()); + return visit(curr->value); + } + // Otherwise remember the constant value set, if any, for subsequent gets. + if (setLocalValue(curr->index, curr->value)) { + return Flow(); + } + } + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitGlobalGet(GlobalGet* curr) { + auto* global = module->getGlobalOrNull(curr->name); + if (global != nullptr) { + // Check if the global has an immutable value anyway + if (!global->imported() && !global->mutable_) { + return visit(global->init); + } + // Check if a constant value has been set in the context of this runner. + auto iter = globalValues.find(curr->name); + if (iter != globalValues.end()) { + return Flow(std::move(iter->second)); + } + } + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitGlobalSet(GlobalSet* curr) { + if (!(flags & FlagValues::REPLACE)) { + // If we are evaluating and not replacing the expression, remember the + // constant value set, if any, for subsequent gets. + auto* global = module->getGlobalOrNull(curr->name); + if (global != nullptr && global->mutable_) { + if (setGlobalValue(curr->name, curr->value)) { + return Flow(); + } + } + } + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitLoad(Load* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitStore(Store* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitAtomicRMW(AtomicRMW* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitAtomicCmpxchg(AtomicCmpxchg* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitAtomicWait(AtomicWait* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitAtomicNotify(AtomicNotify* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitSIMDLoad(SIMDLoad* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitMemoryInit(MemoryInit* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitDataDrop(DataDrop* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitMemoryCopy(MemoryCopy* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitMemoryFill(MemoryFill* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitHost(Host* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitTry(Try* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitThrow(Throw* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitRethrow(Rethrow* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitBrOnExn(BrOnExn* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitPush(Push* curr) { + return Flow(NONCONSTANT_FLOW); +} + +Flow ContextAwareExpressionRunner::visitPop(Pop* curr) { + return Flow(NONCONSTANT_FLOW); +} + +void ContextAwareExpressionRunner::trap(const char* why) { + throw NonconstantException(); +} + } // namespace wasm diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index 65249e88f30..0e30ed8b09a 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -1,8 +1,7 @@ -var Mode = binaryen.ExpressionRunner.Mode; -console.log("// Mode.Evaluate = " + Mode.Evaluate); -console.log("// Mode.Replace = " + Mode.Replace); -console.log("// Mode.EvaluateDeterministic = " + Mode.EvaluateDeterministic); -console.log("// Mode.ReplaceDeterministic = " + Mode.ReplaceDeterministic); +var Flags = binaryen.ExpressionRunner.Flags; +console.log("// ExpressionRunner.Flags.Default = " + Flags.Default); +console.log("// ExpressionRunner.Flags.Replace = " + Flags.Replace); +console.log("// ExpressionRunner.Flags.Parallel = " + Flags.Parallel); binaryen.setAPITracing(true); @@ -10,7 +9,7 @@ var module = new binaryen.Module(); module.addGlobal("aGlobal", binaryen.i32, true, module.i32.const(0)); // Should evaluate down to a constant -var runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); +var runner = new binaryen.ExpressionRunner(module); var expr = runner.runAndDispose( module.i32.add( module.i32.const(1), @@ -20,7 +19,7 @@ var expr = runner.runAndDispose( assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":3}'); // Should traverse control structures -runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); +runner = new binaryen.ExpressionRunner(module); expr = runner.runAndDispose( module.i32.add( module.i32.const(1), @@ -34,7 +33,7 @@ expr = runner.runAndDispose( assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":4}'); // Should be unable to evaluate a local if not explicitly specified -runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); +runner = new binaryen.ExpressionRunner(module); expr = runner.runAndDispose( module.i32.add( module.local.get(0, binaryen.i32), @@ -44,14 +43,14 @@ expr = runner.runAndDispose( assert(expr === 0); // Should handle traps properly -runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); +runner = new binaryen.ExpressionRunner(module); expr = runner.runAndDispose( module.unreachable() ); assert(expr === 0); // Should ignore `local.tee` side-effects if just evaluating the expression -runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); +runner = new binaryen.ExpressionRunner(module); expr = runner.runAndDispose( module.i32.add( module.local.tee(0, module.i32.const(4), binaryen.i32), @@ -61,7 +60,7 @@ expr = runner.runAndDispose( assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":5}'); // Should keep all side-effects if we are going to replace the expression -runner = new binaryen.ExpressionRunner(module, Mode.Replace); +runner = new binaryen.ExpressionRunner(module, Flags.Replace); expr = runner.runAndDispose( module.i32.add( module.local.tee(0, module.i32.const(4), binaryen.i32), @@ -71,7 +70,7 @@ expr = runner.runAndDispose( assert(expr === 0); // Should work with temporary values if just evaluating the expression -runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); +runner = new binaryen.ExpressionRunner(module); expr = runner.runAndDispose( module.i32.add( module.block(null, [ @@ -87,7 +86,7 @@ expr = runner.runAndDispose( assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":6}'); // Should pick up explicitly preset values -runner = new binaryen.ExpressionRunner(module, Mode.Replace); +runner = new binaryen.ExpressionRunner(module, Flags.Replace); assert(runner.setLocalValue(0, module.i32.const(3))); assert(runner.setGlobalValue("aGlobal", module.i32.const(4))); expr = runner.runAndDispose( @@ -99,7 +98,7 @@ expr = runner.runAndDispose( assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":7}'); // Should traverse into simple functions -runner = new binaryen.ExpressionRunner(module, Mode.Evaluate); +runner = new binaryen.ExpressionRunner(module); module.addFunction("add", binaryen.createType([ binaryen.i32, binaryen.i32 ]), binaryen.i32, [], module.block(null, [ module.i32.add( @@ -120,7 +119,7 @@ expr = runner.runAndDispose( assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":8}'); // Should not attempt to traverse into functions if required to be deterministic -runner = new binaryen.ExpressionRunner(module, Mode.EvaluateDeterministic); +runner = new binaryen.ExpressionRunner(module, Flags.Parallel); expr = runner.runAndDispose( module.i32.add( module.i32.const(1), diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index da841726e25..147e6588a13 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -1,7 +1,6 @@ -// Mode.Evaluate = 0 -// Mode.Replace = 1 -// Mode.EvaluateDeterministic = 2 -// Mode.ReplaceDeterministic = 3 +// ExpressionRunner.Flags.Default = 0 +// ExpressionRunner.Flags.Replace = 1 +// ExpressionRunner.Flags.Parallel = 2 // beginning a Binaryen API trace #include #include From 7d51f30742bf7cb13953aed6c42d4502c0e6f983 Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 26 Mar 2020 00:41:14 +0100 Subject: [PATCH 19/27] address review comments --- src/binaryen-c.cpp | 25 ++-- src/binaryen-c.h | 25 ++-- src/js/binaryen.js-post.js | 9 +- src/passes/Precompute.cpp | 36 +++--- src/wasm-interpreter.h | 69 +++++----- src/wasm/wasm-interpreter.cpp | 156 +++++++++++------------ test/binaryen.js/expressionrunner.js | 20 +-- test/binaryen.js/expressionrunner.js.txt | 9 +- 8 files changed, 176 insertions(+), 173 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 895d0fafc42..73e16533564 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4816,16 +4816,20 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, // ========= ExpressionRunner ========= // +BinaryenIndex ExpressionRunnerDefaultMaxDepth() { + return LinearExpressionRunner::DEFAULT_MAX_DEPTH; +} + ExpressionRunnerFlags ExpressionRunnerFlagsDefault() { - return ContextAwareExpressionRunner::FlagValues::DEFAULT; + return LinearExpressionRunner::FlagValues::DEFAULT; } -ExpressionRunnerFlags ExpressionRunnerFlagsReplace() { - return ContextAwareExpressionRunner::FlagValues::REPLACE; +ExpressionRunnerFlags ExpressionRunnerFlagsPreserveSideeffects() { + return LinearExpressionRunner::FlagValues::PRESERVE_SIDEEFFECTS; } -ExpressionRunnerFlags ExpressionRunnerFlagsParallel() { - return ContextAwareExpressionRunner::FlagValues::PARALLEL; +ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls() { + return LinearExpressionRunner::FlagValues::TRAVERSE_CALLS; } ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, @@ -4836,8 +4840,7 @@ ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, << ", " << maxDepth << ");\n"; } auto* wasm = (Module*)module; - return ExpressionRunnerRef( - new ContextAwareExpressionRunner(wasm, flags, maxDepth)); + return ExpressionRunnerRef(new LinearExpressionRunner(wasm, flags, maxDepth)); } int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, @@ -4848,7 +4851,7 @@ int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, << ", expressions[" << expressions[value] << "]);\n"; } - auto* R = (ContextAwareExpressionRunner*)runner; + auto* R = (LinearExpressionRunner*)runner; return R->setLocalValue(index, value); } @@ -4861,21 +4864,21 @@ int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, std::cout << ", expressions[" << expressions[value] << "]);\n"; } - auto* R = (ContextAwareExpressionRunner*)runner; + auto* R = (LinearExpressionRunner*)runner; return R->setGlobalValue(name, value); } BinaryenExpressionRef ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, BinaryenExpressionRef expr) { - auto* R = (ContextAwareExpressionRunner*)runner; + auto* R = (LinearExpressionRunner*)runner; Expression* ret = nullptr; try { auto flow = R->visit(expr); if (!flow.breaking() && !flow.values.empty()) { ret = flow.getConstExpression(*R->getModule()); } - } catch (ContextAwareExpressionRunner::NonconstantException&) { + } catch (LinearExpressionRunner::NonconstantException&) { } if (tracing) { diff --git a/src/binaryen-c.h b/src/binaryen-c.h index f0d58341501..82607454c4e 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1633,20 +1633,23 @@ typedef struct ContextAwareExpressionRunner* ExpressionRunnerRef; typedef uint32_t ExpressionRunnerFlags; -// Just evaluate the expression, so we can ignore some side effects like those -// of a `local.tee`, but not others like traps. +// Default maximum evaluation depth. +BINARYEN_API BinaryenIndex ExpressionRunnerDefaultMaxDepth(); + +// By default, just evaluate the expression, i.e. all we want to know is whether +// it computes down to a concrete value, where it is not necessary to preserve +// side effects like those of a `local.tee`. BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsDefault(); -// We are going to replace the expression afterwards, so side effects including -// those of `local.tee`s for example must be retained. -BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsReplace(); +// 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. +BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsPreserveSideeffects(); -// We are going to execute the runner in a function-parallel scenario, so we -// cannot perform traversal of nodes that might become modified -// non-deterministically, like function calls, where the called function might -// or might not have been optimized already to something we can traverse -// successfully. -BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsParallel(); +// 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. +BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls(); // Creates an ExpressionRunner instance BINARYEN_API ExpressionRunnerRef diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 5e09caea01d..30253deb5b6 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -478,11 +478,12 @@ function initializeConstants() { Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name](); }); - // ExpressionRunner flags + // ExpressionRunner constants + Module['ExpressionRunner']['DEFAULT_MAX_DEPTH'] = Module['_ExpressionRunnerDefaultMaxDepth'](); Module['ExpressionRunner']['Flags'] = { 'Default': Module['_ExpressionRunnerFlagsDefault'](), - 'Replace': Module['_ExpressionRunnerFlagsReplace'](), - 'Parallel': Module['_ExpressionRunnerFlagsParallel']() + 'PreserveSideeffects': Module['_ExpressionRunnerFlagsPreserveSideeffects'](), + 'TraverseCalls': Module['_ExpressionRunnerFlagsTraverseCalls']() }; } @@ -2400,7 +2401,7 @@ Module['Relooper'] = function(module) { // 'ExpressionRunner' interface Module['ExpressionRunner'] = function(module, flags, maxDepth) { if (typeof flags === 'undefined') flags = Module['ExpressionRunner']['Flags']['Default']; - if (typeof maxDepth === 'undefined') maxDepth = 50; // default used by precompute + if (typeof maxDepth === 'undefined') maxDepth = Module['ExpressionRunner']['DEFAULT_MAX_DEPTH']; var runner = Module['_ExpressionRunnerCreate'](module['ptr'], flags, maxDepth); this['ptr'] = runner; diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 93f9fb541d2..9c5a9191586 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -39,16 +39,10 @@ namespace wasm { -using GetValues = ContextAwareExpressionRunner::GetValues; -using FlagValues = ContextAwareExpressionRunner::FlagValues; -using Flags = ContextAwareExpressionRunner::Flags; -using NonconstantException = ContextAwareExpressionRunner::NonconstantException; - -// Limit evaluation depth for 2 reasons: first, it is highly unlikely -// that we can do anything useful to precompute a hugely nested expression -// (we should succed at smaller parts of it first). Second, a low limit is -// helpful to avoid platform differences in native stack sizes. -static const Index MAX_DEPTH = 50; +using FlagValues = LinearExpressionRunner::FlagValues; +using Flags = LinearExpressionRunner::Flags; +using NonconstantException = LinearExpressionRunner::NonconstantException; +using GetValues = LinearExpressionRunner::GetValues; struct Precompute : public WalkerPass< @@ -101,7 +95,7 @@ struct Precompute return; } if (flow.breaking()) { - if (flow.breakTo == ContextAwareExpressionRunner::NONCONSTANT_FLOW) { + if (flow.breakTo == LinearExpressionRunner::NONCONSTANT_FLOW) { return; } if (flow.breakTo == RETURN_FLOW) { @@ -174,16 +168,16 @@ struct Precompute private: // Precompute an expression, returning a flow, which may be a constant - // (that we can replace the expression with if the REPLACE flag is set). + // (that we can replace the expression with if the PRESERVE_SIDEEFFECTS flag + // is set). Flow precomputeExpression(Expression* curr, - Flags flags = FlagValues::REPLACE | - FlagValues::PARALLEL) { + Flags flags = FlagValues::PRESERVE_SIDEEFFECTS) { try { - return ContextAwareExpressionRunner( - getModule(), flags, MAX_DEPTH, &getValues) - .visit(curr); + LinearExpressionRunner runner(getModule(), flags); + runner.setGetValues(&getValues); + return runner.visit(curr); } catch (NonconstantException&) { - return Flow(ContextAwareExpressionRunner::NONCONSTANT_FLOW); + return Flow(LinearExpressionRunner::NONCONSTANT_FLOW); } } @@ -195,9 +189,9 @@ struct Precompute // will have value 1 which we can optimize here, but in precomputeExpression // we could not do anything. Literals precomputeValue(Expression* curr) { - // Note that we do not intent to replace the expression, as we just care - // about the value here. - Flow flow = precomputeExpression(curr, FlagValues::PARALLEL); + // Note that we do not set PRESERVE_SIDEEFFECTS, as we just care about the + // value here. + Flow flow = precomputeExpression(curr, FlagValues::DEFAULT); if (flow.breaking()) { return {}; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 4e4cb38dee9..76f7ee70b0a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2235,10 +2235,10 @@ class ModuleInstance : ModuleInstanceBase(wasm, externalInterface) {} }; -// Evaluates an expression given its surrounding context. Errors if we hit -// anything that can't be evaluated down to a constant. -class ContextAwareExpressionRunner final - : public ExpressionRunner { +// Evaluates an expression linearly, optionally given its surrounding context. +// Errors if we hit anything that can't be evaluated down to a constant. +class LinearExpressionRunner final + : public ExpressionRunner { public: // Map of `local.get`s to their respective values. @@ -2251,53 +2251,64 @@ class ContextAwareExpressionRunner final // replacing the expression, we just want to know if it will contain a known // constant. enum FlagValues { - // Just evaluate the expression by default, where we can ignore some side - // effects like those of a `local.tee`, but not others like traps. + // By default, just evaluate the expression, i.e. all we want to know is + // 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, - // We are going to replace the expression afterwards, so side effects - // including those of `local.tee`s for example must be retained. - REPLACE = 1 << 0, - // We are going to execute the runner in a function-parallel scenario, so we - // cannot perform traversal of nodes that might become modified - // non-deterministically, like function calls, where the called function - // might or might not have been optimized already to something we can - // traverse successfully. - PARALLEL = 1 << 1 + // 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. + 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. + TRAVERSE_CALLS = 1 << 1 }; // Flags indicating special requirements, for example whether we are just // evaluating (default), also going to replace the expression afterwards or // executing in a function-parallel scenario. See FlagValues. typedef uint32_t Flags; - // Special break target indicating a flow not evaluating to a constant + // Limit evaluation depth for 2 reasons: first, it is highly unlikely + // that we can do anything useful when precomputing a hugely nested expression + // (we should succed at smaller parts of it first). Second, a low limit is + // helpful to avoid platform differences in native stack sizes. + static const Index DEFAULT_MAX_DEPTH = 50; + + // Special break target indicating a flow not evaluating to a concrete value. static const Name NONCONSTANT_FLOW; // Special exception indicating a flow not evaluating to a constant struct NonconstantException { }; // TODO: use a flow with a special name, as this is likely very slow - ContextAwareExpressionRunner(Module* module, - Flags flags, - Index maxDepth, - GetValues* getValues = nullptr); + LinearExpressionRunner(Module* module, + Flags flags = FlagValues::DEFAULT, + Index maxDepth = DEFAULT_MAX_DEPTH); // Gets the module this runner belongs to. Module* getModule(); - // Sets a known local value to use. Returns `true` if the value is actually - // constant. + // Sets the optional reference to a map of gets to constant values. + void setGetValues(GetValues* values); + + // Sets a known local value to use. Returns `true` if the value is concrete + // (i.e. not none or unreachable). bool setLocalValue(Index index, Literals& values); // Sets a known local value to use. Order matters if expressions have side - // effects. Returns `true` if the expression actually evaluates to a constant. + // effects. Returns `true` if the value is concrete (i.e. not none or + // unreachable). bool setLocalValue(Index index, Expression* expr); - // Sets a known global value to use. Returns `true` if the value is actually - // constant. + // Sets a known global value to use. Returns `true` if the value is concrete + // (i.e. not none or unreachable). bool setGlobalValue(Name name, Literals& values); // Sets a known global value to use. Order matters if expressions have side - // effects. Returns `true` if the expression actually evaluates to a constant. + // effects. Returns `true` if the value is concrete (i.e. not none or + // unreachable). bool setGlobalValue(Name name, Expression* expr); Flow visitLoop(Loop* curr); @@ -2340,11 +2351,9 @@ class ContextAwareExpressionRunner final // Optional reference to a map of gets to constant values, for example where a // pass did already compute these. Not applicable in C-API usage and possibly // large, hence a reference to the respective map (must not be mutated). - GetValues* getValues; + GetValues* getValues = nullptr; - // Flags indicating special requirements, for example whether we are just - // evaluating (default), also going to replace the expression afterwards or - // executing in a function-parallel scenario. + // Flags indicating special requirements. See FlagValues. Flags flags; }; diff --git a/src/wasm/wasm-interpreter.cpp b/src/wasm/wasm-interpreter.cpp index 327d5130e2e..b927049dcb6 100644 --- a/src/wasm/wasm-interpreter.cpp +++ b/src/wasm/wasm-interpreter.cpp @@ -36,26 +36,27 @@ void Indenter::print() { #endif // WASM_INTERPRETER_DEBUG // -// ContextAwareExpressionRunner +// LinearExpressionRunner // -const Name ContextAwareExpressionRunner::NONCONSTANT_FLOW = - "Binaryen|nonconstant"; +const Name LinearExpressionRunner::NONCONSTANT_FLOW = "Binaryen|nonconstant"; -ContextAwareExpressionRunner::ContextAwareExpressionRunner(Module* module, - Flags flags, - Index maxDepth, - GetValues* getValues) - : ExpressionRunner(maxDepth), module(module), - getValues(getValues), flags(flags) { +LinearExpressionRunner::LinearExpressionRunner(Module* module, + Flags flags, + Index maxDepth) + : ExpressionRunner(maxDepth), module(module), + flags(flags) { // Trap instead of aborting if we hit an invalid expression. trapIfInvalid = true; } -Module* ContextAwareExpressionRunner::getModule() { return module; } +Module* LinearExpressionRunner::getModule() { return module; } -bool ContextAwareExpressionRunner::setLocalValue(Index index, - Literals& values) { +void LinearExpressionRunner::setGetValues(GetValues* values) { + getValues = values; +} + +bool LinearExpressionRunner::setLocalValue(Index index, Literals& values) { if (values.isConcrete()) { localValues[index] = values; return true; @@ -64,8 +65,7 @@ bool ContextAwareExpressionRunner::setLocalValue(Index index, return false; } -bool ContextAwareExpressionRunner::setLocalValue(Index index, - Expression* expr) { +bool LinearExpressionRunner::setLocalValue(Index index, Expression* expr) { auto setFlow = visit(expr); if (!setFlow.breaking()) { return setLocalValue(index, setFlow.values); @@ -73,7 +73,7 @@ bool ContextAwareExpressionRunner::setLocalValue(Index index, return false; } -bool ContextAwareExpressionRunner::setGlobalValue(Name name, Literals& values) { +bool LinearExpressionRunner::setGlobalValue(Name name, Literals& values) { if (values.isConcrete()) { globalValues[name] = values; return true; @@ -82,7 +82,7 @@ bool ContextAwareExpressionRunner::setGlobalValue(Name name, Literals& values) { return false; } -bool ContextAwareExpressionRunner::setGlobalValue(Name name, Expression* expr) { +bool LinearExpressionRunner::setGlobalValue(Name name, Expression* expr) { auto setFlow = visit(expr); if (!setFlow.breaking()) { return setGlobalValue(name, setFlow.values); @@ -90,56 +90,49 @@ bool ContextAwareExpressionRunner::setGlobalValue(Name name, Expression* expr) { return false; } -Flow ContextAwareExpressionRunner::visitLoop(Loop* curr) { +Flow LinearExpressionRunner::visitLoop(Loop* curr) { // loops might be infinite, so must be careful // but we can't tell if non-infinite, since we don't have state, so loops // are just impossible to optimize for now return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitCall(Call* curr) { +Flow LinearExpressionRunner::visitCall(Call* curr) { // Traverse into functions using the same mode, which we can also do // when replacing as long as the function does not have any side effects. // Might yield something useful for simple functions like `clamp`, sometimes // even if arguments are only partially constant or not constant at all. - - // Only traverse into functions if not in a function-parallel scenario, where - // functions may or may not have been optimized already to something we can - // traverse successfully. - if (!(flags & FlagValues::PARALLEL)) { - // Note that we are not a validator, so we skip calls to functions that do - // not exist yet or where the signature does not match. - auto* func = module->getFunctionOrNull(curr->target); - if (func != nullptr && !func->imported()) { + if (flags & FlagValues::TRAVERSE_CALLS) { + auto* func = module->getFunction(curr->target); + if (!func->imported()) { if (func->sig.results.isConcrete()) { auto numOperands = curr->operands.size(); - if (numOperands == func->getNumParams()) { - ContextAwareExpressionRunner runner(module, flags, maxDepth); - runner.depth = depth + 1; - for (Index i = 0; i < numOperands; ++i) { - auto argFlow = visit(curr->operands[i]); - if (!argFlow.breaking() && argFlow.values.isConcrete()) { - runner.localValues[i] = std::move(argFlow.values); - } - } - auto retFlow = runner.visit(func->body); - if (retFlow.breakTo == RETURN_FLOW) { - return Flow(std::move(retFlow.values)); - } else if (!retFlow.breaking()) { - return retFlow; + assert(numOperands == func->getNumParams()); + LinearExpressionRunner runner(module, flags, maxDepth); + runner.depth = depth + 1; + for (Index i = 0; i < numOperands; ++i) { + auto argFlow = visit(curr->operands[i]); + if (!argFlow.breaking() && argFlow.values.isConcrete()) { + runner.localValues[i] = std::move(argFlow.values); } } + auto retFlow = runner.visit(func->body); + if (retFlow.breakTo == RETURN_FLOW) { + return Flow(std::move(retFlow.values)); + } else if (!retFlow.breaking()) { + return retFlow; + } } } } return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitCallIndirect(CallIndirect* curr) { +Flow LinearExpressionRunner::visitCallIndirect(CallIndirect* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitLocalGet(LocalGet* curr) { +Flow LinearExpressionRunner::visitLocalGet(LocalGet* curr) { // Check if a constant value has been set in the context of this runner. auto iter = localValues.find(curr->index); if (iter != localValues.end()) { @@ -161,8 +154,8 @@ Flow ContextAwareExpressionRunner::visitLocalGet(LocalGet* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitLocalSet(LocalSet* curr) { - if (!(flags & FlagValues::REPLACE)) { +Flow LinearExpressionRunner::visitLocalSet(LocalSet* curr) { + 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 (curr->type.isConcrete()) { @@ -177,109 +170,106 @@ Flow ContextAwareExpressionRunner::visitLocalSet(LocalSet* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitGlobalGet(GlobalGet* curr) { - auto* global = module->getGlobalOrNull(curr->name); - if (global != nullptr) { - // Check if the global has an immutable value anyway - if (!global->imported() && !global->mutable_) { - return visit(global->init); - } - // Check if a constant value has been set in the context of this runner. - auto iter = globalValues.find(curr->name); - if (iter != globalValues.end()) { - return Flow(std::move(iter->second)); - } +Flow LinearExpressionRunner::visitGlobalGet(GlobalGet* curr) { + auto* global = module->getGlobal(curr->name); + // Check if the global has an immutable value anyway + if (!global->imported() && !global->mutable_) { + return visit(global->init); + } + // Check if a constant value has been set in the context of this runner. + auto iter = globalValues.find(curr->name); + if (iter != globalValues.end()) { + return Flow(std::move(iter->second)); } return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitGlobalSet(GlobalSet* curr) { - if (!(flags & FlagValues::REPLACE)) { +Flow LinearExpressionRunner::visitGlobalSet(GlobalSet* curr) { + if (!(flags & FlagValues::PRESERVE_SIDEEFFECTS)) { // If we are evaluating and not replacing the expression, remember the // constant value set, if any, for subsequent gets. - auto* global = module->getGlobalOrNull(curr->name); - if (global != nullptr && global->mutable_) { - if (setGlobalValue(curr->name, curr->value)) { - return Flow(); - } + auto* global = module->getGlobal(curr->name); + assert(global->mutable_); + if (setGlobalValue(curr->name, curr->value)) { + return Flow(); } } return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitLoad(Load* curr) { +Flow LinearExpressionRunner::visitLoad(Load* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitStore(Store* curr) { +Flow LinearExpressionRunner::visitStore(Store* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitAtomicRMW(AtomicRMW* curr) { +Flow LinearExpressionRunner::visitAtomicRMW(AtomicRMW* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitAtomicCmpxchg(AtomicCmpxchg* curr) { +Flow LinearExpressionRunner::visitAtomicCmpxchg(AtomicCmpxchg* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitAtomicWait(AtomicWait* curr) { +Flow LinearExpressionRunner::visitAtomicWait(AtomicWait* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitAtomicNotify(AtomicNotify* curr) { +Flow LinearExpressionRunner::visitAtomicNotify(AtomicNotify* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitSIMDLoad(SIMDLoad* curr) { +Flow LinearExpressionRunner::visitSIMDLoad(SIMDLoad* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitMemoryInit(MemoryInit* curr) { +Flow LinearExpressionRunner::visitMemoryInit(MemoryInit* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitDataDrop(DataDrop* curr) { +Flow LinearExpressionRunner::visitDataDrop(DataDrop* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitMemoryCopy(MemoryCopy* curr) { +Flow LinearExpressionRunner::visitMemoryCopy(MemoryCopy* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitMemoryFill(MemoryFill* curr) { +Flow LinearExpressionRunner::visitMemoryFill(MemoryFill* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitHost(Host* curr) { +Flow LinearExpressionRunner::visitHost(Host* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitTry(Try* curr) { +Flow LinearExpressionRunner::visitTry(Try* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitThrow(Throw* curr) { +Flow LinearExpressionRunner::visitThrow(Throw* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitRethrow(Rethrow* curr) { +Flow LinearExpressionRunner::visitRethrow(Rethrow* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitBrOnExn(BrOnExn* curr) { +Flow LinearExpressionRunner::visitBrOnExn(BrOnExn* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitPush(Push* curr) { +Flow LinearExpressionRunner::visitPush(Push* curr) { return Flow(NONCONSTANT_FLOW); } -Flow ContextAwareExpressionRunner::visitPop(Pop* curr) { +Flow LinearExpressionRunner::visitPop(Pop* curr) { return Flow(NONCONSTANT_FLOW); } -void ContextAwareExpressionRunner::trap(const char* why) { +void LinearExpressionRunner::trap(const char* why) { throw NonconstantException(); } diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index 0e30ed8b09a..ebf53c8cad7 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -1,7 +1,9 @@ +console.log("// ExpressionRunner.DEFAULT_MAX_DEPTH = " + binaryen.ExpressionRunner.DEFAULT_MAX_DEPTH); + var Flags = binaryen.ExpressionRunner.Flags; console.log("// ExpressionRunner.Flags.Default = " + Flags.Default); -console.log("// ExpressionRunner.Flags.Replace = " + Flags.Replace); -console.log("// ExpressionRunner.Flags.Parallel = " + Flags.Parallel); +console.log("// ExpressionRunner.Flags.PreserveSideeffects = " + Flags.PreserveSideeffects); +console.log("// ExpressionRunner.Flags.TraverseCalls = " + Flags.TraverseCalls); binaryen.setAPITracing(true); @@ -59,8 +61,8 @@ expr = runner.runAndDispose( ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":5}'); -// Should keep all side-effects if we are going to replace the expression -runner = new binaryen.ExpressionRunner(module, Flags.Replace); +// Should preserve any side-effects if explicitly requested +runner = new binaryen.ExpressionRunner(module, Flags.PreserveSideeffects); expr = runner.runAndDispose( module.i32.add( module.local.tee(0, module.i32.const(4), binaryen.i32), @@ -86,7 +88,7 @@ expr = runner.runAndDispose( assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":6}'); // Should pick up explicitly preset values -runner = new binaryen.ExpressionRunner(module, Flags.Replace); +runner = new binaryen.ExpressionRunner(module, Flags.PreserveSideeffects); assert(runner.setLocalValue(0, module.i32.const(3))); assert(runner.setGlobalValue("aGlobal", module.i32.const(4))); expr = runner.runAndDispose( @@ -97,8 +99,8 @@ expr = runner.runAndDispose( ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":7}'); -// Should traverse into simple functions -runner = new binaryen.ExpressionRunner(module); +// Should traverse into (simple) functions if requested +runner = new binaryen.ExpressionRunner(module, Flags.TraverseCalls); module.addFunction("add", binaryen.createType([ binaryen.i32, binaryen.i32 ]), binaryen.i32, [], module.block(null, [ module.i32.add( @@ -118,8 +120,8 @@ expr = runner.runAndDispose( ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":8}'); -// Should not attempt to traverse into functions if required to be deterministic -runner = new binaryen.ExpressionRunner(module, Flags.Parallel); +// Should not attempt to traverse into functions if not explicitly set +runner = new binaryen.ExpressionRunner(module); expr = runner.runAndDispose( module.i32.add( module.i32.const(1), diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index 147e6588a13..75b3defab43 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -1,6 +1,7 @@ +// ExpressionRunner.DEFAULT_MAX_DEPTH = 50 // ExpressionRunner.Flags.Default = 0 -// ExpressionRunner.Flags.Replace = 1 -// ExpressionRunner.Flags.Parallel = 2 +// ExpressionRunner.Flags.PreserveSideeffects = 1 +// ExpressionRunner.Flags.TraverseCalls = 2 // beginning a Binaryen API trace #include #include @@ -93,7 +94,7 @@ int main() { BinaryenExpressionGetId(expressions[41]); BinaryenExpressionGetType(expressions[41]); BinaryenConstGetValueI32(expressions[41]); - the_runner = ExpressionRunnerCreate(the_module, 0, 50); + the_runner = ExpressionRunnerCreate(the_module, 2, 50); { BinaryenType t0[] = {2, 2}; BinaryenTypeCreate(t0, 2); // 11 @@ -121,7 +122,7 @@ int main() { BinaryenExpressionGetId(expressions[51]); BinaryenExpressionGetType(expressions[51]); BinaryenConstGetValueI32(expressions[51]); - the_runner = ExpressionRunnerCreate(the_module, 2, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 50); expressions[52] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[53] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); expressions[54] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); From ed62e30126e95e1340eac3aac0deb61b0eb60eba Mon Sep 17 00:00:00 2001 From: dcode Date: Wed, 1 Apr 2020 17:20:47 +0200 Subject: [PATCH 20/27] refactor --- src/binaryen-c.cpp | 26 +- src/binaryen-c.h | 12 +- src/js/binaryen.js-post.js | 9 +- src/passes/Precompute.cpp | 77 ++++- src/wasm-interpreter.h | 418 +++++++++++++++-------- src/wasm/wasm-interpreter.cpp | 254 -------------- src/wasm/wasm.cpp | 1 + test/binaryen.js/expressionrunner.js | 20 +- test/binaryen.js/expressionrunner.js.txt | 32 +- 9 files changed, 390 insertions(+), 459 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index fa4239fa137..b8b938c119e 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4886,31 +4886,29 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, // ========= ExpressionRunner ========= // -BinaryenIndex ExpressionRunnerDefaultMaxDepth() { - return LinearExpressionRunner::DEFAULT_MAX_DEPTH; -} - ExpressionRunnerFlags ExpressionRunnerFlagsDefault() { - return LinearExpressionRunner::FlagValues::DEFAULT; + return CExpressionRunner::FlagValues::DEFAULT; } ExpressionRunnerFlags ExpressionRunnerFlagsPreserveSideeffects() { - return LinearExpressionRunner::FlagValues::PRESERVE_SIDEEFFECTS; + return CExpressionRunner::FlagValues::PRESERVE_SIDEEFFECTS; } ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls() { - return LinearExpressionRunner::FlagValues::TRAVERSE_CALLS; + return CExpressionRunner::FlagValues::TRAVERSE_CALLS; } ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, ExpressionRunnerFlags flags, - BinaryenIndex maxDepth) { + BinaryenIndex maxDepth, + BinaryenIndex maxLoopIterations) { if (tracing) { std::cout << " the_runner = ExpressionRunnerCreate(the_module, " << flags - << ", " << maxDepth << ");\n"; + << ", " << maxDepth << ", " << maxLoopIterations << ");\n"; } auto* wasm = (Module*)module; - return ExpressionRunnerRef(new LinearExpressionRunner(wasm, flags, maxDepth)); + return ExpressionRunnerRef( + new CExpressionRunner(wasm, flags, maxDepth, maxLoopIterations)); } int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, @@ -4921,7 +4919,7 @@ int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, << ", expressions[" << expressions[value] << "]);\n"; } - auto* R = (LinearExpressionRunner*)runner; + auto* R = (CExpressionRunner*)runner; return R->setLocalValue(index, value); } @@ -4934,21 +4932,21 @@ int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, std::cout << ", expressions[" << expressions[value] << "]);\n"; } - auto* R = (LinearExpressionRunner*)runner; + auto* R = (CExpressionRunner*)runner; return R->setGlobalValue(name, value); } BinaryenExpressionRef ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, BinaryenExpressionRef expr) { - auto* R = (LinearExpressionRunner*)runner; + auto* R = (CExpressionRunner*)runner; Expression* ret = nullptr; try { auto flow = R->visit(expr); if (!flow.breaking() && !flow.values.empty()) { ret = flow.getConstExpression(*R->getModule()); } - } catch (LinearExpressionRunner::NonconstantException&) { + } catch (CExpressionRunner::NonconstantException&) { } if (tracing) { diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 5805f5756d3..3536f7fe56a 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1648,18 +1648,15 @@ BINARYEN_API BinaryenExpressionRef RelooperRenderAndDispose( #ifdef __cplusplus namespace wasm { -class ContextAwareExpressionRunner; +class CExpressionRunner; } // namespace wasm -typedef class wasm::ContextAwareExpressionRunner* ExpressionRunnerRef; +typedef class wasm::CExpressionRunner* ExpressionRunnerRef; #else -typedef struct ContextAwareExpressionRunner* ExpressionRunnerRef; +typedef struct CExpressionRunner* ExpressionRunnerRef; #endif typedef uint32_t ExpressionRunnerFlags; -// Default maximum evaluation depth. -BINARYEN_API BinaryenIndex ExpressionRunnerDefaultMaxDepth(); - // By default, just evaluate the expression, i.e. all we want to know is whether // it computes down to a concrete value, where it is not necessary to preserve // side effects like those of a `local.tee`. @@ -1679,7 +1676,8 @@ BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls(); BINARYEN_API ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, ExpressionRunnerFlags flags, - BinaryenIndex maxDepth); + BinaryenIndex maxDepth, + BinaryenIndex maxLoopIterations); // Sets a known local value to use. Order matters if expressions have side // effects. Returns `true` if the expression actually evaluates to a constant. diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 5a5e08c2829..7bd88fadc89 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -486,8 +486,7 @@ function initializeConstants() { Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name](); }); - // ExpressionRunner constants - Module['ExpressionRunner']['DEFAULT_MAX_DEPTH'] = Module['_ExpressionRunnerDefaultMaxDepth'](); + // ExpressionRunner flags Module['ExpressionRunner']['Flags'] = { 'Default': Module['_ExpressionRunnerFlagsDefault'](), 'PreserveSideeffects': Module['_ExpressionRunnerFlagsPreserveSideeffects'](), @@ -2436,10 +2435,8 @@ Module['Relooper'] = function(module) { }; // 'ExpressionRunner' interface -Module['ExpressionRunner'] = function(module, flags, maxDepth) { - if (typeof flags === 'undefined') flags = Module['ExpressionRunner']['Flags']['Default']; - if (typeof maxDepth === 'undefined') maxDepth = Module['ExpressionRunner']['DEFAULT_MAX_DEPTH']; - var runner = Module['_ExpressionRunnerCreate'](module['ptr'], flags, maxDepth); +Module['ExpressionRunner'] = function(module, flags, maxDepth, maxLoopIterations) { + var runner = Module['_ExpressionRunnerCreate'](module['ptr'], flags, maxDepth, maxLoopIterations); this['ptr'] = runner; this['setLocalValue'] = function(index, valueExpr) { diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 9c5a9191586..c498edea947 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -39,10 +39,55 @@ namespace wasm { -using FlagValues = LinearExpressionRunner::FlagValues; -using Flags = LinearExpressionRunner::Flags; -using NonconstantException = LinearExpressionRunner::NonconstantException; -using GetValues = LinearExpressionRunner::GetValues; +// Limit evaluation depth for 2 reasons: first, it is highly unlikely +// that we can do anything useful to precompute a hugely nested expression +// (we should succed at smaller parts of it first). Second, a low limit is +// helpful to avoid platform differences in native stack sizes. +static const Index MAX_DEPTH = 50; + +// Limit loop iterations since loops might be infinite. +static const Index MAX_LOOP_ITERATIONS = 3; + +typedef std::unordered_map GetValues; + +// Precomputes an expression. Errors if we hit anything that can't be +// precomputed. +class PrecomputingExpressionRunner + : public ExpressionRunner { + + // map gets to constant values, if they are known to be constant + GetValues& getValues; + +public: + PrecomputingExpressionRunner(Module* module_, + GetValues& getValues, + bool replaceExpression) + : ExpressionRunner( + replaceExpression ? FlagValues::PRESERVE_SIDEEFFECTS + : FlagValues::DEFAULT), + getValues(getValues) { + module = module_; + maxDepth = MAX_DEPTH; + maxLoopIterations = MAX_LOOP_ITERATIONS; + } + + struct NonconstantException { + }; // TODO: use a flow with a special name, as this is likely very slow + + Flow visitLocalGet(LocalGet* curr) { + // Prefer known get values computed during the pass + auto iter = getValues.find(curr); + if (iter != getValues.end()) { + auto values = iter->second; + if (values.isConcrete()) { + return Flow(std::move(values)); + } + } + return ExpressionRunner::visitLocalGet(curr); + } + + void trap(const char* why) override { throw NonconstantException(); } +}; struct Precompute : public WalkerPass< @@ -95,7 +140,7 @@ struct Precompute return; } if (flow.breaking()) { - if (flow.breakTo == LinearExpressionRunner::NONCONSTANT_FLOW) { + if (flow.breakTo == NONCONSTANT_FLOW) { return; } if (flow.breakTo == RETURN_FLOW) { @@ -168,16 +213,14 @@ struct Precompute private: // Precompute an expression, returning a flow, which may be a constant - // (that we can replace the expression with if the PRESERVE_SIDEEFFECTS flag - // is set). - Flow precomputeExpression(Expression* curr, - Flags flags = FlagValues::PRESERVE_SIDEEFFECTS) { + // (that we can replace the expression with if replaceExpression is set). + Flow precomputeExpression(Expression* curr, bool replaceExpression = true) { try { - LinearExpressionRunner runner(getModule(), flags); - runner.setGetValues(&getValues); - return runner.visit(curr); - } catch (NonconstantException&) { - return Flow(LinearExpressionRunner::NONCONSTANT_FLOW); + return PrecomputingExpressionRunner( + getModule(), getValues, replaceExpression) + .visit(curr); + } catch (PrecomputingExpressionRunner::NonconstantException&) { + return Flow(NONCONSTANT_FLOW); } } @@ -189,9 +232,9 @@ struct Precompute // will have value 1 which we can optimize here, but in precomputeExpression // we could not do anything. Literals precomputeValue(Expression* curr) { - // Note that we do not set PRESERVE_SIDEEFFECTS, as we just care about the - // value here. - Flow flow = precomputeExpression(curr, FlagValues::DEFAULT); + // Note that we set replaceExpression to false, as we just care about + // the value here. + Flow flow = precomputeExpression(curr, false /* replaceExpression */); if (flow.breaking()) { return {}; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index b70e9097cb1..c105a7c6c0c 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -44,7 +44,7 @@ using namespace cashew; // Utilities -extern Name WASM, RETURN_FLOW; +extern Name WASM, RETURN_FLOW, NONCONSTANT_FLOW; // Stuff that flows around during executing expressions: a literal, or a change // in control flow. @@ -150,13 +150,17 @@ class Indenter { template class ExpressionRunner : public OverriddenVisitor { protected: - Index maxDepth; + Index maxDepth = 0; // = no limit + + Index maxLoopIterations = 0; // = no limit Index depth = 0; - // Trap instead of aborting if we hit an invalid expression. Useful where - // we are only interested in valid expressions before validation. - bool trapIfInvalid = false; + Module* module = nullptr; + + std::unordered_map localValues; + + std::unordered_map globalValues; Flow generateArguments(const ExpressionList& operands, LiteralList& arguments) { @@ -174,11 +178,74 @@ class ExpressionRunner : public OverriddenVisitor { } public: - ExpressionRunner(Index maxDepth) : maxDepth(maxDepth) {} + enum FlagValues { + // By default, just evaluate the expression, i.e. all we want to know is + // 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. + 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. + TRAVERSE_CALLS = 1 << 1, + // Trap instead of aborting if we hit an invalid expression. Useful where + // we are only interested in valid expressions before validation. + TRAP_ON_INVALID = 1 << 2, + }; + + // Flags indicating special requirements, for example whether we are just + // evaluating (default), also going to replace the expression afterwards or + // executing in a function-parallel scenario. See FlagValues. + typedef uint32_t Flags; + + Flags flags; + + ExpressionRunner(Flags flags = FlagValues::DEFAULT) : flags(flags) {} + + // Gets the module this runner is operating on. + Module* getModule() { return module; } + + // Sets a known local value to use. + void setLocalValue(Index index, Literals& values) { + assert(values.isConcrete()); + localValues[index] = values; + } + + // Sets a known local value to use. Order matters if expressions have side + // effects. Returns `true` if a concrete value can be computed. + bool setLocalValue(Index index, Expression* expr) { + auto setFlow = visit(expr); + if (!setFlow.breaking()) { + setLocalValue(index, setFlow.values); + return true; + } + return false; + } + + // Sets a known global value to use. + void setGlobalValue(Name name, Literals& values) { + assert(values.isConcrete()); + globalValues[name] = values; + } + + // Sets a known global value to use. Order matters if expressions have side + // effects. Returns `true` if a concrete value can be computed. + bool setGlobalValue(Name name, Expression* expr) { + auto setFlow = visit(expr); + if (!setFlow.breaking()) { + setGlobalValue(name, setFlow.values); + return true; + } + return false; + } Flow visit(Expression* curr) { depth++; - if (depth > maxDepth) { + if (maxDepth > 0 && depth > maxDepth) { trap("interpreter recursion limit"); } auto ret = OverriddenVisitor::visit(curr); @@ -186,7 +253,7 @@ class ExpressionRunner : public OverriddenVisitor { Type type = ret.getType(); if (type.isConcrete() || curr->type.isConcrete()) { if (!Type::isSubType(type, curr->type)) { - if (trapIfInvalid) { + if (flags & FlagValues::TRAP_ON_INVALID) { trap("unexpected type"); } #if 1 // def WASM_INTERPRETER_DEBUG @@ -257,10 +324,14 @@ class ExpressionRunner : public OverriddenVisitor { } Flow visitLoop(Loop* curr) { NOTE_ENTER("Loop"); + Index loopCount = 0; while (1) { Flow flow = visit(curr->body); if (flow.breaking()) { if (flow.breakTo == curr->name) { + if (maxLoopIterations > 0 && ++loopCount >= maxLoopIterations) { + return Flow(NONCONSTANT_FLOW); + } continue; // lol } } @@ -1181,28 +1252,170 @@ class ExpressionRunner : public OverriddenVisitor { assert(flow.values.size() > curr->index); return Flow(flow.values[curr->index]); } + Flow visitLocalGet(LocalGet* curr) { + NOTE_ENTER("LocalGet"); + NOTE_EVAL1(curr->index); + // Check if a constant value has been set in the context of this runner. + auto iter = localValues.find(curr->index); + if (iter != localValues.end()) { + return Flow(std::move(iter->second)); + } + return Flow(NONCONSTANT_FLOW); + } + Flow visitLocalSet(LocalSet* curr) { + 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 (curr->type.isConcrete()) { + assert(curr->isTee()); + return visit(curr->value); + } + // Otherwise remember the constant value set, if any, for subsequent gets. + if (setLocalValue(curr->index, curr->value)) { + return Flow(); + } + } + return Flow(NONCONSTANT_FLOW); + } + Flow visitGlobalGet(GlobalGet* curr) { + NOTE_ENTER("GlobalGet"); + NOTE_NAME(curr->name); + if (module != nullptr) { + auto* global = module->getGlobal(curr->name); + // Check if the global has an immutable value anyway + if (!global->imported() && !global->mutable_) { + return visit(global->init); + } + } + // Check if a constant value has been set in the context of this runner. + auto iter = globalValues.find(curr->name); + if (iter != globalValues.end()) { + return Flow(std::move(iter->second)); + } + return Flow(NONCONSTANT_FLOW); + } + Flow visitGlobalSet(GlobalSet* curr) { + NOTE_ENTER("GlobalSet"); + NOTE_NAME(curr->name); + if (!(flags & FlagValues::PRESERVE_SIDEEFFECTS) && module != nullptr) { + // If we are evaluating and not replacing the expression, remember the + // constant value set, if any, for subsequent gets. + auto* global = module->getGlobal(curr->name); + assert(global->mutable_); + if (setGlobalValue(curr->name, curr->value)) { + return Flow(); + } + } + return Flow(NONCONSTANT_FLOW); + } + Flow visitCall(Call* curr) { + NOTE_ENTER("Call"); + NOTE_NAME(curr->target); + // Traverse into functions using the same mode, which we can also do + // when replacing as long as the function does not have any side effects. + // Might yield something useful for simple functions like `clamp`, sometimes + // even if arguments are only partially constant or not constant at all. + if ((flags & FlagValues::TRAVERSE_CALLS) != 0 && module != nullptr) { + auto* func = module->getFunction(curr->target); + if (!func->imported()) { + if (func->sig.results.isConcrete()) { + auto numOperands = curr->operands.size(); + assert(numOperands == func->getNumParams()); + ExpressionRunner runner(flags); + runner.module = module; + runner.maxDepth = maxDepth; + runner.maxLoopIterations = maxLoopIterations; + runner.depth = depth + 1; + runner.globalValues = globalValues; + for (Index i = 0; i < numOperands; ++i) { + auto argFlow = visit(curr->operands[i]); + if (!argFlow.breaking()) { + assert(argFlow.values.isConcrete()); + runner.localValues[i] = std::move(argFlow.values); + } + } + auto retFlow = runner.visit(func->body); + if (retFlow.breakTo == RETURN_FLOW) { + return Flow(std::move(retFlow.values)); + } else if (!retFlow.breaking()) { + return retFlow; + } + } + } + } + return Flow(NONCONSTANT_FLOW); + } - Flow visitCall(Call*) { WASM_UNREACHABLE("unimp"); } - Flow visitCallIndirect(CallIndirect*) { WASM_UNREACHABLE("unimp"); } - Flow visitLocalGet(LocalGet*) { WASM_UNREACHABLE("unimp"); } - Flow visitLocalSet(LocalSet*) { WASM_UNREACHABLE("unimp"); } - Flow visitGlobalSet(GlobalSet*) { WASM_UNREACHABLE("unimp"); } - Flow visitLoad(Load* curr) { WASM_UNREACHABLE("unimp"); } - Flow visitStore(Store* curr) { WASM_UNREACHABLE("unimp"); } - Flow visitHost(Host* curr) { WASM_UNREACHABLE("unimp"); } - Flow visitMemoryInit(MemoryInit* curr) { WASM_UNREACHABLE("unimp"); } - Flow visitDataDrop(DataDrop* curr) { WASM_UNREACHABLE("unimp"); } - Flow visitMemoryCopy(MemoryCopy* curr) { WASM_UNREACHABLE("unimp"); } - Flow visitMemoryFill(MemoryFill* curr) { WASM_UNREACHABLE("unimp"); } - Flow visitAtomicRMW(AtomicRMW*) { WASM_UNREACHABLE("unimp"); } - Flow visitAtomicCmpxchg(AtomicCmpxchg*) { WASM_UNREACHABLE("unimp"); } - Flow visitAtomicWait(AtomicWait*) { WASM_UNREACHABLE("unimp"); } - Flow visitAtomicNotify(AtomicNotify*) { WASM_UNREACHABLE("unimp"); } - Flow visitSIMDLoad(SIMDLoad*) { WASM_UNREACHABLE("unimp"); } - Flow visitSIMDLoadSplat(SIMDLoad*) { WASM_UNREACHABLE("unimp"); } - Flow visitSIMDLoadExtend(SIMDLoad*) { WASM_UNREACHABLE("unimp"); } - Flow visitPush(Push*) { WASM_UNREACHABLE("unimp"); } - Flow visitPop(Pop*) { WASM_UNREACHABLE("unimp"); } + Flow visitCallIndirect(CallIndirect*) { + NOTE_ENTER("CallIndirect"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitLoad(Load* curr) { + NOTE_ENTER("Load"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitStore(Store* curr) { + NOTE_ENTER("Store"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitHost(Host* curr) { + NOTE_ENTER("Host"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitMemoryInit(MemoryInit* curr) { + NOTE_ENTER("MemoryInit"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitDataDrop(DataDrop* curr) { + NOTE_ENTER("DataDrop"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitMemoryCopy(MemoryCopy* curr) { + NOTE_ENTER("MemoryCopy"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitMemoryFill(MemoryFill* curr) { + NOTE_ENTER("MemoryFill"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitAtomicRMW(AtomicRMW*) { + NOTE_ENTER("AtomicRMW"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitAtomicCmpxchg(AtomicCmpxchg*) { + NOTE_ENTER("AtomicCmpxchg"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitAtomicWait(AtomicWait*) { + NOTE_ENTER("AtomicWait"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitAtomicNotify(AtomicNotify*) { + NOTE_ENTER("AtomicNotify"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitSIMDLoad(SIMDLoad*) { + NOTE_ENTER("SIMDLoad"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitSIMDLoadSplat(SIMDLoad*) { + NOTE_ENTER("SIMDLoadSplat"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitSIMDLoadExtend(SIMDLoad*) { + NOTE_ENTER("SIMDLoadExtend"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitPush(Push*) { + NOTE_ENTER("Push"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitPop(Pop*) { + NOTE_ENTER("Pop"); + return Flow(NONCONSTANT_FLOW); + } Flow visitRefNull(RefNull* curr) { NOTE_ENTER("RefNull"); return Literal::makeNullref(); @@ -1223,10 +1436,22 @@ class ExpressionRunner : public OverriddenVisitor { return Literal::makeFuncref(curr->func); } // TODO Implement EH instructions - Flow visitTry(Try*) { WASM_UNREACHABLE("unimp"); } - Flow visitThrow(Throw*) { WASM_UNREACHABLE("unimp"); } - Flow visitRethrow(Rethrow*) { WASM_UNREACHABLE("unimp"); } - Flow visitBrOnExn(BrOnExn*) { WASM_UNREACHABLE("unimp"); } + Flow visitTry(Try*) { + NOTE_ENTER("Try"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitThrow(Throw*) { + NOTE_ENTER("Throw"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitRethrow(Rethrow*) { + NOTE_ENTER("Rethrow"); + return Flow(NONCONSTANT_FLOW); + } + Flow visitBrOnExn(BrOnExn*) { + NOTE_ENTER("BrOnExn"); + return Flow(NONCONSTANT_FLOW); + } virtual void trap(const char* why) { WASM_UNREACHABLE("unimp"); } }; @@ -2247,126 +2472,23 @@ class ModuleInstance : ModuleInstanceBase(wasm, externalInterface) {} }; -// Evaluates an expression linearly, optionally given its surrounding context. -// Errors if we hit anything that can't be evaluated down to a constant. -class LinearExpressionRunner final - : public ExpressionRunner { - +// Expression runner exposed by the C-API +class CExpressionRunner final : public ExpressionRunner { public: - // Map of `local.get`s to their respective values. - typedef std::unordered_map GetValues; - - // Whether we are trying to precompute down to an expression (which we can - // do on say 5 + 6) or to a value (which we can't do on a local.tee that - // flows a 7 through it). When we want to replace the expression, we can - // only do so when it has no side effects. When we don't care about - // replacing the expression, we just want to know if it will contain a known - // constant. - enum FlagValues { - // By default, just evaluate the expression, i.e. all we want to know is - // 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. - 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. - TRAVERSE_CALLS = 1 << 1 - }; - // Flags indicating special requirements, for example whether we are just - // evaluating (default), also going to replace the expression afterwards or - // executing in a function-parallel scenario. See FlagValues. - typedef uint32_t Flags; - - // Limit evaluation depth for 2 reasons: first, it is highly unlikely - // that we can do anything useful when precomputing a hugely nested expression - // (we should succed at smaller parts of it first). Second, a low limit is - // helpful to avoid platform differences in native stack sizes. - static const Index DEFAULT_MAX_DEPTH = 50; - - // Special break target indicating a flow not evaluating to a concrete value. - static const Name NONCONSTANT_FLOW; + CExpressionRunner(Module* module_, + CExpressionRunner::Flags flags, + Index maxDepth_, + Index maxLoopIterations_) + : ExpressionRunner(flags | FlagValues::TRAP_ON_INVALID) { + module = module_; + maxDepth = maxDepth_; + maxLoopIterations = maxLoopIterations_; + } - // Special exception indicating a flow not evaluating to a constant struct NonconstantException { }; // TODO: use a flow with a special name, as this is likely very slow - LinearExpressionRunner(Module* module, - Flags flags = FlagValues::DEFAULT, - Index maxDepth = DEFAULT_MAX_DEPTH); - - // Gets the module this runner belongs to. - Module* getModule(); - - // Sets the optional reference to a map of gets to constant values. - void setGetValues(GetValues* values); - - // Sets a known local value to use. Returns `true` if the value is concrete - // (i.e. not none or unreachable). - bool setLocalValue(Index index, Literals& values); - - // Sets a known local value to use. Order matters if expressions have side - // effects. Returns `true` if the value is concrete (i.e. not none or - // unreachable). - bool setLocalValue(Index index, Expression* expr); - - // Sets a known global value to use. Returns `true` if the value is concrete - // (i.e. not none or unreachable). - bool setGlobalValue(Name name, Literals& values); - - // Sets a known global value to use. Order matters if expressions have side - // effects. Returns `true` if the value is concrete (i.e. not none or - // unreachable). - bool setGlobalValue(Name name, Expression* expr); - - Flow visitLoop(Loop* curr); - Flow visitCall(Call* curr); - Flow visitCallIndirect(CallIndirect* curr); - Flow visitLocalGet(LocalGet* curr); - Flow visitLocalSet(LocalSet* curr); - Flow visitGlobalGet(GlobalGet* curr); - Flow visitGlobalSet(GlobalSet* curr); - Flow visitLoad(Load* curr); - Flow visitStore(Store* curr); - Flow visitAtomicRMW(AtomicRMW* curr); - Flow visitAtomicCmpxchg(AtomicCmpxchg* curr); - Flow visitAtomicWait(AtomicWait* curr); - Flow visitAtomicNotify(AtomicNotify* curr); - Flow visitSIMDLoad(SIMDLoad* curr); - Flow visitMemoryInit(MemoryInit* curr); - Flow visitDataDrop(DataDrop* curr); - Flow visitMemoryCopy(MemoryCopy* curr); - Flow visitMemoryFill(MemoryFill* curr); - Flow visitHost(Host* curr); - Flow visitTry(Try* curr); - Flow visitThrow(Throw* curr); - Flow visitRethrow(Rethrow* curr); - Flow visitBrOnExn(BrOnExn* curr); - Flow visitPush(Push* curr); - Flow visitPop(Pop* curr); - void trap(const char* why) override; - -private: - // Module this runner belongs to. - Module* module; - - // Map of local indexes to values set in the context of this runner - std::unordered_map localValues; - - // Map of global names to values set in the context of this runner - std::unordered_map globalValues; - - // Optional reference to a map of gets to constant values, for example where a - // pass did already compute these. Not applicable in C-API usage and possibly - // large, hence a reference to the respective map (must not be mutated). - GetValues* getValues = nullptr; - - // Flags indicating special requirements. See FlagValues. - Flags flags; + void trap(const char* why) override { throw NonconstantException(); } }; } // namespace wasm diff --git a/src/wasm/wasm-interpreter.cpp b/src/wasm/wasm-interpreter.cpp index b927049dcb6..ad4b565fe71 100644 --- a/src/wasm/wasm-interpreter.cpp +++ b/src/wasm/wasm-interpreter.cpp @@ -1,19 +1,3 @@ -/* - * Copyright 2020 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - #include "wasm-interpreter.h" namespace wasm { @@ -35,242 +19,4 @@ void Indenter::print() { } #endif // WASM_INTERPRETER_DEBUG -// -// LinearExpressionRunner -// - -const Name LinearExpressionRunner::NONCONSTANT_FLOW = "Binaryen|nonconstant"; - -LinearExpressionRunner::LinearExpressionRunner(Module* module, - Flags flags, - Index maxDepth) - : ExpressionRunner(maxDepth), module(module), - flags(flags) { - // Trap instead of aborting if we hit an invalid expression. - trapIfInvalid = true; -} - -Module* LinearExpressionRunner::getModule() { return module; } - -void LinearExpressionRunner::setGetValues(GetValues* values) { - getValues = values; -} - -bool LinearExpressionRunner::setLocalValue(Index index, Literals& values) { - if (values.isConcrete()) { - localValues[index] = values; - return true; - } - localValues.erase(index); - return false; -} - -bool LinearExpressionRunner::setLocalValue(Index index, Expression* expr) { - auto setFlow = visit(expr); - if (!setFlow.breaking()) { - return setLocalValue(index, setFlow.values); - } - return false; -} - -bool LinearExpressionRunner::setGlobalValue(Name name, Literals& values) { - if (values.isConcrete()) { - globalValues[name] = values; - return true; - } - globalValues.erase(name); - return false; -} - -bool LinearExpressionRunner::setGlobalValue(Name name, Expression* expr) { - auto setFlow = visit(expr); - if (!setFlow.breaking()) { - return setGlobalValue(name, setFlow.values); - } - return false; -} - -Flow LinearExpressionRunner::visitLoop(Loop* curr) { - // loops might be infinite, so must be careful - // but we can't tell if non-infinite, since we don't have state, so loops - // are just impossible to optimize for now - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitCall(Call* curr) { - // Traverse into functions using the same mode, which we can also do - // when replacing as long as the function does not have any side effects. - // Might yield something useful for simple functions like `clamp`, sometimes - // even if arguments are only partially constant or not constant at all. - if (flags & FlagValues::TRAVERSE_CALLS) { - auto* func = module->getFunction(curr->target); - if (!func->imported()) { - if (func->sig.results.isConcrete()) { - auto numOperands = curr->operands.size(); - assert(numOperands == func->getNumParams()); - LinearExpressionRunner runner(module, flags, maxDepth); - runner.depth = depth + 1; - for (Index i = 0; i < numOperands; ++i) { - auto argFlow = visit(curr->operands[i]); - if (!argFlow.breaking() && argFlow.values.isConcrete()) { - runner.localValues[i] = std::move(argFlow.values); - } - } - auto retFlow = runner.visit(func->body); - if (retFlow.breakTo == RETURN_FLOW) { - return Flow(std::move(retFlow.values)); - } else if (!retFlow.breaking()) { - return retFlow; - } - } - } - } - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitCallIndirect(CallIndirect* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitLocalGet(LocalGet* curr) { - // Check if a constant value has been set in the context of this runner. - auto iter = localValues.find(curr->index); - if (iter != localValues.end()) { - return Flow(std::move(iter->second)); - } - // If not the case, see if the calling pass did compute the value of this - // specific `local.get` in an earlier step already. This is a fallback - // targeting the precompute pass specifically, which already did this work, - // but is not applicable when the runner is used via the C-API for example. - if (getValues != nullptr) { - auto iter = getValues->find(curr); - if (iter != getValues->end()) { - auto values = iter->second; - if (values.isConcrete()) { - return Flow(std::move(values)); - } - } - } - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitLocalSet(LocalSet* curr) { - 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 (curr->type.isConcrete()) { - assert(curr->isTee()); - return visit(curr->value); - } - // Otherwise remember the constant value set, if any, for subsequent gets. - if (setLocalValue(curr->index, curr->value)) { - return Flow(); - } - } - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitGlobalGet(GlobalGet* curr) { - auto* global = module->getGlobal(curr->name); - // Check if the global has an immutable value anyway - if (!global->imported() && !global->mutable_) { - return visit(global->init); - } - // Check if a constant value has been set in the context of this runner. - auto iter = globalValues.find(curr->name); - if (iter != globalValues.end()) { - return Flow(std::move(iter->second)); - } - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitGlobalSet(GlobalSet* curr) { - if (!(flags & FlagValues::PRESERVE_SIDEEFFECTS)) { - // If we are evaluating and not replacing the expression, remember the - // constant value set, if any, for subsequent gets. - auto* global = module->getGlobal(curr->name); - assert(global->mutable_); - if (setGlobalValue(curr->name, curr->value)) { - return Flow(); - } - } - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitLoad(Load* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitStore(Store* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitAtomicRMW(AtomicRMW* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitAtomicCmpxchg(AtomicCmpxchg* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitAtomicWait(AtomicWait* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitAtomicNotify(AtomicNotify* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitSIMDLoad(SIMDLoad* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitMemoryInit(MemoryInit* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitDataDrop(DataDrop* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitMemoryCopy(MemoryCopy* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitMemoryFill(MemoryFill* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitHost(Host* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitTry(Try* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitThrow(Throw* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitRethrow(Rethrow* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitBrOnExn(BrOnExn* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitPush(Push* curr) { - return Flow(NONCONSTANT_FLOW); -} - -Flow LinearExpressionRunner::visitPop(Pop* curr) { - return Flow(NONCONSTANT_FLOW); -} - -void LinearExpressionRunner::trap(const char* why) { - throw NonconstantException(); -} - } // namespace wasm diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 6f7ea4f869d..fd47cbc8b0c 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -24,6 +24,7 @@ namespace wasm { Name WASM("wasm"); Name RETURN_FLOW("*return:)*"); +Name NONCONSTANT_FLOW("*nonconstant:)*"); namespace BinaryConsts { namespace UserSections { diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index ebf53c8cad7..e510b322056 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -1,5 +1,3 @@ -console.log("// ExpressionRunner.DEFAULT_MAX_DEPTH = " + binaryen.ExpressionRunner.DEFAULT_MAX_DEPTH); - var Flags = binaryen.ExpressionRunner.Flags; console.log("// ExpressionRunner.Flags.Default = " + Flags.Default); console.log("// ExpressionRunner.Flags.PreserveSideeffects = " + Flags.PreserveSideeffects); @@ -133,4 +131,22 @@ expr = runner.runAndDispose( ); assert(expr === 0); +// Should stop on maxDepth +runner = new binaryen.ExpressionRunner(module, Flags.Default, 1); +expr = runner.runAndDispose( + module.block(null, + module.i32.const(1), + binaryen.i32) +); +assert(expr === 0); + +// Should not loop infinitely +runner = new binaryen.ExpressionRunner(module, Flags.Default, 50, 3); +expr = runner.runAndDispose( + module.loop("theLoop", + module.br("theLoop") + ) +); +assert(expr === 0); + binaryen.setAPITracing(false); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index 75b3defab43..256fd0fc708 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -1,4 +1,3 @@ -// ExpressionRunner.DEFAULT_MAX_DEPTH = 50 // ExpressionRunner.Flags.Default = 0 // ExpressionRunner.Flags.PreserveSideeffects = 1 // ExpressionRunner.Flags.TraverseCalls = 2 @@ -20,7 +19,7 @@ int main() { expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); expressions[1] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); globals[0] = BinaryenAddGlobal(the_module, "aGlobal", 2, 1, expressions[1]); - the_runner = ExpressionRunnerCreate(the_module, 0, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[2] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[3] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); expressions[4] = BinaryenBinary(the_module, 0, expressions[2], expressions[3]); @@ -28,7 +27,7 @@ int main() { BinaryenExpressionGetId(expressions[5]); BinaryenExpressionGetType(expressions[5]); BinaryenConstGetValueI32(expressions[5]); - the_runner = ExpressionRunnerCreate(the_module, 0, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[6] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[7] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); expressions[8] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); @@ -39,15 +38,15 @@ int main() { BinaryenExpressionGetId(expressions[12]); BinaryenExpressionGetType(expressions[12]); BinaryenConstGetValueI32(expressions[12]); - the_runner = ExpressionRunnerCreate(the_module, 0, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[13] = BinaryenLocalGet(the_module, 0, 2); expressions[14] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[15] = BinaryenBinary(the_module, 0, expressions[13], expressions[14]); ExpressionRunnerRunAndDispose(the_runner, expressions[15]); - the_runner = ExpressionRunnerCreate(the_module, 0, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[16] = BinaryenUnreachable(the_module); ExpressionRunnerRunAndDispose(the_runner, expressions[16]); - the_runner = ExpressionRunnerCreate(the_module, 0, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[17] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); expressions[18] = BinaryenLocalTee(the_module, 0, expressions[17], 2); expressions[19] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); @@ -56,13 +55,13 @@ int main() { BinaryenExpressionGetId(expressions[21]); BinaryenExpressionGetType(expressions[21]); BinaryenConstGetValueI32(expressions[21]); - the_runner = ExpressionRunnerCreate(the_module, 1, 50); + the_runner = ExpressionRunnerCreate(the_module, 1, 0, 0); expressions[22] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); expressions[23] = BinaryenLocalTee(the_module, 0, expressions[22], 2); expressions[24] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[25] = BinaryenBinary(the_module, 0, expressions[23], expressions[24]); ExpressionRunnerRunAndDispose(the_runner, expressions[25]); - the_runner = ExpressionRunnerCreate(the_module, 0, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[26] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); expressions[27] = BinaryenLocalSet(the_module, 0, expressions[26]); expressions[28] = BinaryenLocalGet(the_module, 0, 2); @@ -82,7 +81,7 @@ int main() { BinaryenExpressionGetId(expressions[35]); BinaryenExpressionGetType(expressions[35]); BinaryenConstGetValueI32(expressions[35]); - the_runner = ExpressionRunnerCreate(the_module, 1, 50); + the_runner = ExpressionRunnerCreate(the_module, 1, 0, 0); expressions[36] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); ExpressionRunnerSetLocalValue(the_runner, 0, expressions[36]); expressions[37] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); @@ -94,7 +93,7 @@ int main() { BinaryenExpressionGetId(expressions[41]); BinaryenExpressionGetType(expressions[41]); BinaryenConstGetValueI32(expressions[41]); - the_runner = ExpressionRunnerCreate(the_module, 2, 50); + the_runner = ExpressionRunnerCreate(the_module, 2, 0, 0); { BinaryenType t0[] = {2, 2}; BinaryenTypeCreate(t0, 2); // 11 @@ -122,7 +121,7 @@ int main() { BinaryenExpressionGetId(expressions[51]); BinaryenExpressionGetType(expressions[51]); BinaryenConstGetValueI32(expressions[51]); - the_runner = ExpressionRunnerCreate(the_module, 0, 50); + the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[52] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[53] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); expressions[54] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); @@ -132,6 +131,17 @@ int main() { } expressions[56] = BinaryenBinary(the_module, 0, expressions[52], expressions[55]); ExpressionRunnerRunAndDispose(the_runner, expressions[56]); + the_runner = ExpressionRunnerCreate(the_module, 0, 1, 0); + expressions[57] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + { + BinaryenExpressionRef children[] = { 0 }; + expressions[58] = BinaryenBlock(the_module, NULL, children, 0, 2); + } + ExpressionRunnerRunAndDispose(the_runner, expressions[58]); + the_runner = ExpressionRunnerCreate(the_module, 0, 50, 3); + expressions[59] = BinaryenBreak(the_module, "theLoop", expressions[0], expressions[0]); + expressions[60] = BinaryenLoop(the_module, "theLoop", expressions[59]); + ExpressionRunnerRunAndDispose(the_runner, expressions[60]); return 0; } // ending a Binaryen API trace From 803cd228b87a273297e4632eee32d93256508908 Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 2 Apr 2020 08:17:12 +0200 Subject: [PATCH 21/27] reuse existing runner --- src/wasm-interpreter.h | 13 +++---- test/binaryen.js/expressionrunner.js | 14 ++++--- test/binaryen.js/expressionrunner.js.txt | 48 +++++++++++++----------- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index c105a7c6c0c..b54cf7e2584 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1323,20 +1323,17 @@ class ExpressionRunner : public OverriddenVisitor { if (func->sig.results.isConcrete()) { auto numOperands = curr->operands.size(); assert(numOperands == func->getNumParams()); - ExpressionRunner runner(flags); - runner.module = module; - runner.maxDepth = maxDepth; - runner.maxLoopIterations = maxLoopIterations; - runner.depth = depth + 1; - runner.globalValues = globalValues; + auto prevLocalValues = localValues; + localValues.clear(); for (Index i = 0; i < numOperands; ++i) { auto argFlow = visit(curr->operands[i]); if (!argFlow.breaking()) { assert(argFlow.values.isConcrete()); - runner.localValues[i] = std::move(argFlow.values); + localValues[i] = std::move(argFlow.values); } } - auto retFlow = runner.visit(func->body); + auto retFlow = visit(func->body); + localValues = std::move(prevLocalValues); if (retFlow.breakTo == RETURN_FLOW) { return Flow(std::move(retFlow.values)); } else if (!retFlow.breaking()) { diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index e510b322056..aea9f5672c2 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -107,13 +107,17 @@ module.addFunction("add", binaryen.createType([ binaryen.i32, binaryen.i32 ]), b ) ], binaryen.i32) ); +assert(runner.setLocalValue(0, module.i32.const(1))); expr = runner.runAndDispose( module.i32.add( - module.i32.const(1), - module.call("add", [ - module.i32.const(3), - module.i32.const(4) - ], binaryen.i32) + module.i32.add( + module.local.get(0, binaryen.i32), + module.call("add", [ + module.i32.const(2), + module.i32.const(4) + ], binaryen.i32) + ), + module.local.get(0, binaryen.i32) ) ); assert(JSON.stringify(binaryen.getExpressionInfo(expr)) === '{"id":14,"type":2,"value":8}'); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index 256fd0fc708..90367e0ab80 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -110,38 +110,42 @@ int main() { functions[0] = BinaryenAddFunction(the_module, "add", 11, 2, varTypes, 0, expressions[45]); } expressions[46] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); - expressions[47] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); - expressions[48] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + ExpressionRunnerSetLocalValue(the_runner, 0, expressions[46]); + expressions[47] = BinaryenLocalGet(the_module, 0, 2); + expressions[48] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); + expressions[49] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); { - BinaryenExpressionRef operands[] = { expressions[47], expressions[48] }; - expressions[49] = BinaryenCall(the_module, "add", operands, 2, 2); + BinaryenExpressionRef operands[] = { expressions[48], expressions[49] }; + expressions[50] = BinaryenCall(the_module, "add", operands, 2, 2); } - expressions[50] = BinaryenBinary(the_module, 0, expressions[46], expressions[49]); - expressions[51] = ExpressionRunnerRunAndDispose(the_runner, expressions[50]); - BinaryenExpressionGetId(expressions[51]); - BinaryenExpressionGetType(expressions[51]); - BinaryenConstGetValueI32(expressions[51]); + expressions[51] = BinaryenBinary(the_module, 0, expressions[47], expressions[50]); + expressions[52] = BinaryenLocalGet(the_module, 0, 2); + expressions[53] = BinaryenBinary(the_module, 0, expressions[51], expressions[52]); + expressions[54] = ExpressionRunnerRunAndDispose(the_runner, expressions[53]); + BinaryenExpressionGetId(expressions[54]); + BinaryenExpressionGetType(expressions[54]); + BinaryenConstGetValueI32(expressions[54]); the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); - expressions[52] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); - expressions[53] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); - expressions[54] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + expressions[55] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[56] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); + expressions[57] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); { - BinaryenExpressionRef operands[] = { expressions[53], expressions[54] }; - expressions[55] = BinaryenCall(the_module, "add", operands, 2, 2); + BinaryenExpressionRef operands[] = { expressions[56], expressions[57] }; + expressions[58] = BinaryenCall(the_module, "add", operands, 2, 2); } - expressions[56] = BinaryenBinary(the_module, 0, expressions[52], expressions[55]); - ExpressionRunnerRunAndDispose(the_runner, expressions[56]); + expressions[59] = BinaryenBinary(the_module, 0, expressions[55], expressions[58]); + ExpressionRunnerRunAndDispose(the_runner, expressions[59]); the_runner = ExpressionRunnerCreate(the_module, 0, 1, 0); - expressions[57] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[60] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); { BinaryenExpressionRef children[] = { 0 }; - expressions[58] = BinaryenBlock(the_module, NULL, children, 0, 2); + expressions[61] = BinaryenBlock(the_module, NULL, children, 0, 2); } - ExpressionRunnerRunAndDispose(the_runner, expressions[58]); + ExpressionRunnerRunAndDispose(the_runner, expressions[61]); the_runner = ExpressionRunnerCreate(the_module, 0, 50, 3); - expressions[59] = BinaryenBreak(the_module, "theLoop", expressions[0], expressions[0]); - expressions[60] = BinaryenLoop(the_module, "theLoop", expressions[59]); - ExpressionRunnerRunAndDispose(the_runner, expressions[60]); + expressions[62] = BinaryenBreak(the_module, "theLoop", expressions[0], expressions[0]); + expressions[63] = BinaryenLoop(the_module, "theLoop", expressions[62]); + ExpressionRunnerRunAndDispose(the_runner, expressions[63]); return 0; } // ending a Binaryen API trace From 597c5fad0708567965d433e73534358c339c6a44 Mon Sep 17 00:00:00 2001 From: dcode Date: Thu, 2 Apr 2020 08:37:11 +0200 Subject: [PATCH 22/27] revert trap on invalid --- src/wasm-interpreter.h | 20 +++++++------------- test/binaryen.js/expressionrunner.js | 4 ++-- test/binaryen.js/expressionrunner.js.txt | 4 ++-- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index b54cf7e2584..243da821e99 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -191,10 +191,7 @@ class ExpressionRunner : public OverriddenVisitor { // 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. - TRAVERSE_CALLS = 1 << 1, - // Trap instead of aborting if we hit an invalid expression. Useful where - // we are only interested in valid expressions before validation. - TRAP_ON_INVALID = 1 << 2, + TRAVERSE_CALLS = 1 << 1 }; // Flags indicating special requirements, for example whether we are just @@ -245,24 +242,21 @@ class ExpressionRunner : public OverriddenVisitor { Flow visit(Expression* curr) { depth++; - if (maxDepth > 0 && depth > maxDepth) { + if (maxDepth != 0 && depth > maxDepth) { trap("interpreter recursion limit"); } auto ret = OverriddenVisitor::visit(curr); if (!ret.breaking()) { Type type = ret.getType(); if (type.isConcrete() || curr->type.isConcrete()) { - if (!Type::isSubType(type, curr->type)) { - if (flags & FlagValues::TRAP_ON_INVALID) { - trap("unexpected type"); - } #if 1 // def WASM_INTERPRETER_DEBUG + if (!Type::isSubType(type, curr->type)) { std::cerr << "expected " << curr->type << ", seeing " << type << " from\n" << curr << '\n'; -#endif - assert(false); } +#endif + assert(Type::isSubType(type, curr->type)); } } depth--; @@ -329,7 +323,7 @@ class ExpressionRunner : public OverriddenVisitor { Flow flow = visit(curr->body); if (flow.breaking()) { if (flow.breakTo == curr->name) { - if (maxLoopIterations > 0 && ++loopCount >= maxLoopIterations) { + if (maxLoopIterations != 0 && ++loopCount >= maxLoopIterations) { return Flow(NONCONSTANT_FLOW); } continue; // lol @@ -2476,7 +2470,7 @@ class CExpressionRunner final : public ExpressionRunner { CExpressionRunner::Flags flags, Index maxDepth_, Index maxLoopIterations_) - : ExpressionRunner(flags | FlagValues::TRAP_ON_INVALID) { + : ExpressionRunner(flags) { module = module_; maxDepth = maxDepth_; maxLoopIterations = maxLoopIterations_; diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index aea9f5672c2..05291aad4dd 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -138,9 +138,9 @@ assert(expr === 0); // Should stop on maxDepth runner = new binaryen.ExpressionRunner(module, Flags.Default, 1); expr = runner.runAndDispose( - module.block(null, + module.block(null, [ module.i32.const(1), - binaryen.i32) + ], binaryen.i32) ); assert(expr === 0); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index 90367e0ab80..d7712f00612 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -138,8 +138,8 @@ int main() { the_runner = ExpressionRunnerCreate(the_module, 0, 1, 0); expressions[60] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); { - BinaryenExpressionRef children[] = { 0 }; - expressions[61] = BinaryenBlock(the_module, NULL, children, 0, 2); + BinaryenExpressionRef children[] = { expressions[60] }; + expressions[61] = BinaryenBlock(the_module, NULL, children, 1, 2); } ExpressionRunnerRunAndDispose(the_runner, expressions[61]); the_runner = ExpressionRunnerCreate(the_module, 0, 50, 3); From b4ca9779ad9cd55d578839fd7c9c287e111014d6 Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 3 Apr 2020 14:41:03 +0200 Subject: [PATCH 23/27] address comments --- src/binaryen-c.cpp | 14 ++- src/passes/Precompute.cpp | 38 +++---- src/wasm-interpreter.h | 125 +++++++++++------------ test/passes/precompute_all-features.txt | 5 +- test/passes/precompute_all-features.wast | 7 ++ 5 files changed, 105 insertions(+), 84 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index b8b938c119e..20ce989c554 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4920,7 +4920,12 @@ int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, } auto* R = (CExpressionRunner*)runner; - return R->setLocalValue(index, value); + auto setFlow = R->visit(value); + if (!setFlow.breaking()) { + R->setLocalValue(index, setFlow.values); + return 1; + } + return 0; } int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, @@ -4933,7 +4938,12 @@ int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, } auto* R = (CExpressionRunner*)runner; - return R->setGlobalValue(name, value); + auto setFlow = R->visit(value); + if (!setFlow.breaking()) { + R->setGlobalValue(name, setFlow.values); + return 1; + } + return 0; } BinaryenExpressionRef diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index c498edea947..e8089891d8a 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -39,15 +39,6 @@ namespace wasm { -// Limit evaluation depth for 2 reasons: first, it is highly unlikely -// that we can do anything useful to precompute a hugely nested expression -// (we should succed at smaller parts of it first). Second, a low limit is -// helpful to avoid platform differences in native stack sizes. -static const Index MAX_DEPTH = 50; - -// Limit loop iterations since loops might be infinite. -static const Index MAX_LOOP_ITERATIONS = 3; - typedef std::unordered_map GetValues; // Precomputes an expression. Errors if we hit anything that can't be @@ -55,27 +46,38 @@ typedef std::unordered_map GetValues; class PrecomputingExpressionRunner : public ExpressionRunner { - // map gets to constant values, if they are known to be constant + // Concrete values of gets computed during the pass, which the runner does not + // know about since it only records values of sets it visits. GetValues& getValues; + // Limit evaluation depth for 2 reasons: first, it is highly unlikely + // that we can do anything useful to precompute a hugely nested expression + // (we should succed at smaller parts of it first). Second, a low limit is + // helpful to avoid platform differences in native stack sizes. + static const Index MAX_DEPTH = 50; + + // Limit loop iterations since loops might be infinite. Since we are going to + // replace the expression and must preserve side effects, we limit this to the + // very first iteration because a side effect would be necessary to achieve + // more than one iteration before becoming concrete. + static const Index MAX_LOOP_ITERATIONS = 1; + public: - PrecomputingExpressionRunner(Module* module_, + PrecomputingExpressionRunner(Module* module, GetValues& getValues, bool replaceExpression) : ExpressionRunner( + module, replaceExpression ? FlagValues::PRESERVE_SIDEEFFECTS - : FlagValues::DEFAULT), - getValues(getValues) { - module = module_; - maxDepth = MAX_DEPTH; - maxLoopIterations = MAX_LOOP_ITERATIONS; - } + : FlagValues::DEFAULT, + MAX_DEPTH, + MAX_LOOP_ITERATIONS), + getValues(getValues) {} struct NonconstantException { }; // TODO: use a flow with a special name, as this is likely very slow Flow visitLocalGet(LocalGet* curr) { - // Prefer known get values computed during the pass auto iter = getValues.find(curr); if (iter != getValues.end()) { auto values = iter->second; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 243da821e99..c1a306a1c68 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -149,34 +149,6 @@ class Indenter { // Execute an expression template class ExpressionRunner : public OverriddenVisitor { -protected: - Index maxDepth = 0; // = no limit - - Index maxLoopIterations = 0; // = no limit - - Index depth = 0; - - Module* module = nullptr; - - std::unordered_map localValues; - - std::unordered_map globalValues; - - Flow generateArguments(const ExpressionList& operands, - LiteralList& arguments) { - NOTE_ENTER_("generateArguments"); - arguments.reserve(operands.size()); - for (auto expression : operands) { - Flow flow = this->visit(expression); - if (flow.breaking()) { - return flow; - } - NOTE_EVAL1(flow.values); - arguments.push_back(flow.getSingleValue()); - } - return Flow(); - } - public: enum FlagValues { // By default, just evaluate the expression, i.e. all we want to know is @@ -199,9 +171,56 @@ class ExpressionRunner : public OverriddenVisitor { // executing in a function-parallel scenario. See FlagValues. typedef uint32_t Flags; +protected: + // Flags indicating special requirements. See FlagValues. Flags flags; - ExpressionRunner(Flags flags = FlagValues::DEFAULT) : flags(flags) {} + // Maximum depth before giving up. Defaults to 0 for no limit. + Index maxDepth; + Index depth = 0; + + // Maximum loop iterations before giving up on a loop. Defaults to 0 for no + // limit. A maximum of 1 essentially attempts to interpret a loop as a block, + // giving up as soon as it loops, which is always safe to do. + Index maxLoopIterations; + + // Optional module context to search for globals and called functions. NULL if + // we are not interested in any context or if the subclass uses an alternative + // mechanism, like RuntimeExpressionRunner does. + Module* module; + + // Map remembering concrete local values set in the context of this flow. + std::unordered_map localValues; + // Map remembering concrete global values set in the context of this flow. + std::unordered_map globalValues; + + Flow generateArguments(const ExpressionList& operands, + LiteralList& arguments) { + NOTE_ENTER_("generateArguments"); + arguments.reserve(operands.size()); + for (auto expression : operands) { + Flow flow = this->visit(expression); + if (flow.breaking()) { + return flow; + } + NOTE_EVAL1(flow.values); + arguments.push_back(flow.getSingleValue()); + } + return Flow(); + } + +public: + ExpressionRunner(Flags flags = FlagValues::DEFAULT, + Index maxDepth = 0, + Index maxLoopIterations = 0) + : ExpressionRunner(nullptr, flags, maxDepth, maxLoopIterations) {} + + ExpressionRunner(Module* module, + Flags flags = FlagValues::DEFAULT, + Index maxDepth = 0, + Index maxLoopIterations = 0) + : flags(flags), maxDepth(maxDepth), maxLoopIterations(maxLoopIterations), + module(module) {} // Gets the module this runner is operating on. Module* getModule() { return module; } @@ -212,34 +231,12 @@ class ExpressionRunner : public OverriddenVisitor { localValues[index] = values; } - // Sets a known local value to use. Order matters if expressions have side - // effects. Returns `true` if a concrete value can be computed. - bool setLocalValue(Index index, Expression* expr) { - auto setFlow = visit(expr); - if (!setFlow.breaking()) { - setLocalValue(index, setFlow.values); - return true; - } - return false; - } - // Sets a known global value to use. void setGlobalValue(Name name, Literals& values) { assert(values.isConcrete()); globalValues[name] = values; } - // Sets a known global value to use. Order matters if expressions have side - // effects. Returns `true` if a concrete value can be computed. - bool setGlobalValue(Name name, Expression* expr) { - auto setFlow = visit(expr); - if (!setFlow.breaking()) { - setGlobalValue(name, setFlow.values); - return true; - } - return false; - } - Flow visit(Expression* curr) { depth++; if (maxDepth != 0 && depth > maxDepth) { @@ -1267,7 +1264,9 @@ class ExpressionRunner : public OverriddenVisitor { return visit(curr->value); } // Otherwise remember the constant value set, if any, for subsequent gets. - if (setLocalValue(curr->index, curr->value)) { + auto setFlow = visit(curr->value); + if (!setFlow.breaking()) { + setLocalValue(curr->index, setFlow.values); return Flow(); } } @@ -1298,7 +1297,9 @@ class ExpressionRunner : public OverriddenVisitor { // constant value set, if any, for subsequent gets. auto* global = module->getGlobal(curr->name); assert(global->mutable_); - if (setGlobalValue(curr->name, curr->value)) { + auto setFlow = visit(curr->value); + if (!setFlow.breaking()) { + setGlobalValue(curr->name, setFlow.values); return Flow(); } } @@ -1812,8 +1813,9 @@ template class ModuleInstanceBase { RuntimeExpressionRunner(ModuleInstanceBase& instance, FunctionScope& scope, Index maxDepth) - : ExpressionRunner(maxDepth), instance(instance), - scope(scope) {} + : ExpressionRunner( + RuntimeExpressionRunner::FlagValues::DEFAULT, maxDepth), + instance(instance), scope(scope) {} Flow visitCall(Call* curr) { NOTE_ENTER("Call"); @@ -2466,15 +2468,12 @@ class ModuleInstance // Expression runner exposed by the C-API class CExpressionRunner final : public ExpressionRunner { public: - CExpressionRunner(Module* module_, + CExpressionRunner(Module* module, CExpressionRunner::Flags flags, - Index maxDepth_, - Index maxLoopIterations_) - : ExpressionRunner(flags) { - module = module_; - maxDepth = maxDepth_; - maxLoopIterations = maxLoopIterations_; - } + Index maxDepth, + Index maxLoopIterations) + : ExpressionRunner( + module, flags, maxDepth, maxLoopIterations) {} struct NonconstantException { }; // TODO: use a flow with a special name, as this is likely very slow diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt index 4d613f06fc0..d20173ce848 100644 --- a/test/passes/precompute_all-features.txt +++ b/test/passes/precompute_all-features.txt @@ -258,7 +258,10 @@ (i64.const 42) ) ) - (func $reftype-test (; 18 ;) (result nullref) + (func $loop-precompute (; 18 ;) (result i32) + (i32.const 1) + ) + (func $reftype-test (; 19 ;) (result nullref) (ref.null) ) ) diff --git a/test/passes/precompute_all-features.wast b/test/passes/precompute_all-features.wast index da755bde632..edacc7ea723 100644 --- a/test/passes/precompute_all-features.wast +++ b/test/passes/precompute_all-features.wast @@ -376,6 +376,13 @@ ) ) ) + (func $loop-precompute (result i32) + (block $block (result i32) + (loop $loop + (br $block (i32.const 1)) + ) + ) + ) ;; Check if Precompute pass does not crash on reference types (func $reftype-test (result nullref) From dcbab6a92bdf76853775f8cf9eb8a2e8587ba0c7 Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 3 Apr 2020 23:35:41 +0200 Subject: [PATCH 24/27] address comments --- src/wasm-interpreter.h | 56 ++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index c1a306a1c68..9267154c508 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -171,23 +171,24 @@ class ExpressionRunner : public OverriddenVisitor { // executing in a function-parallel scenario. See FlagValues. typedef uint32_t Flags; + // Indicates no limit of maxDepth or maxLoopIterations. + static const Index NO_LIMIT = 0; + protected: + // Optional module context to search for globals and called functions. NULL if + // we are not interested in any context or if the subclass uses an alternative + // mechanism, like RuntimeExpressionRunner does. + Module* module = nullptr; + // Flags indicating special requirements. See FlagValues. - Flags flags; + Flags flags = FlagValues::DEFAULT; - // Maximum depth before giving up. Defaults to 0 for no limit. - Index maxDepth; + // Maximum depth before giving up. + Index maxDepth = NO_LIMIT; Index depth = 0; - // Maximum loop iterations before giving up on a loop. Defaults to 0 for no - // limit. A maximum of 1 essentially attempts to interpret a loop as a block, - // giving up as soon as it loops, which is always safe to do. - Index maxLoopIterations; - - // Optional module context to search for globals and called functions. NULL if - // we are not interested in any context or if the subclass uses an alternative - // mechanism, like RuntimeExpressionRunner does. - Module* module; + // Maximum iterations before giving up on a loop. + Index maxLoopIterations = NO_LIMIT; // Map remembering concrete local values set in the context of this flow. std::unordered_map localValues; @@ -210,17 +211,14 @@ class ExpressionRunner : public OverriddenVisitor { } public: - ExpressionRunner(Flags flags = FlagValues::DEFAULT, - Index maxDepth = 0, - Index maxLoopIterations = 0) - : ExpressionRunner(nullptr, flags, maxDepth, maxLoopIterations) {} - + ExpressionRunner() {} + ExpressionRunner(Index maxDepth) : maxDepth(maxDepth) {} ExpressionRunner(Module* module, - Flags flags = FlagValues::DEFAULT, - Index maxDepth = 0, - Index maxLoopIterations = 0) - : flags(flags), maxDepth(maxDepth), maxLoopIterations(maxLoopIterations), - module(module) {} + Flags flags, + Index maxDepth, + Index maxLoopIterations) + : module(module), flags(flags), maxDepth(maxDepth), + maxLoopIterations(maxLoopIterations) {} // Gets the module this runner is operating on. Module* getModule() { return module; } @@ -239,7 +237,7 @@ class ExpressionRunner : public OverriddenVisitor { Flow visit(Expression* curr) { depth++; - if (maxDepth != 0 && depth > maxDepth) { + if (maxDepth != NO_LIMIT && depth > maxDepth) { trap("interpreter recursion limit"); } auto ret = OverriddenVisitor::visit(curr); @@ -320,7 +318,8 @@ class ExpressionRunner : public OverriddenVisitor { Flow flow = visit(curr->body); if (flow.breaking()) { if (flow.breakTo == curr->name) { - if (maxLoopIterations != 0 && ++loopCount >= maxLoopIterations) { + if (maxLoopIterations != NO_LIMIT && + ++loopCount >= maxLoopIterations) { return Flow(NONCONSTANT_FLOW); } continue; // lol @@ -1259,12 +1258,12 @@ class ExpressionRunner : public OverriddenVisitor { if (!(flags & FlagValues::PRESERVE_SIDEEFFECTS)) { // If we are evaluating and not replacing the expression, see if there is // a value flowing through a tee. + auto setFlow = visit(curr->value); if (curr->type.isConcrete()) { assert(curr->isTee()); - return visit(curr->value); + return setFlow; } // Otherwise remember the constant value set, if any, for subsequent gets. - auto setFlow = visit(curr->value); if (!setFlow.breaking()) { setLocalValue(curr->index, setFlow.values); return Flow(); @@ -1813,9 +1812,8 @@ template class ModuleInstanceBase { RuntimeExpressionRunner(ModuleInstanceBase& instance, FunctionScope& scope, Index maxDepth) - : ExpressionRunner( - RuntimeExpressionRunner::FlagValues::DEFAULT, maxDepth), - instance(instance), scope(scope) {} + : ExpressionRunner(maxDepth), instance(instance), + scope(scope) {} Flow visitCall(Call* curr) { NOTE_ENTER("Call"); From 4c8fc7c692886ae9c3e59b53a947686cda6b6ec9 Mon Sep 17 00:00:00 2001 From: dcode Date: Tue, 7 Apr 2020 12:44:33 +0200 Subject: [PATCH 25/27] address (most) comments --- src/binaryen-c.cpp | 69 +++++++++++++++++++---- src/binaryen-c.h | 18 ++++-- src/passes/Precompute.cpp | 3 - src/wasm-interpreter.h | 44 ++++++--------- test/binaryen.js/custom-section.js.txt | 2 +- test/binaryen.js/expressionrunner.js | 64 +++++++++++++++++++-- test/binaryen.js/expressionrunner.js.txt | 64 ++++++++++++--------- test/binaryen.js/inlining-options.js.txt | 2 +- test/binaryen.js/kitchen-sink.js.txt | 4 +- test/binaryen.js/low-memory-unused.js.txt | 2 +- test/binaryen.js/pass-arguments.js.txt | 2 +- test/example/c-api-kitchen-sink.txt | 4 +- 12 files changed, 189 insertions(+), 89 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 20ce989c554..dbbfa552843 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -134,6 +134,7 @@ std::map globals; std::map events; std::map exports; std::map relooperBlocks; +std::map expressionRunners; size_t noteExpression(BinaryenExpressionRef expression) { auto id = expressions.size(); @@ -142,6 +143,26 @@ size_t noteExpression(BinaryenExpressionRef expression) { return id; } +// We would normally use the size of `expressionRunners` as the next index, but +// since we are going to delete runners the same address can become reused, +// which would result in unpredictable sizes (indexes) due to undefined +// behavior. Use a sequential id instead. +static size_t nextExpressionRunnerId = 0; + +// Even though unlikely, it is possible that we are trying to use an id that is +// still in use after wrapping around, which we must prevent. +std::unordered_set usedExpressionRunnerIds; + +size_t noteExpressionRunner(ExpressionRunnerRef runner) { + size_t id; + do { + id = nextExpressionRunnerId++; + } while (usedExpressionRunnerIds.find(id) != usedExpressionRunnerIds.end()); + expressionRunners[runner] = id; + usedExpressionRunnerIds.insert(id); + return id; +} + std::string getTemp() { static size_t n = 0; return "t" + std::to_string(n++); @@ -540,6 +561,7 @@ void BinaryenModuleDispose(BinaryenModuleRef module) { std::cout << " events.clear();\n"; std::cout << " exports.clear();\n"; std::cout << " relooperBlocks.clear();\n"; + std::cout << " expressionRunners.clear();\n"; expressions.clear(); functions.clear(); globals.clear(); @@ -4886,6 +4908,22 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, // ========= ExpressionRunner ========= // +namespace wasm { + +class CExpressionRunner final : public ExpressionRunner { +public: + CExpressionRunner(Module* module, + CExpressionRunner::Flags flags, + Index maxDepth, + Index maxLoopIterations) + : ExpressionRunner( + module, flags, maxDepth, maxLoopIterations) {} + + void trap(const char* why) override { throw NonconstantException(); } +}; + +} // namespace wasm + ExpressionRunnerFlags ExpressionRunnerFlagsDefault() { return CExpressionRunner::FlagValues::DEFAULT; } @@ -4902,21 +4940,25 @@ ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, ExpressionRunnerFlags flags, BinaryenIndex maxDepth, BinaryenIndex maxLoopIterations) { - if (tracing) { - std::cout << " the_runner = ExpressionRunnerCreate(the_module, " << flags - << ", " << maxDepth << ", " << maxLoopIterations << ");\n"; - } auto* wasm = (Module*)module; - return ExpressionRunnerRef( + auto* runner = ExpressionRunnerRef( new CExpressionRunner(wasm, flags, maxDepth, maxLoopIterations)); + if (tracing) { + auto id = noteExpressionRunner(runner); + std::cout << " expressionRunners[" << id + << "] = ExpressionRunnerCreate(the_module, " << flags << ", " + << maxDepth << ", " << maxLoopIterations << ");\n"; + } + return runner; } int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, BinaryenIndex index, BinaryenExpressionRef value) { if (tracing) { - std::cout << " ExpressionRunnerSetLocalValue(the_runner, " << index - << ", expressions[" << expressions[value] << "]);\n"; + std::cout << " ExpressionRunnerSetLocalValue(expressionRunners[" + << expressionRunners[runner] << "], " << index << ", expressions[" + << expressions[value] << "]);\n"; } auto* R = (CExpressionRunner*)runner; @@ -4932,7 +4974,8 @@ int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, const char* name, BinaryenExpressionRef value) { if (tracing) { - std::cout << " ExpressionRunnerSetGlobalValue(the_runner, "; + std::cout << " ExpressionRunnerSetGlobalValue(expressionRunners[" + << expressionRunners[runner] << "], "; traceNameOrNULL(name); std::cout << ", expressions[" << expressions[value] << "]);\n"; } @@ -4966,8 +5009,10 @@ ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, } else { std::cout << " "; } - std::cout << "ExpressionRunnerRunAndDispose(the_runner, expressions[" - << expressions[expr] << "]);\n"; + auto id = expressionRunners[runner]; + std::cout << "ExpressionRunnerRunAndDispose(expressionRunners[" << id + << "], expressions[" << expressions[expr] << "]);\n"; + usedExpressionRunnerIds.erase(id); } delete R; @@ -4993,9 +5038,9 @@ void BinaryenSetAPITracing(int on) { " std::map events;\n" " std::map exports;\n" " std::map relooperBlocks;\n" + " std::map expressionRunners;\n" " BinaryenModuleRef the_module = NULL;\n" - " RelooperRef the_relooper = NULL;\n" - " ExpressionRunnerRef the_runner = NULL;\n"; + " RelooperRef the_relooper = NULL;\n"; } else { std::cout << " return 0;\n"; std::cout << "}\n"; diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 3536f7fe56a..06e594a574c 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1662,14 +1662,16 @@ typedef uint32_t ExpressionRunnerFlags; // side effects like those of a `local.tee`. BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsDefault(); -// 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. BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsPreserveSideeffects(); // 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. +// might be concurrently modified, leading to undefined behavior. BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls(); // Creates an ExpressionRunner instance @@ -1680,13 +1682,17 @@ ExpressionRunnerCreate(BinaryenModuleRef module, BinaryenIndex maxLoopIterations); // Sets a known local value to use. Order matters if expressions have side -// effects. Returns `true` if the expression actually evaluates to a constant. +// effects. For example, if the expression also sets a local, this side effect +// will also happen (not affected by any flags). Returns `true` if the +// expression actually evaluates to a constant. BINARYEN_API int ExpressionRunnerSetLocalValue(ExpressionRunnerRef runner, BinaryenIndex index, BinaryenExpressionRef value); // Sets a known global value to use. Order matters if expressions have side -// effects. Returns `true` if the expression actually evaluates to a constant. +// effects. For example, if the expression also sets a local, this side effect +// will also happen (not affected by any flags). Returns `true` if the +// expression actually evaluates to a constant. BINARYEN_API int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, const char* name, BinaryenExpressionRef value); diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index e8089891d8a..c0f0b7054b0 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -74,9 +74,6 @@ class PrecomputingExpressionRunner MAX_LOOP_ITERATIONS), getValues(getValues) {} - struct NonconstantException { - }; // TODO: use a flow with a special name, as this is likely very slow - Flow visitLocalGet(LocalGet* curr) { auto iter = getValues.find(curr); if (iter != getValues.end()) { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 9267154c508..29f83e5d25e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -155,14 +155,15 @@ class ExpressionRunner : public OverriddenVisitor { // 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 { : 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 { 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); - 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); + if (curr->type.isConcrete()) { + assert(curr->isTee()); + return setFlow; + } return Flow(); } } @@ -2463,22 +2467,6 @@ class ModuleInstance : ModuleInstanceBase(wasm, externalInterface) {} }; -// Expression runner exposed by the C-API -class CExpressionRunner final : public ExpressionRunner { -public: - CExpressionRunner(Module* module, - CExpressionRunner::Flags flags, - Index maxDepth, - Index maxLoopIterations) - : ExpressionRunner( - 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 diff --git a/test/binaryen.js/custom-section.js.txt b/test/binaryen.js/custom-section.js.txt index 5543ee69e9c..1f0e4bfe4b1 100644 --- a/test/binaryen.js/custom-section.js.txt +++ b/test/binaryen.js/custom-section.js.txt @@ -9,9 +9,9 @@ int main() { std::map events; std::map exports; std::map relooperBlocks; + std::map expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; - ExpressionRunnerRef the_runner = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); { diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index 05291aad4dd..d42c3eb7330 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -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]); + } 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); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt index d7712f00612..dea36c0d99f 100644 --- a/test/binaryen.js/expressionrunner.js.txt +++ b/test/binaryen.js/expressionrunner.js.txt @@ -12,56 +12,56 @@ int main() { std::map events; std::map exports; std::map relooperBlocks; + std::map expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; - ExpressionRunnerRef the_runner = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); expressions[1] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); globals[0] = BinaryenAddGlobal(the_module, "aGlobal", 2, 1, expressions[1]); - the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); + expressionRunners[0] = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[2] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[3] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); expressions[4] = BinaryenBinary(the_module, 0, expressions[2], expressions[3]); - expressions[5] = ExpressionRunnerRunAndDispose(the_runner, expressions[4]); + expressions[5] = ExpressionRunnerRunAndDispose(expressionRunners[0], expressions[4]); BinaryenExpressionGetId(expressions[5]); BinaryenExpressionGetType(expressions[5]); BinaryenConstGetValueI32(expressions[5]); - the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); + expressionRunners[1] = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[6] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[7] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); expressions[8] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); expressions[9] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); expressions[10] = BinaryenIf(the_module, expressions[7], expressions[8], expressions[9]); expressions[11] = BinaryenBinary(the_module, 0, expressions[6], expressions[10]); - expressions[12] = ExpressionRunnerRunAndDispose(the_runner, expressions[11]); + expressions[12] = ExpressionRunnerRunAndDispose(expressionRunners[1], expressions[11]); BinaryenExpressionGetId(expressions[12]); BinaryenExpressionGetType(expressions[12]); BinaryenConstGetValueI32(expressions[12]); - the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); + expressionRunners[2] = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[13] = BinaryenLocalGet(the_module, 0, 2); expressions[14] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[15] = BinaryenBinary(the_module, 0, expressions[13], expressions[14]); - ExpressionRunnerRunAndDispose(the_runner, expressions[15]); - the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); + ExpressionRunnerRunAndDispose(expressionRunners[2], expressions[15]); + expressionRunners[3] = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[16] = BinaryenUnreachable(the_module); - ExpressionRunnerRunAndDispose(the_runner, expressions[16]); - the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); + ExpressionRunnerRunAndDispose(expressionRunners[3], expressions[16]); + expressionRunners[4] = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[17] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); expressions[18] = BinaryenLocalTee(the_module, 0, expressions[17], 2); expressions[19] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[20] = BinaryenBinary(the_module, 0, expressions[18], expressions[19]); - expressions[21] = ExpressionRunnerRunAndDispose(the_runner, expressions[20]); + expressions[21] = ExpressionRunnerRunAndDispose(expressionRunners[4], expressions[20]); BinaryenExpressionGetId(expressions[21]); BinaryenExpressionGetType(expressions[21]); BinaryenConstGetValueI32(expressions[21]); - the_runner = ExpressionRunnerCreate(the_module, 1, 0, 0); + expressionRunners[5] = ExpressionRunnerCreate(the_module, 1, 0, 0); expressions[22] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); expressions[23] = BinaryenLocalTee(the_module, 0, expressions[22], 2); expressions[24] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[25] = BinaryenBinary(the_module, 0, expressions[23], expressions[24]); - ExpressionRunnerRunAndDispose(the_runner, expressions[25]); - the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); + ExpressionRunnerRunAndDispose(expressionRunners[5], expressions[25]); + expressionRunners[6] = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[26] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); expressions[27] = BinaryenLocalSet(the_module, 0, expressions[26]); expressions[28] = BinaryenLocalGet(the_module, 0, 2); @@ -77,23 +77,23 @@ int main() { expressions[33] = BinaryenBlock(the_module, NULL, children, 2, 2); } expressions[34] = BinaryenBinary(the_module, 0, expressions[29], expressions[33]); - expressions[35] = ExpressionRunnerRunAndDispose(the_runner, expressions[34]); + expressions[35] = ExpressionRunnerRunAndDispose(expressionRunners[6], expressions[34]); BinaryenExpressionGetId(expressions[35]); BinaryenExpressionGetType(expressions[35]); BinaryenConstGetValueI32(expressions[35]); - the_runner = ExpressionRunnerCreate(the_module, 1, 0, 0); + expressionRunners[7] = ExpressionRunnerCreate(the_module, 1, 0, 0); expressions[36] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); - ExpressionRunnerSetLocalValue(the_runner, 0, expressions[36]); + ExpressionRunnerSetLocalValue(expressionRunners[7], 0, expressions[36]); expressions[37] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); - ExpressionRunnerSetGlobalValue(the_runner, "aGlobal", expressions[37]); + ExpressionRunnerSetGlobalValue(expressionRunners[7], "aGlobal", expressions[37]); expressions[38] = BinaryenLocalGet(the_module, 0, 2); expressions[39] = BinaryenGlobalGet(the_module, "aGlobal", 2); expressions[40] = BinaryenBinary(the_module, 0, expressions[38], expressions[39]); - expressions[41] = ExpressionRunnerRunAndDispose(the_runner, expressions[40]); + expressions[41] = ExpressionRunnerRunAndDispose(expressionRunners[7], expressions[40]); BinaryenExpressionGetId(expressions[41]); BinaryenExpressionGetType(expressions[41]); BinaryenConstGetValueI32(expressions[41]); - the_runner = ExpressionRunnerCreate(the_module, 2, 0, 0); + expressionRunners[8] = ExpressionRunnerCreate(the_module, 2, 0, 0); { BinaryenType t0[] = {2, 2}; BinaryenTypeCreate(t0, 2); // 11 @@ -110,7 +110,7 @@ int main() { functions[0] = BinaryenAddFunction(the_module, "add", 11, 2, varTypes, 0, expressions[45]); } expressions[46] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); - ExpressionRunnerSetLocalValue(the_runner, 0, expressions[46]); + ExpressionRunnerSetLocalValue(expressionRunners[8], 0, expressions[46]); expressions[47] = BinaryenLocalGet(the_module, 0, 2); expressions[48] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); expressions[49] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); @@ -121,11 +121,11 @@ int main() { expressions[51] = BinaryenBinary(the_module, 0, expressions[47], expressions[50]); expressions[52] = BinaryenLocalGet(the_module, 0, 2); expressions[53] = BinaryenBinary(the_module, 0, expressions[51], expressions[52]); - expressions[54] = ExpressionRunnerRunAndDispose(the_runner, expressions[53]); + expressions[54] = ExpressionRunnerRunAndDispose(expressionRunners[8], expressions[53]); BinaryenExpressionGetId(expressions[54]); BinaryenExpressionGetType(expressions[54]); BinaryenConstGetValueI32(expressions[54]); - the_runner = ExpressionRunnerCreate(the_module, 0, 0, 0); + expressionRunners[9] = ExpressionRunnerCreate(the_module, 0, 0, 0); expressions[55] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); expressions[56] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); expressions[57] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); @@ -134,18 +134,26 @@ int main() { expressions[58] = BinaryenCall(the_module, "add", operands, 2, 2); } expressions[59] = BinaryenBinary(the_module, 0, expressions[55], expressions[58]); - ExpressionRunnerRunAndDispose(the_runner, expressions[59]); - the_runner = ExpressionRunnerCreate(the_module, 0, 1, 0); + ExpressionRunnerRunAndDispose(expressionRunners[9], expressions[59]); + expressionRunners[10] = ExpressionRunnerCreate(the_module, 0, 1, 0); expressions[60] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); { BinaryenExpressionRef children[] = { expressions[60] }; expressions[61] = BinaryenBlock(the_module, NULL, children, 1, 2); } - ExpressionRunnerRunAndDispose(the_runner, expressions[61]); - the_runner = ExpressionRunnerCreate(the_module, 0, 50, 3); + ExpressionRunnerRunAndDispose(expressionRunners[10], expressions[61]); + expressionRunners[11] = ExpressionRunnerCreate(the_module, 0, 50, 3); expressions[62] = BinaryenBreak(the_module, "theLoop", expressions[0], expressions[0]); expressions[63] = BinaryenLoop(the_module, "theLoop", expressions[62]); - ExpressionRunnerRunAndDispose(the_runner, expressions[63]); + ExpressionRunnerRunAndDispose(expressionRunners[11], expressions[63]); + BinaryenModuleDispose(the_module); + expressions.clear(); + functions.clear(); + globals.clear(); + events.clear(); + exports.clear(); + relooperBlocks.clear(); + expressionRunners.clear(); return 0; } // ending a Binaryen API trace diff --git a/test/binaryen.js/inlining-options.js.txt b/test/binaryen.js/inlining-options.js.txt index 6adbd5583db..e8d6f6b99ee 100644 --- a/test/binaryen.js/inlining-options.js.txt +++ b/test/binaryen.js/inlining-options.js.txt @@ -9,9 +9,9 @@ int main() { std::map events; std::map exports; std::map relooperBlocks; + std::map expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; - ExpressionRunnerRef the_runner = NULL; BinaryenGetAlwaysInlineMaxSize(); // alwaysInlineMaxSize=2 BinaryenSetAlwaysInlineMaxSize(11); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index bde7ed9b9f5..e850e6e6cad 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -9,9 +9,9 @@ int main() { std::map events; std::map exports; std::map relooperBlocks; + std::map expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; - ExpressionRunnerRef the_runner = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); BinaryenAddEvent(the_module, "a-event", 0, 2, 0); @@ -5461,6 +5461,7 @@ getExpressionInfo(tuple[3])={"id":14,"type":5,"value":3.7} events.clear(); exports.clear(); relooperBlocks.clear(); + expressionRunners.clear(); the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); BinaryenAddFunctionImport(the_module, "check", "module", "check", 2, 0); @@ -6390,6 +6391,7 @@ optimized: events.clear(); exports.clear(); relooperBlocks.clear(); + expressionRunners.clear(); // BinaryenTypeNone: 0 // [] // BinaryenTypeUnreachable: 1 diff --git a/test/binaryen.js/low-memory-unused.js.txt b/test/binaryen.js/low-memory-unused.js.txt index 17058bbff78..b1c73352019 100644 --- a/test/binaryen.js/low-memory-unused.js.txt +++ b/test/binaryen.js/low-memory-unused.js.txt @@ -53,9 +53,9 @@ int main() { std::map events; std::map exports; std::map relooperBlocks; + std::map expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; - ExpressionRunnerRef the_runner = NULL; BinaryenSetLowMemoryUnused(1); BinaryenGetLowMemoryUnused(); return 0; diff --git a/test/binaryen.js/pass-arguments.js.txt b/test/binaryen.js/pass-arguments.js.txt index 084189c21fb..6d4f898a1a1 100644 --- a/test/binaryen.js/pass-arguments.js.txt +++ b/test/binaryen.js/pass-arguments.js.txt @@ -9,9 +9,9 @@ int main() { std::map events; std::map exports; std::map relooperBlocks; + std::map expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; - ExpressionRunnerRef the_runner = NULL; BinaryenGetPassArgument("theKey"); BinaryenSetPassArgument("theKey", "theValue"); BinaryenGetPassArgument("theKey"); diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 5fe7d700639..71554c02257 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -9,9 +9,9 @@ int main() { std::map events; std::map exports; std::map relooperBlocks; + std::map expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; - ExpressionRunnerRef the_runner = NULL; the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); expressions[1] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); @@ -3616,6 +3616,7 @@ int main() { events.clear(); exports.clear(); relooperBlocks.clear(); + expressionRunners.clear(); the_module = BinaryenModuleCreate(); expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); BinaryenAddFunctionImport(the_module, "check", "module", "check", 2, 0); @@ -4538,6 +4539,7 @@ optimized: events.clear(); exports.clear(); relooperBlocks.clear(); + expressionRunners.clear(); // BinaryenTypeNone: 0 // BinaryenTypeUnreachable: 1 // BinaryenTypeInt32: 2 From 8586c5723bbe3dc813eae8fa8b9b082e38fe790c Mon Sep 17 00:00:00 2001 From: dcode Date: Tue, 7 Apr 2020 12:51:01 +0200 Subject: [PATCH 26/27] mention interaction between TraverseCalls and PreserveSideEffects --- src/binaryen-c.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 06e594a574c..9b984fd6893 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1671,7 +1671,8 @@ BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsPreserveSideeffects(); // Traverse through function calls, attempting to compute their concrete value. // Must not be used in function-parallel scenarios, where the called function -// might be concurrently modified, leading to undefined behavior. +// might be concurrently modified, leading to undefined behavior. Traversing +// another function reuses all of this runner's flags. BINARYEN_API ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls(); // Creates an ExpressionRunner instance From 17e5de04b602b5250e6d0312d561faf72a520838 Mon Sep 17 00:00:00 2001 From: dcode Date: Fri, 17 Apr 2020 00:01:12 +0200 Subject: [PATCH 27/27] address comments --- src/binaryen-c.cpp | 16 ++++++++-------- test/binaryen.js/expressionrunner.js | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 0982e6aff30..d9d3300828f 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -209,20 +209,20 @@ size_t noteExpression(BinaryenExpressionRef expression) { return id; } -// We would normally use the size of `expressionRunners` as the next index, but -// since we are going to delete runners the same address can become reused, -// which would result in unpredictable sizes (indexes) due to undefined -// behavior. Use a sequential id instead. -static size_t nextExpressionRunnerId = 0; - // Even though unlikely, it is possible that we are trying to use an id that is // still in use after wrapping around, which we must prevent. -std::unordered_set usedExpressionRunnerIds; +static std::unordered_set usedExpressionRunnerIds; size_t noteExpressionRunner(ExpressionRunnerRef runner) { + // We would normally use the size of `expressionRunners` as the next index, + // but since we are going to delete runners the same address can become + // reused, which would result in unpredictable sizes (indexes) due to + // undefined behavior. Use a sequential id instead. + static size_t nextId = 0; + size_t id; do { - id = nextExpressionRunnerId++; + id = nextId++; } while (usedExpressionRunnerIds.find(id) != usedExpressionRunnerIds.end()); expressionRunners[runner] = id; usedExpressionRunnerIds.insert(id); diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js index d42c3eb7330..35117c45393 100644 --- a/test/binaryen.js/expressionrunner.js +++ b/test/binaryen.js/expressionrunner.js @@ -7,8 +7,8 @@ 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]); + for (let i in x) assertDeepEqual(x[i], y[i]); + for (let i in y) assertDeepEqual(x[i], y[i]); } else { assert(x === y); }