diff options
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 { |