diff options
author | Daniel Wirtz <dcode@dcode.io> | 2020-04-20 23:01:26 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-20 14:01:26 -0700 |
commit | 483d759230f4693abfca3a74a97b1c1db6d2a0d6 (patch) | |
tree | c37b39802b9e5791abb3e329b88ba7185557de49 /src | |
parent | 1dc820c913712a2c50d05caee77e90a7ec49d2e1 (diff) | |
download | binaryen-483d759230f4693abfca3a74a97b1c1db6d2a0d6.tar.gz binaryen-483d759230f4693abfca3a74a97b1c1db6d2a0d6.tar.bz2 binaryen-483d759230f4693abfca3a74a97b1c1db6d2a0d6.zip |
Refactor expression runner so it can be used via the C and JS APIs (#2702)
Refactors most of the precompute pass's expression runner into its
base class so it can also be used via the C and JS APIs. Also adds
the option to populate the runner with known constant local and global
values upfront, and remembers assigned intermediate values as well
as traversing into functions if requested.
Diffstat (limited to 'src')
-rw-r--r-- | src/binaryen-c.cpp | 138 | ||||
-rw-r--r-- | src/binaryen-c.h | 61 | ||||
-rw-r--r-- | src/js/binaryen.js-post.js | 25 | ||||
-rw-r--r-- | src/passes/Precompute.cpp | 103 | ||||
-rw-r--r-- | src/wasm-interpreter.h | 268 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 1 |
6 files changed, 493 insertions, 103 deletions
diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 2ea4bd87f..d9d330082 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -135,6 +135,7 @@ std::map<BinaryenGlobalRef, size_t> globals; std::map<BinaryenEventRef, size_t> events; std::map<BinaryenExportRef, size_t> exports; std::map<RelooperBlockRef, size_t> relooperBlocks; +std::map<ExpressionRunnerRef, size_t> expressionRunners; static bool isBasicAPIType(BinaryenType type) { return type == BinaryenTypeAuto() || type <= Type::_last_value_type; @@ -208,6 +209,26 @@ size_t noteExpression(BinaryenExpressionRef expression) { return id; } +// 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. +static std::unordered_set<size_t> 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 = nextId++; + } 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++); @@ -604,6 +625,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"; types.clear(); expressions.clear(); functions.clear(); @@ -4951,6 +4973,121 @@ BinaryenExpressionRef RelooperRenderAndDispose(RelooperRef relooper, } // +// ========= ExpressionRunner ========= +// + +namespace wasm { + +class CExpressionRunner final : public ExpressionRunner<CExpressionRunner> { +public: + CExpressionRunner(Module* module, + CExpressionRunner::Flags flags, + Index maxDepth, + Index maxLoopIterations) + : ExpressionRunner<CExpressionRunner>( + module, flags, maxDepth, maxLoopIterations) {} + + void trap(const char* why) override { throw NonconstantException(); } +}; + +} // namespace wasm + +ExpressionRunnerFlags ExpressionRunnerFlagsDefault() { + return CExpressionRunner::FlagValues::DEFAULT; +} + +ExpressionRunnerFlags ExpressionRunnerFlagsPreserveSideeffects() { + return CExpressionRunner::FlagValues::PRESERVE_SIDEEFFECTS; +} + +ExpressionRunnerFlags ExpressionRunnerFlagsTraverseCalls() { + return CExpressionRunner::FlagValues::TRAVERSE_CALLS; +} + +ExpressionRunnerRef ExpressionRunnerCreate(BinaryenModuleRef module, + ExpressionRunnerFlags flags, + BinaryenIndex maxDepth, + BinaryenIndex maxLoopIterations) { + auto* wasm = (Module*)module; + 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(expressionRunners[" + << expressionRunners[runner] << "], " << index << ", expressions[" + << expressions[value] << "]);\n"; + } + + auto* R = (CExpressionRunner*)runner; + auto setFlow = R->visit(value); + if (!setFlow.breaking()) { + R->setLocalValue(index, setFlow.values); + return 1; + } + return 0; +} + +int ExpressionRunnerSetGlobalValue(ExpressionRunnerRef runner, + const char* name, + BinaryenExpressionRef value) { + if (tracing) { + std::cout << " ExpressionRunnerSetGlobalValue(expressionRunners[" + << expressionRunners[runner] << "], "; + traceNameOrNULL(name); + std::cout << ", expressions[" << expressions[value] << "]);\n"; + } + + auto* R = (CExpressionRunner*)runner; + auto setFlow = R->visit(value); + if (!setFlow.breaking()) { + R->setGlobalValue(name, setFlow.values); + return 1; + } + return 0; +} + +BinaryenExpressionRef +ExpressionRunnerRunAndDispose(ExpressionRunnerRef runner, + BinaryenExpressionRef expr) { + 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 (CExpressionRunner::NonconstantException&) { + } + + if (tracing) { + if (ret != nullptr) { + auto id = noteExpression(ret); + std::cout << " expressions[" << id << "] = "; + } else { + std::cout << " "; + } + auto id = expressionRunners[runner]; + std::cout << "ExpressionRunnerRunAndDispose(expressionRunners[" << id + << "], expressions[" << expressions[expr] << "]);\n"; + usedExpressionRunnerIds.erase(id); + } + + delete R; + return ret; +} + +// // ========= Other APIs ========= // @@ -4970,6 +5107,7 @@ void BinaryenSetAPITracing(int on) { " std::map<size_t, BinaryenEventRef> events;\n" " std::map<size_t, BinaryenExportRef> exports;\n" " std::map<size_t, RelooperBlockRef> relooperBlocks;\n" + " std::map<size_t, ExpressionRunnerRef> expressionRunners;\n" " BinaryenModuleRef the_module = NULL;\n" " RelooperRef the_relooper = NULL;\n"; } else { diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 4387fb638..c4956b423 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -1643,6 +1643,67 @@ BINARYEN_API BinaryenExpressionRef RelooperRenderAndDispose( RelooperRef relooper, RelooperBlockRef entry, BinaryenIndex labelHelper); // +// ========= ExpressionRunner ========== +// + +#ifdef __cplusplus +namespace wasm { +class CExpressionRunner; +} // namespace wasm +typedef class wasm::CExpressionRunner* ExpressionRunnerRef; +#else +typedef struct CExpressionRunner* ExpressionRunnerRef; +#endif + +typedef uint32_t ExpressionRunnerFlags; + +// 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(); + +// 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 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 +BINARYEN_API ExpressionRunnerRef +ExpressionRunnerCreate(BinaryenModuleRef module, + ExpressionRunnerFlags flags, + BinaryenIndex maxDepth, + BinaryenIndex maxLoopIterations); + +// Sets a known local value to use. Order matters if expressions have side +// 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. 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); + +// 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 44dc7174b..7bd88fadc 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -485,6 +485,13 @@ function initializeConstants() { ].forEach(function(name) { Module['SideEffects'][name] = Module['_BinaryenSideEffect' + name](); }); + + // ExpressionRunner flags + Module['ExpressionRunner']['Flags'] = { + 'Default': Module['_ExpressionRunnerFlagsDefault'](), + 'PreserveSideeffects': Module['_ExpressionRunnerFlagsPreserveSideeffects'](), + 'TraverseCalls': Module['_ExpressionRunnerFlagsTraverseCalls']() + }; } // 'Module' interface @@ -2427,6 +2434,24 @@ Module['Relooper'] = function(module) { }; }; +// 'ExpressionRunner' interface +Module['ExpressionRunner'] = function(module, flags, maxDepth, maxLoopIterations) { + var runner = Module['_ExpressionRunnerCreate'](module['ptr'], flags, maxDepth, maxLoopIterations); + 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); + }; +}; + 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 d809b8b43..dd390150e 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -39,53 +39,41 @@ 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<LocalGet*, Literals> GetValues; // Precomputes an expression. Errors if we hit anything that can't be // precomputed. class PrecomputingExpressionRunner : public ExpressionRunner<PrecomputingExpressionRunner> { - Module* module; - // 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; - // 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; + // 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, GetValues& getValues, bool replaceExpression) - : ExpressionRunner<PrecomputingExpressionRunner>(MAX_DEPTH), module(module), - getValues(getValues), replaceExpression(replaceExpression) {} - - struct NonstandaloneException { - }; // TODO: use a flow with a special name, as this is likely very slow + : ExpressionRunner<PrecomputingExpressionRunner>( + module, + replaceExpression ? FlagValues::PRESERVE_SIDEEFFECTS + : FlagValues::DEFAULT, + MAX_DEPTH, + MAX_LOOP_ITERATIONS), + getValues(getValues) {} - 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()) { @@ -94,51 +82,10 @@ public: 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); + return ExpressionRunner<PrecomputingExpressionRunner>::visitLocalGet(curr); } - 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(); } + void trap(const char* why) override { throw NonconstantException(); } }; struct Precompute @@ -223,7 +170,7 @@ struct Precompute return; } if (flow.breaking()) { - if (flow.breakTo == NOTPRECOMPUTABLE_FLOW) { + if (flow.breakTo == NONCONSTANT_FLOW) { return; } if (flow.breakTo == RETURN_FLOW) { @@ -276,8 +223,8 @@ private: return PrecomputingExpressionRunner( getModule(), getValues, replaceExpression) .visit(curr); - } catch (PrecomputingExpressionRunner::NonstandaloneException&) { - return Flow(NOTPRECOMPUTABLE_FLOW); + } catch (PrecomputingExpressionRunner::NonconstantException&) { + return Flow(NONCONSTANT_FLOW); } } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index fed58d060..6432eee79 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. @@ -149,11 +149,53 @@ public: // Execute an expression template<typename SubType> class ExpressionRunner : public OverriddenVisitor<SubType, Flow> { +public: + 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. 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 be concurrently modified, leading to undefined 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; + + // Indicates no limit of maxDepth or maxLoopIterations. + static const Index NO_LIMIT = 0; + protected: - Index maxDepth; + // 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 = FlagValues::DEFAULT; + // Maximum depth before giving up. + Index maxDepth = NO_LIMIT; Index depth = 0; + // 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<Index, Literals> localValues; + // Map remembering concrete global values set in the context of this flow. + std::unordered_map<Name, Literals> globalValues; + Flow generateArguments(const ExpressionList& operands, LiteralList& arguments) { NOTE_ENTER_("generateArguments"); @@ -170,11 +212,36 @@ protected: } public: + ExpressionRunner() {} ExpressionRunner(Index maxDepth) : maxDepth(maxDepth) {} + ExpressionRunner(Module* module, + Flags flags, + Index maxDepth, + Index maxLoopIterations) + : 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; } + + // Sets a known local value to use. + void setLocalValue(Index index, Literals& values) { + assert(values.isConcrete()); + localValues[index] = values; + } + + // Sets a known global value to use. + void setGlobalValue(Name name, Literals& values) { + assert(values.isConcrete()); + globalValues[name] = values; + } Flow visit(Expression* curr) { depth++; - if (depth > maxDepth) { + if (maxDepth != NO_LIMIT && depth > maxDepth) { trap("interpreter recursion limit"); } auto ret = OverriddenVisitor<SubType, Flow>::visit(curr); @@ -250,10 +317,15 @@ public: } 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 != NO_LIMIT && + ++loopCount >= maxLoopIterations) { + return Flow(NONCONSTANT_FLOW); + } continue; // lol } } @@ -1174,28 +1246,171 @@ public: 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, remember the + // constant value set, if any, and see if there is a value flowing through + // a tee. + auto setFlow = visit(curr->value); + if (!setFlow.breaking()) { + setLocalValue(curr->index, setFlow.values); + if (curr->type.isConcrete()) { + assert(curr->isTee()); + return setFlow; + } + 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_); + auto setFlow = visit(curr->value); + if (!setFlow.breaking()) { + setGlobalValue(curr->name, setFlow.values); + 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()); + 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()); + localValues[i] = std::move(argFlow.values); + } + } + auto retFlow = visit(func->body); + localValues = std::move(prevLocalValues); + 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(); @@ -1245,7 +1460,10 @@ public: trap("rethrow"); WASM_UNREACHABLE("rethrow"); } - Flow visitBrOnExn(BrOnExn* curr) { WASM_UNREACHABLE("unimp"); } + Flow visitBrOnExn(BrOnExn* curr) { + NOTE_ENTER("BrOnExn"); + return Flow(NONCONSTANT_FLOW); + } virtual void trap(const char* why) { WASM_UNREACHABLE(why); } }; diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 87981729e..bce1aa4de 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 { |