diff options
-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 | ||||
-rw-r--r-- | test/binaryen.js/custom-section.js.txt | 1 | ||||
-rw-r--r-- | test/binaryen.js/expressionrunner.js | 208 | ||||
-rw-r--r-- | test/binaryen.js/expressionrunner.js.txt | 161 | ||||
-rw-r--r-- | test/binaryen.js/inlining-options.js.txt | 1 | ||||
-rw-r--r-- | test/binaryen.js/kitchen-sink.js.txt | 3 | ||||
-rw-r--r-- | test/binaryen.js/low-memory-unused.js.txt | 1 | ||||
-rw-r--r-- | test/binaryen.js/pass-arguments.js.txt | 1 | ||||
-rw-r--r-- | test/example/c-api-kitchen-sink.txt | 3 | ||||
-rw-r--r-- | test/passes/precompute_all-features.txt | 3 | ||||
-rw-r--r-- | test/passes/precompute_all-features.wast | 7 |
16 files changed, 882 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 { diff --git a/test/binaryen.js/custom-section.js.txt b/test/binaryen.js/custom-section.js.txt index 063ef9dde..7c68582f1 100644 --- a/test/binaryen.js/custom-section.js.txt +++ b/test/binaryen.js/custom-section.js.txt @@ -10,6 +10,7 @@ int main() { std::map<size_t, BinaryenEventRef> events; std::map<size_t, BinaryenExportRef> exports; std::map<size_t, RelooperBlockRef> relooperBlocks; + std::map<size_t, ExpressionRunnerRef> expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; the_module = BinaryenModuleCreate(); diff --git a/test/binaryen.js/expressionrunner.js b/test/binaryen.js/expressionrunner.js new file mode 100644 index 000000000..35117c453 --- /dev/null +++ b/test/binaryen.js/expressionrunner.js @@ -0,0 +1,208 @@ +var Flags = binaryen.ExpressionRunner.Flags; +console.log("// ExpressionRunner.Flags.Default = " + Flags.Default); +console.log("// ExpressionRunner.Flags.PreserveSideeffects = " + Flags.PreserveSideeffects); +console.log("// ExpressionRunner.Flags.TraverseCalls = " + Flags.TraverseCalls); + +binaryen.setAPITracing(true); + +function assertDeepEqual(x, y) { + if (typeof x === "object") { + for (let i in x) assertDeepEqual(x[i], y[i]); + for (let 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)); + +// Should evaluate down to a constant +var runner = new binaryen.ExpressionRunner(module); +var expr = runner.runAndDispose( + module.i32.add( + module.i32.const(1), + module.i32.const(2) + ) +); +assertDeepEqual( + binaryen.getExpressionInfo(expr), + { + id: binaryen.ExpressionIds.Const, + type: binaryen.i32, + value: 3 + } +); + +// Should traverse control structures +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) + ) + ), +); +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); +expr = runner.runAndDispose( + module.i32.add( + module.local.get(0, binaryen.i32), + module.i32.const(1) + ) +); +assert(expr === 0); + +// Should handle traps properly +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); +expr = runner.runAndDispose( + module.i32.add( + module.local.tee(0, module.i32.const(4), binaryen.i32), + module.i32.const(1) + ) +); +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); +expr = runner.runAndDispose( + module.i32.add( + module.local.tee(0, module.i32.const(4), binaryen.i32), + module.i32.const(1) + ) +); +assert(expr === 0); + +// Should work with temporary values if just evaluating the expression +runner = new binaryen.ExpressionRunner(module); +expr = runner.runAndDispose( + module.i32.add( + module.block(null, [ + module.local.set(0, module.i32.const(2)), + module.local.get(0, binaryen.i32) + ], binaryen.i32), + module.block(null, [ + module.global.set("aGlobal", module.i32.const(4)), + module.global.get("aGlobal", binaryen.i32) + ], binaryen.i32) + ) +); +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); +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) + ) +); +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); +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) +); +assert(runner.setLocalValue(0, module.i32.const(1))); +expr = runner.runAndDispose( + module.i32.add( + 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) + ) +); +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); +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); + +// 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); + +module.dispose(); +binaryen.setAPITracing(false); diff --git a/test/binaryen.js/expressionrunner.js.txt b/test/binaryen.js/expressionrunner.js.txt new file mode 100644 index 000000000..4d74c9feb --- /dev/null +++ b/test/binaryen.js/expressionrunner.js.txt @@ -0,0 +1,161 @@ +// ExpressionRunner.Flags.Default = 0 +// ExpressionRunner.Flags.PreserveSideeffects = 1 +// ExpressionRunner.Flags.TraverseCalls = 2 +// beginning a Binaryen API trace +#include <math.h> +#include <map> +#include "binaryen-c.h" +int main() { + std::map<size_t, BinaryenType> types; + std::map<size_t, BinaryenExpressionRef> expressions; + std::map<size_t, BinaryenFunctionRef> functions; + std::map<size_t, BinaryenGlobalRef> globals; + std::map<size_t, BinaryenEventRef> events; + std::map<size_t, BinaryenExportRef> exports; + std::map<size_t, RelooperBlockRef> relooperBlocks; + std::map<size_t, ExpressionRunnerRef> expressionRunners; + BinaryenModuleRef the_module = NULL; + RelooperRef the_relooper = NULL; + the_module = BinaryenModuleCreate(); + expressions[size_t(NULL)] = BinaryenExpressionRef(NULL); + expressions[1] = BinaryenConst(the_module, BinaryenLiteralInt32(0)); + globals[0] = BinaryenAddGlobal(the_module, "aGlobal", BinaryenTypeInt32(), 1, expressions[1]); + 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(expressionRunners[0], expressions[4]); + BinaryenExpressionGetId(expressions[5]); + BinaryenExpressionGetType(expressions[5]); + BinaryenConstGetValueI32(expressions[5]); + 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(expressionRunners[1], expressions[11]); + BinaryenExpressionGetId(expressions[12]); + BinaryenExpressionGetType(expressions[12]); + BinaryenConstGetValueI32(expressions[12]); + expressionRunners[2] = ExpressionRunnerCreate(the_module, 0, 0, 0); + expressions[13] = BinaryenLocalGet(the_module, 0, BinaryenTypeInt32()); + expressions[14] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[15] = BinaryenBinary(the_module, 0, expressions[13], expressions[14]); + ExpressionRunnerRunAndDispose(expressionRunners[2], expressions[15]); + expressionRunners[3] = ExpressionRunnerCreate(the_module, 0, 0, 0); + expressions[16] = BinaryenUnreachable(the_module); + 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], BinaryenTypeInt32()); + expressions[19] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[20] = BinaryenBinary(the_module, 0, expressions[18], expressions[19]); + expressions[21] = ExpressionRunnerRunAndDispose(expressionRunners[4], expressions[20]); + BinaryenExpressionGetId(expressions[21]); + BinaryenExpressionGetType(expressions[21]); + BinaryenConstGetValueI32(expressions[21]); + expressionRunners[5] = ExpressionRunnerCreate(the_module, 1, 0, 0); + expressions[22] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + expressions[23] = BinaryenLocalTee(the_module, 0, expressions[22], BinaryenTypeInt32()); + expressions[24] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + expressions[25] = BinaryenBinary(the_module, 0, expressions[23], expressions[24]); + 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, BinaryenTypeInt32()); + { + BinaryenExpressionRef children[] = { expressions[27], expressions[28] }; + expressions[29] = BinaryenBlock(the_module, NULL, children, 2, BinaryenTypeInt32()); + } + expressions[30] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + expressions[31] = BinaryenGlobalSet(the_module, "aGlobal", expressions[30]); + expressions[32] = BinaryenGlobalGet(the_module, "aGlobal", BinaryenTypeInt32()); + { + BinaryenExpressionRef children[] = { expressions[31], expressions[32] }; + expressions[33] = BinaryenBlock(the_module, NULL, children, 2, BinaryenTypeInt32()); + } + expressions[34] = BinaryenBinary(the_module, 0, expressions[29], expressions[33]); + expressions[35] = ExpressionRunnerRunAndDispose(expressionRunners[6], expressions[34]); + BinaryenExpressionGetId(expressions[35]); + BinaryenExpressionGetType(expressions[35]); + BinaryenConstGetValueI32(expressions[35]); + expressionRunners[7] = ExpressionRunnerCreate(the_module, 1, 0, 0); + expressions[36] = BinaryenConst(the_module, BinaryenLiteralInt32(3)); + ExpressionRunnerSetLocalValue(expressionRunners[7], 0, expressions[36]); + expressions[37] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + ExpressionRunnerSetGlobalValue(expressionRunners[7], "aGlobal", expressions[37]); + expressions[38] = BinaryenLocalGet(the_module, 0, BinaryenTypeInt32()); + expressions[39] = BinaryenGlobalGet(the_module, "aGlobal", BinaryenTypeInt32()); + expressions[40] = BinaryenBinary(the_module, 0, expressions[38], expressions[39]); + expressions[41] = ExpressionRunnerRunAndDispose(expressionRunners[7], expressions[40]); + BinaryenExpressionGetId(expressions[41]); + BinaryenExpressionGetType(expressions[41]); + BinaryenConstGetValueI32(expressions[41]); + expressionRunners[8] = ExpressionRunnerCreate(the_module, 2, 0, 0); + { + BinaryenType t0[] = {BinaryenTypeInt32(), BinaryenTypeInt32()}; + types[0] = BinaryenTypeCreate(t0, 2); + } + expressions[42] = BinaryenLocalGet(the_module, 0, BinaryenTypeInt32()); + expressions[43] = BinaryenLocalGet(the_module, 1, BinaryenTypeInt32()); + expressions[44] = BinaryenBinary(the_module, 0, expressions[42], expressions[43]); + { + BinaryenExpressionRef children[] = { expressions[44] }; + expressions[45] = BinaryenBlock(the_module, NULL, children, 1, BinaryenTypeInt32()); + } + { + BinaryenType varTypes[] = { 0 }; + functions[0] = BinaryenAddFunction(the_module, "add", types[0], BinaryenTypeInt32(), varTypes, 0, expressions[45]); + } + expressions[46] = BinaryenConst(the_module, BinaryenLiteralInt32(1)); + ExpressionRunnerSetLocalValue(expressionRunners[8], 0, expressions[46]); + expressions[47] = BinaryenLocalGet(the_module, 0, BinaryenTypeInt32()); + expressions[48] = BinaryenConst(the_module, BinaryenLiteralInt32(2)); + expressions[49] = BinaryenConst(the_module, BinaryenLiteralInt32(4)); + { + BinaryenExpressionRef operands[] = { expressions[48], expressions[49] }; + expressions[50] = BinaryenCall(the_module, "add", operands, 2, BinaryenTypeInt32()); + } + expressions[51] = BinaryenBinary(the_module, 0, expressions[47], expressions[50]); + expressions[52] = BinaryenLocalGet(the_module, 0, BinaryenTypeInt32()); + expressions[53] = BinaryenBinary(the_module, 0, expressions[51], expressions[52]); + expressions[54] = ExpressionRunnerRunAndDispose(expressionRunners[8], expressions[53]); + BinaryenExpressionGetId(expressions[54]); + BinaryenExpressionGetType(expressions[54]); + BinaryenConstGetValueI32(expressions[54]); + 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)); + { + BinaryenExpressionRef operands[] = { expressions[56], expressions[57] }; + expressions[58] = BinaryenCall(the_module, "add", operands, 2, BinaryenTypeInt32()); + } + expressions[59] = BinaryenBinary(the_module, 0, expressions[55], expressions[58]); + 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, BinaryenTypeInt32()); + } + 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(expressionRunners[11], expressions[63]); + BinaryenModuleDispose(the_module); + types.clear(); + 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 077e366ce..91d360449 100644 --- a/test/binaryen.js/inlining-options.js.txt +++ b/test/binaryen.js/inlining-options.js.txt @@ -10,6 +10,7 @@ int main() { std::map<size_t, BinaryenEventRef> events; std::map<size_t, BinaryenExportRef> exports; std::map<size_t, RelooperBlockRef> relooperBlocks; + std::map<size_t, ExpressionRunnerRef> expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; BinaryenGetAlwaysInlineMaxSize(); diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 128c4ba51..7497e00a9 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -10,6 +10,7 @@ int main() { std::map<size_t, BinaryenEventRef> events; std::map<size_t, BinaryenExportRef> exports; std::map<size_t, RelooperBlockRef> relooperBlocks; + std::map<size_t, ExpressionRunnerRef> expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; the_module = BinaryenModuleCreate(); @@ -5462,6 +5463,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", BinaryenTypeInt32(), BinaryenTypeNone()); @@ -6392,6 +6394,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 4933825a5..6f6e82d9b 100644 --- a/test/binaryen.js/low-memory-unused.js.txt +++ b/test/binaryen.js/low-memory-unused.js.txt @@ -54,6 +54,7 @@ int main() { std::map<size_t, BinaryenEventRef> events; std::map<size_t, BinaryenExportRef> exports; std::map<size_t, RelooperBlockRef> relooperBlocks; + std::map<size_t, ExpressionRunnerRef> expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; BinaryenSetLowMemoryUnused(1); diff --git a/test/binaryen.js/pass-arguments.js.txt b/test/binaryen.js/pass-arguments.js.txt index df98df08c..d13c4cad7 100644 --- a/test/binaryen.js/pass-arguments.js.txt +++ b/test/binaryen.js/pass-arguments.js.txt @@ -10,6 +10,7 @@ int main() { std::map<size_t, BinaryenEventRef> events; std::map<size_t, BinaryenExportRef> exports; std::map<size_t, RelooperBlockRef> relooperBlocks; + std::map<size_t, ExpressionRunnerRef> expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; BinaryenGetPassArgument("theKey"); diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 1a2a8b067..183a1ec6b 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -10,6 +10,7 @@ int main() { std::map<size_t, BinaryenEventRef> events; std::map<size_t, BinaryenExportRef> exports; std::map<size_t, RelooperBlockRef> relooperBlocks; + std::map<size_t, ExpressionRunnerRef> expressionRunners; BinaryenModuleRef the_module = NULL; RelooperRef the_relooper = NULL; the_module = BinaryenModuleCreate(); @@ -3617,6 +3618,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", BinaryenTypeInt32(), BinaryenTypeNone()); @@ -4540,6 +4542,7 @@ optimized: events.clear(); exports.clear(); relooperBlocks.clear(); + expressionRunners.clear(); // BinaryenTypeNone: 0 // BinaryenTypeUnreachable: 1 // BinaryenTypeInt32: 2 diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt index 34c9554dd..9c7f4eab1 100644 --- a/test/passes/precompute_all-features.txt +++ b/test/passes/precompute_all-features.txt @@ -258,6 +258,9 @@ (i64.const 42) ) ) + (func $loop-precompute (result i32) + (i32.const 1) + ) (func $reftype-test (result nullref) (ref.null) ) diff --git a/test/passes/precompute_all-features.wast b/test/passes/precompute_all-features.wast index 2a59ea290..bbe984704 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) |