diff options
-rwxr-xr-x | build-js.sh | 1 | ||||
-rw-r--r-- | src/ir/effects.h | 4 | ||||
-rw-r--r-- | src/ir/module-utils.h | 57 | ||||
-rw-r--r-- | src/pass.h | 7 | ||||
-rw-r--r-- | src/passes/Bysyncify.cpp | 909 | ||||
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/Inlining.cpp | 2 | ||||
-rw-r--r-- | src/passes/pass.cpp | 3 | ||||
-rw-r--r-- | src/passes/passes.h | 1 | ||||
-rw-r--r-- | src/wasm2js.h | 2 | ||||
-rw-r--r-- | test/passes/bysyncify.txt | 2710 | ||||
-rw-r--r-- | test/passes/bysyncify.wast | 146 | ||||
-rw-r--r-- | test/passes/bysyncify_optimize-level=1.txt | 1541 | ||||
-rw-r--r-- | test/passes/bysyncify_optimize-level=1.wast | 97 | ||||
-rw-r--r-- | test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.txt | 1998 | ||||
-rw-r--r-- | test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.wast | 146 | ||||
-rw-r--r-- | test/unit/input/bysyncify.js | 155 | ||||
-rw-r--r-- | test/unit/input/bysyncify.wast | 200 | ||||
-rw-r--r-- | test/unit/test_bysyncify.py | 20 |
19 files changed, 7995 insertions, 5 deletions
diff --git a/build-js.sh b/build-js.sh index e5e991493..77eedda77 100755 --- a/build-js.sh +++ b/build-js.sh @@ -94,6 +94,7 @@ mkdir -p ${OUT} $BINARYEN_SRC/passes/pass.cpp \ $BINARYEN_SRC/passes/AlignmentLowering.cpp \ $BINARYEN_SRC/passes/AvoidReinterprets.cpp \ + $BINARYEN_SRC/passes/Bysyncify.cpp \ $BINARYEN_SRC/passes/CoalesceLocals.cpp \ $BINARYEN_SRC/passes/DeadArgumentElimination.cpp \ $BINARYEN_SRC/passes/CodeFolding.cpp \ diff --git a/src/ir/effects.h b/src/ir/effects.h index 7c64beafd..b7c420100 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -27,7 +27,7 @@ namespace wasm { struct EffectAnalyzer : public PostWalker<EffectAnalyzer, OverriddenVisitor<EffectAnalyzer>> { - EffectAnalyzer(PassOptions& passOptions, Expression* ast = nullptr) { + EffectAnalyzer(const PassOptions& passOptions, Expression* ast = nullptr) { ignoreImplicitTraps = passOptions.ignoreImplicitTraps; debugInfo = passOptions.debugInfo; if (ast) { @@ -372,7 +372,7 @@ struct EffectAnalyzer // Helpers static bool - canReorder(PassOptions& passOptions, Expression* a, Expression* b) { + canReorder(const PassOptions& passOptions, Expression* a, Expression* b) { EffectAnalyzer aEffects(passOptions, a); EffectAnalyzer bEffects(passOptions, b); return !aEffects.invalidates(bEffects); diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index c13e9f1ca..88298fd43 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -19,6 +19,7 @@ #include "ir/find_all.h" #include "ir/manipulation.h" +#include "pass.h" #include "wasm.h" namespace wasm { @@ -287,6 +288,62 @@ template<typename T> inline void iterDefinedEvents(Module& wasm, T visitor) { } } +// Performs a parallel map on function in the module, emitting a map object +// of function => result. +// TODO: use in inlining and elsewhere +template<typename T> struct ParallelFunctionMap { + + typedef std::map<Function*, T> Map; + Map map; + + typedef std::function<void(Function*, T&)> Func; + + struct Info { + Map* map; + Func work; + }; + + ParallelFunctionMap(Module& wasm, Func work) { + // Fill in map, as we operate on it in parallel (each function to its own + // entry). + for (auto& func : wasm.functions) { + map[func.get()]; + } + + // Run on the imports first. TODO: parallelize this too + for (auto& func : wasm.functions) { + if (func->imported()) { + work(func.get(), map[func.get()]); + } + } + + // Run on the implemented functions. + struct Mapper : public WalkerPass<PostWalker<Mapper>> { + + bool isFunctionParallel() override { return true; } + + Mapper(Info* info) : info(info) {} + + Mapper* create() override { return new Mapper(info); } + + void doWalkFunction(Function* curr) { + assert((*info->map).count(curr)); + info->work(curr, (*info->map)[curr]); + } + + private: + Info* info; + }; + + Info info = {&map, work}; + + PassRunner runner(&wasm); + runner.setIsNested(true); + runner.add<Mapper>(&info); + runner.run(); + } +}; + } // namespace ModuleUtils } // namespace wasm diff --git a/src/pass.h b/src/pass.h index 9f1be9a3b..0a9cc251f 100644 --- a/src/pass.h +++ b/src/pass.h @@ -127,6 +127,13 @@ struct PassOptions { } return arguments[key]; } + + std::string getArgumentOrDefault(std::string key, std::string default_) { + if (arguments.count(key) == 0) { + return default_; + } + return arguments[key]; + } }; // diff --git a/src/passes/Bysyncify.cpp b/src/passes/Bysyncify.cpp new file mode 100644 index 000000000..ffbf6db1c --- /dev/null +++ b/src/passes/Bysyncify.cpp @@ -0,0 +1,909 @@ +/* + * Copyright 2019 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Bysyncify: async/await style code transformation, that allows pausing +// and resuming. This lets a language support synchronous operations in +// an async manner, for example, you can do a blocking wait, and that will +// be turned into code that unwinds the stack at the "blocking" operation, +// then is able to rewind it back up when the actual async operation +// comples, the so the code appears to have been running synchronously +// all the while. Use cases for this include coroutines, python generators, +// etc. +// +// The approach here is a third-generation design after Emscripten's original +// Asyncify and then Emterpreter-Async approaches: +// +// * Asyncify rewrote control flow in LLVM IR. A problem is that this needs +// to save all SSA registers as part of the local state, which can be +// very costly. A further increase can happen because of phis that are +// added because of control flow transformations. As a result we saw +// pathological cases where the code size increase was unacceptable. +// * Emterpreter-Async avoids any risk of code size increase by running it +// in a little bytecode VM, which also makes pausing/resuming trivial. +// Speed is the main downside here; however, the approach did prove that by +// *selectively* emterpretifying only certain code, many projects can run +// at full speed - often just the "main loop" must be emterpreted, while +// high-speed code that it calls, and in which cannot be an async operation, +// remain at full speed. +// +// Bysyncify's design learns from both of those: +// +// * The code rewrite is done in Binaryen, that is, at the wasm level. At +// this level we will only need to save wasm locals, which is a much smaller +// number than LLVM's SSA registers. +// * We aim for low but non-zero overhead. Low overhead is very important +// for obvious reasons, while Emterpreter-Async proved it is tolerable to +// have *some* overhead, if the transform can be applied selectively. +// +// The specific transform implemented here is simpler than Asyncify but should +// still have low overhead when properly optimized. Asyncify worked at the CFG +// level and added branches there; Bysyncify on the other hand works on the +// structured control flow of wasm and simply "skips over" code when rewinding +// the stack, and jumps out when unwinding. The transformed code looks +// conceptually like this: +// +// void foo(int x) { +// // new prelude +// if (rewinding) { +// loadLocals(); +// } +// // main body starts here +// if (!rewinding) { +// // some code we must skip while rewinding +// x = x + 1; +// x = x / 2; +// } +// // If rewinding, do this call only if it is the right one. +// if (!rewinding or nextCall == 0) { +// bar(x); +// if (unwinding) { +// noteUnWound(0); +// saveLocals(); +// return; +// } +// } +// if (!rewinding) { +// // more code we must skip while rewinding +// while (x & 7) { +// x = x + 1; +// } +// } +// [..] +// +// The general idea is that while rewinding we just "keep going forward", +// skipping code we should not run. That is, instead of jumping directly +// to the right place, we have ifs that skip along instead. The ifs for +// rewinding and unwinding should be well-predicted, so the overhead should +// not be very high. However, we do need to reach the right location via +// such skipping, which means that in a very large function the rewind +// takes some extra time. On the other hand, though, code that cannot +// unwind or rewind (like that loop near the end) can run at full speed. +// Overall, this should allow good performance with small overhead that is +// mostly noticed at rewind time. +// +// After this pass is run a new i32 global "__bysyncify_state" is added, which +// has the following values: +// +// 0: normal execution +// 1: unwinding the stack +// 2: rewinding the stack +// +// There is also "__bysyncify_data" which when rewinding and unwinding +// contains a pointer to a data structure with the info needed to rewind +// and unwind: +// +// { // offsets +// i32 - current bysyncify stack location // 0 +// i32 - bysyncify stack end // 4 +// } +// +// The bysyncify stack is a representation of the call frame, as a list of +// indexes of calls. In the example above, we saw index "0" for calling "bar" +// from "foo". When unwinding, the indexes are added to the stack; when +// rewinding, they are popped off; the current bysyncify stack location is +// undated while doing both operations. The bysyncify stack is also used to +// save locals. Note that the stack end location is provided, which is for +// error detection. +// +// Note: all pointers are assumed to be 4-byte aligned. +// +// When you start an unwinding operation, you must set the initial fields +// of the data structure, that is, set the current stack location to the +// proper place, and the end to the proper end based on how much memory +// you reserved. Note that bysyncify will grow the stack up. +// +// The pass will also create three functions that let you control unwinding +// and rewinding: +// +// * bysyncify_start_unwind(data : i32): call this to start unwinding the +// stack from the current location. "data" must point to a data +// structure as described above (with fields containing valid data). +// +// * bysyncify_start_rewind(data : i32): call this to start rewinding the +// stack vack up to the location stored in the provided data. This prepares +// for the rewind; to start it, you must call the first function in the +// call stack to be unwound. +// +// * bysyncify_stop_rewind(): call this to note that rewinding has +// concluded, and normal execution can resume. +// +// These three functions are exported so that you can call them from the +// outside. If you want to manage things from inside the wasm, then you +// couldn't have called them before they were created by this pass. To work +// around that, you can create imports to bysyncify.start_unwind, +// bysyncify.start_rewind, and bysyncify.stop_rewind; if those exist when +// this pass runs then it will turn those into direct calls to the functions +// that it creates. +// +// To use this API, call bysyncify_start_unwind when you want to. The call +// stack will then be unwound, and so execution will resume in the JS or +// other host environment on the outside that called into wasm. Then when +// you want to rewind, call bysyncify_start_rewind, and then call the same +// initial function you called before, so that unwinding can begin. The +// unwinding will reach the same function from which you started, since +// we are recreating the call stack. At that point you should call +// bysyncify_stop_rewind and then execution can resume normally. +// +// By default this pass assumes that any import may call any of the +// exported bysyncify methods, that is, any import may start an unwind/rewind. +// To customize this, you can provide an argument to wasm-opt (or another +// tool that can run this pass), +// +// --pass-arg=bysyncify@module1.base1,module2.base2,module3.base3 +// +// Each module.base in that comma-separated list will be considered to +// be an import that can unwind/rewind, and all others are assumed not to +// (aside from the bysyncify.* imports, which are always assumed to). To +// say that no import (aside from bysyncify.*) can do so (that is, the +// opposite of the default behavior), you can simply provide an import +// that doesn't exist (say, +// --pass-arg=bysyncify@no.imports +// + +#include "ir/effects.h" +#include "ir/literal-utils.h" +#include "ir/module-utils.h" +#include "ir/utils.h" +#include "pass.h" +#include "support/unique_deferring_queue.h" +#include "wasm-builder.h" +#include "wasm.h" + +namespace wasm { + +namespace { + +static const Name BYSYNCIFY_STATE = "__bysyncify_state"; +static const Name BYSYNCIFY_DATA = "__bysyncify_data"; +static const Name BYSYNCIFY_START_UNWIND = "bysyncify_start_unwind"; +static const Name BYSYNCIFY_START_REWIND = "bysyncify_start_rewind"; +static const Name BYSYNCIFY_STOP_REWIND = "bysyncify_stop_rewind"; +static const Name BYSYNCIFY_UNWIND = "__bysyncify_unwind"; +static const Name BYSYNCIFY = "bysyncify"; +static const Name START_UNWIND = "start_unwind"; +static const Name START_REWIND = "start_rewind"; +static const Name STOP_REWIND = "stop_rewind"; +static const Name BYSYNCIFY_GET_CALL_INDEX = "__bysyncify_get_call_index"; +static const Name BYSYNCIFY_CHECK_CALL_INDEX = "__bysyncify_check_call_index"; + +// TODO: having just normal/unwind_or_rewind would decrease code +// size, but make debugging harder +enum class State { Normal = 0, Unwinding = 1, Rewinding = 2 }; + +enum class DataOffset { BStackPos = 0, BStackEnd = 4 }; + +const auto STACK_ALIGN = 4; + +// Analyze the entire module to see which calls may change the state, that +// is, start an unwind or rewind), either in itself or in something called +// by it. +class ModuleAnalyzer { + Module& module; + + struct Info { + bool canChangeState = false; + std::set<Function*> callsTo; + std::set<Function*> calledBy; + }; + + typedef std::map<Function*, Info> Map; + Map map; + +public: + ModuleAnalyzer(Module& module, + std::function<bool(Name, Name)> canImportChangeState) + : module(module) { + // Scan to see which functions can directly change the state. + // Also handle the bysyncify imports, removing them (as we will implement + // them later), and replace calls to them with calls to the later proper + // name. + ModuleUtils::ParallelFunctionMap<Info> scanner( + module, [&](Function* func, Info& info) { + if (func->imported()) { + // The bysyncify imports to start an unwind or rewind can definitely + // change the state. + if (func->module == BYSYNCIFY && + (func->base == START_UNWIND || func->base == START_REWIND)) { + info.canChangeState = true; + } else { + info.canChangeState = + canImportChangeState(func->module, func->base); + } + return; + } + struct Walker : PostWalker<Walker> { + void visitCall(Call* curr) { + auto* target = module->getFunction(curr->target); + if (target->imported() && target->module == BYSYNCIFY) { + // Redirect the imports to the functions we'll add later. + if (target->base == START_UNWIND) { + curr->target = BYSYNCIFY_START_UNWIND; + } else if (target->base == START_REWIND) { + curr->target = BYSYNCIFY_START_REWIND; + } else if (target->base == STOP_REWIND) { + curr->target = BYSYNCIFY_STOP_REWIND; + // TODO: in theory, this does not change the state + } else { + Fatal() << "call to unidenfied bysyncify import: " + << target->base; + } + info->canChangeState = true; + return; + } + info->callsTo.insert(target); + } + void visitCallIndirect(CallIndirect* curr) { + // TODO optimize + info->canChangeState = true; + } + Info* info; + Module* module; + }; + Walker walker; + walker.info = &info; + walker.module = &module; + walker.walk(func->body); + }); + map.swap(scanner.map); + + // Remove the bysyncify imports, if any. + for (auto& pair : map) { + auto* func = pair.first; + if (func->imported() && func->module == BYSYNCIFY) { + module.removeFunction(func->name); + } + } + + // Find what is called by what. + for (auto& pair : map) { + auto* func = pair.first; + auto& info = pair.second; + for (auto* target : info.callsTo) { + map[target].calledBy.insert(func); + } + } + + // Close over, finding all functions that can reach something that can + // change the state. + // The work queue contains an item we just learned can change the state. + UniqueDeferredQueue<Function*> work; + for (auto& func : module.functions) { + if (map[func.get()].canChangeState) { + work.push(func.get()); + } + } + while (!work.empty()) { + auto* func = work.pop(); + for (auto* caller : map[func].calledBy) { + if (!map[caller].canChangeState) { + map[caller].canChangeState = true; + work.push(caller); + } + } + } + } + + bool canChangeState(Function* func) { return map[func].canChangeState; } + + bool canChangeState(Expression* curr) { + // Look inside to see if we call any of the things we know can change the + // state. + // TODO: caching, this is O(N^2) + struct Walker : PostWalker<Walker> { + void visitCall(Call* curr) { + // We only implement these at the very end, but we know that they + // definitely change the state. + if (curr->target == BYSYNCIFY_START_UNWIND || + curr->target == BYSYNCIFY_START_REWIND || + curr->target == BYSYNCIFY_GET_CALL_INDEX || + curr->target == BYSYNCIFY_CHECK_CALL_INDEX) { + canChangeState = true; + return; + } + // The target may not exist if it is one of our temporary intrinsics. + auto* target = module->getFunctionOrNull(curr->target); + if (target && (*map)[target].canChangeState) { + canChangeState = true; + } + } + void visitCallIndirect(CallIndirect* curr) { + // TODO optimize + canChangeState = true; + } + Module* module; + Map* map; + bool canChangeState = false; + }; + Walker walker; + walker.module = &module; + walker.map = ↦ + walker.walk(curr); + return walker.canChangeState; + } +}; + +// Checks if something performs a call: either a direct or indirect call, +// and perhaps it is dropped or assigned to a local. This captures all the +// cases of a call in flat IR. +static bool doesCall(Expression* curr) { + if (auto* set = curr->dynCast<LocalSet>()) { + curr = set->value; + } else if (auto* drop = curr->dynCast<Drop>()) { + curr = drop->value; + } + return curr->is<Call>() || curr->is<CallIndirect>(); +} + +class BysyncifyBuilder : public Builder { +public: + BysyncifyBuilder(Module& wasm) : Builder(wasm) {} + + Expression* makeGetStackPos() { + return makeLoad(4, + false, + int32_t(DataOffset::BStackPos), + 4, + makeGlobalGet(BYSYNCIFY_DATA, i32), + i32); + } + + Expression* makeIncStackPos(int32_t by) { + if (by == 0) { + return makeNop(); + } + return makeStore( + 4, + int32_t(DataOffset::BStackPos), + 4, + makeGlobalGet(BYSYNCIFY_DATA, i32), + makeBinary(AddInt32, makeGetStackPos(), makeConst(Literal(by))), + i32); + } + + Expression* makeStateCheck(State value) { + return makeBinary(EqInt32, + makeGlobalGet(BYSYNCIFY_STATE, i32), + makeConst(Literal(int32_t(value)))); + } +}; + +// Instrument control flow, around calls and adding skips for rewinding. +struct BysyncifyFlow : public Pass { + bool isFunctionParallel() override { return true; } + + ModuleAnalyzer* analyzer; + + BysyncifyFlow* create() override { return new BysyncifyFlow(analyzer); } + + BysyncifyFlow(ModuleAnalyzer* analyzer) : analyzer(analyzer) {} + + void + runOnFunction(PassRunner* runner, Module* module_, Function* func) override { + module = module_; + // If the function cannot change our state, we have nothing to do - + // we will never unwind or rewind the stack here. + if (!analyzer->canChangeState(func)) { + return; + } + // Rewrite the function body. + builder = make_unique<BysyncifyBuilder>(*module); + // Each function we enter will pop one from the stack, which is the index + // of the next call to make. + auto* block = builder->makeBlock( + {builder->makeIf(builder->makeStateCheck( + State::Rewinding), // TODO: such checks can be !normal + makeCallIndexPop()), + process(func->body)}); + if (func->result != none) { + // Rewriting control flow may alter things; make sure the function ends in + // something valid (which the optimizer can remove later). + block->list.push_back(builder->makeUnreachable()); + } + block->finalize(); + func->body = block; + // Making things like returns conditional may alter types. + ReFinalize().walkFunctionInModule(func, module); + } + +private: + std::unique_ptr<BysyncifyBuilder> builder; + + Module* module; + + // Each call in the function has an index, noted during unwind and checked + // during rewind. + Index callIndex = 0; + + Expression* process(Expression* curr) { + if (!analyzer->canChangeState(curr)) { + return makeMaybeSkip(curr); + } + // The IR is in flat form, which makes this much simpler. We basically + // need to add skips to avoid code when rewinding, and checks around calls + // with unwinding/rewinding support. + // + // Aside from that, we must "linearize" all control flow so that we can + // reach the right part when unwinding. For example, for an if we do this: + // + // if (condition()) { + // side1(); + // } else { + // side2(); + // } + // => + // if (!rewinding) { + // temp = condition(); + // } + // if (rewinding || temp) { + // side1(); + // } + // if (rewinding || !temp) { + // side2(); + // } + // + // This way we will linearly get through all the code in the function, + // if we are rewinding. In a similar way we skip over breaks, etc.; just + // "keep on truckin'". + // + // Note that temp locals added in this way are just normal locals, and in + // particular they are saved and loaded. That way if we resume from the + // first if arm we will avoid the second. + if (auto* block = curr->dynCast<Block>()) { + // At least one of our children may change the state. Clump them as + // necessary. + Index i = 0; + auto& list = block->list; + while (i < list.size()) { + if (analyzer->canChangeState(list[i])) { + list[i] = process(list[i]); + i++; + } else { + Index end = i + 1; + while (end < list.size() && !analyzer->canChangeState(list[end])) { + end++; + } + // We have a range of [i, end) in which the state cannot change, + // so all we need to do is skip it if rewinding. + if (end == i + 1) { + list[i] = makeMaybeSkip(list[i]); + } else { + auto* block = builder->makeBlock(); + for (auto j = i; j < end; j++) { + block->list.push_back(list[j]); + } + block->finalize(); + list[i] = makeMaybeSkip(block); + for (auto j = i + 1; j < end; j++) { + list[j] = builder->makeNop(); + } + } + i = end; + } + } + return block; + } else if (auto* iff = curr->dynCast<If>()) { + // The state change cannot be in the condition due to flat form, so it + // must be in one of the children. + assert(!analyzer->canChangeState(iff->condition)); + // We must linearize this, which means we pass through both arms if we + // are rewinding. This is pretty simple as in flat form the if condition + // is either a const or a get, so easily copyable. + // Start with the first arm, for which we reuse the original if. + auto* otherArm = iff->ifFalse; + iff->ifFalse = nullptr; + auto* originalCondition = iff->condition; + iff->condition = builder->makeBinary( + OrInt32, originalCondition, builder->makeStateCheck(State::Rewinding)); + iff->ifTrue = process(iff->ifTrue); + iff->finalize(); + if (!otherArm) { + return iff; + } + // Add support for the second arm as well. + auto* otherIf = builder->makeIf( + builder->makeBinary( + OrInt32, + builder->makeUnary( + EqZInt32, ExpressionManipulator::copy(originalCondition, *module)), + builder->makeStateCheck(State::Rewinding)), + process(otherArm)); + otherIf->finalize(); + return builder->makeSequence(iff, otherIf); + } else if (auto* loop = curr->dynCast<Loop>()) { + loop->body = process(loop->body); + return loop; + } else if (doesCall(curr)) { + return makeCallSupport(curr); + } + // We must handle all control flow above, and all things that can change + // the state, so there should be nothing that can reach here - add it + // earlier as necessary. + WASM_UNREACHABLE(); + } + + // Possibly skip some code, if rewinding. + Expression* makeMaybeSkip(Expression* curr) { + return builder->makeIf(builder->makeStateCheck(State::Normal), curr); + } + + Expression* makeCallSupport(Expression* curr) { + // TODO: stop doing this after code can no longer reach a call that may + // change the state + assert(doesCall(curr)); + assert(curr->type == none); + auto index = callIndex++; + // Execute the call, if either normal execution, or if rewinding and this + // is the right call to go into. + // TODO: we can read the next call index once in each function (but should + // avoid saving/restoring that local later) + return builder->makeIf( + builder->makeIf(builder->makeStateCheck(State::Normal), + builder->makeConst(Literal(int32_t(1))), + makeCallIndexPeek(index)), + builder->makeSequence(curr, makePossibleUnwind(index))); + } + + Expression* makePossibleUnwind(Index index) { + // In this pass we emit an "unwind" as a call to a fake intrinsic. We + // will implement it in the later pass. (We can't create the unwind block + // target here, as the optimizer would remove it later; we can only add + // it when we add its contents, later.) + return builder->makeIf( + builder->makeStateCheck(State::Unwinding), + builder->makeCall( + BYSYNCIFY_UNWIND, {builder->makeConst(Literal(int32_t(index)))}, none)); + } + + Expression* makeCallIndexPeek(Index index) { + // Emit an intrinsic for this, as we store the index into a local, and + // don't want it to be seen by bysyncify itself. + return builder->makeCall(BYSYNCIFY_CHECK_CALL_INDEX, + {builder->makeConst(Literal(int32_t(index)))}, + i32); + } + + Expression* makeCallIndexPop() { + // Emit an intrinsic for this, as we store the index into a local, and + // don't want it to be seen by bysyncify itself. + return builder->makeCall(BYSYNCIFY_GET_CALL_INDEX, {}, none); + } +}; + +// Instrument local saving/restoring. +struct BysyncifyLocals : public WalkerPass<PostWalker<BysyncifyLocals>> { + bool isFunctionParallel() override { return true; } + + ModuleAnalyzer* analyzer; + + BysyncifyLocals* create() override { return new BysyncifyLocals(analyzer); } + + BysyncifyLocals(ModuleAnalyzer* analyzer) : analyzer(analyzer) {} + + void visitCall(Call* curr) { + // Replace calls to the fake intrinsics. + if (curr->target == BYSYNCIFY_UNWIND) { + replaceCurrent(builder->makeBreak(BYSYNCIFY_UNWIND, curr->operands[0])); + } else if (curr->target == BYSYNCIFY_GET_CALL_INDEX) { + replaceCurrent(builder->makeSequence( + builder->makeIncStackPos(-4), + builder->makeLocalSet( + rewindIndex, + builder->makeLoad(4, false, 0, 4, builder->makeGetStackPos(), i32)))); + } else if (curr->target == BYSYNCIFY_CHECK_CALL_INDEX) { + replaceCurrent(builder->makeBinary( + EqInt32, + builder->makeLocalGet(rewindIndex, i32), + builder->makeConst( + Literal(int32_t(curr->operands[0]->cast<Const>()->value.geti32()))))); + } + } + + void doWalkFunction(Function* func) { + // If the function cannot change our state, we have nothing to do - + // we will never unwind or rewind the stack here. + if (!analyzer->canChangeState(func->body)) { + return; + } + // Note the locals we want to preserve, before we add any more helpers. + numPreservableLocals = func->getNumLocals(); + // The new function body has a prelude to load locals if rewinding, + // then the actual main body, which does all its unwindings by breaking + // to the unwind block, which then handles pushing the call index, as + // well as saving the locals. + // An index is needed for getting the unwinding and rewinding call indexes + // around TODO: can this be the same index? + auto unwindIndex = builder->addVar(func, i32); + rewindIndex = builder->addVar(func, i32); + // Rewrite the function body. + builder = make_unique<BysyncifyBuilder>(*getModule()); + walk(func->body); + // After the normal function body, emit a barrier before the postamble. + Expression* barrier; + if (func->result == none) { + // The function may have ended without a return; ensure one. + barrier = builder->makeReturn(); + } else { + // The function must have returned or hit an unreachable, but emit one + // to make possible bugs easier to figure out (as this should never be + // reached). The optimizer can remove this anyhow. + barrier = builder->makeUnreachable(); + } + auto* newBody = builder->makeBlock( + {builder->makeIf(builder->makeStateCheck(State::Rewinding), + makeLocalLoading()), + builder->makeLocalSet( + unwindIndex, + builder->makeBlock(BYSYNCIFY_UNWIND, + builder->makeSequence(func->body, barrier))), + makeCallIndexPush(unwindIndex), + makeLocalSaving()}); + if (func->result != none) { + // If we unwind, we must still "return" a value, even if it will be + // ignored on the outside. + newBody->list.push_back( + LiteralUtils::makeZero(func->result, *getModule())); + newBody->finalize(func->result); + } + func->body = newBody; + // Making things like returns conditional may alter types. + ReFinalize().walkFunctionInModule(func, getModule()); + } + +private: + std::unique_ptr<BysyncifyBuilder> builder; + + Index rewindIndex; + Index numPreservableLocals; + + Expression* makeLocalLoading() { + if (numPreservableLocals == 0) { + return builder->makeNop(); + } + auto* func = getFunction(); + Index total = 0; + for (Index i = 0; i < numPreservableLocals; i++) { + auto type = func->getLocalType(i); + auto size = getTypeSize(type); + total += size; + } + auto* block = builder->makeBlock(); + block->list.push_back(builder->makeIncStackPos(-total)); + auto tempIndex = builder->addVar(func, i32); + block->list.push_back( + builder->makeLocalSet(tempIndex, builder->makeGetStackPos())); + Index offset = 0; + for (Index i = 0; i < numPreservableLocals; i++) { + auto type = func->getLocalType(i); + auto size = getTypeSize(type); + assert(size % STACK_ALIGN == 0); + // TODO: higher alignment? + block->list.push_back(builder->makeLocalSet( + i, + builder->makeLoad(size, + true, + offset, + STACK_ALIGN, + builder->makeLocalGet(tempIndex, i32), + type))); + offset += size; + } + block->finalize(); + return block; + } + + Expression* makeLocalSaving() { + if (numPreservableLocals == 0) { + return builder->makeNop(); + } + auto* func = getFunction(); + auto* block = builder->makeBlock(); + auto tempIndex = builder->addVar(func, i32); + block->list.push_back( + builder->makeLocalSet(tempIndex, builder->makeGetStackPos())); + Index offset = 0; + for (Index i = 0; i < numPreservableLocals; i++) { + auto type = func->getLocalType(i); + auto size = getTypeSize(type); + assert(size % STACK_ALIGN == 0); + // TODO: higher alignment? + block->list.push_back( + builder->makeStore(size, + offset, + STACK_ALIGN, + builder->makeLocalGet(tempIndex, i32), + builder->makeLocalGet(i, type), + type)); + offset += size; + } + block->list.push_back(builder->makeIncStackPos(offset)); + block->finalize(); + return block; + } + + Expression* makeCallIndexPush(Index tempIndex) { + // TODO: add a check against the stack end here + return builder->makeSequence( + builder->makeStore(4, + 0, + 4, + builder->makeGetStackPos(), + builder->makeLocalGet(tempIndex, i32), + i32), + builder->makeIncStackPos(4)); + } +}; + +} // anonymous namespace + +struct Bysyncify : public Pass { + void run(PassRunner* runner, Module* module) override { + bool optimize = runner->options.optimizeLevel > 0; + // Find which imports can change the state. + const char* ALL_IMPORTS_CAN_CHANGE_STATE = "__bysyncify_all_imports"; + auto stateChangingImports = runner->options.getArgumentOrDefault( + "bysyncify", ALL_IMPORTS_CAN_CHANGE_STATE); + bool allImportsCanChangeState = + stateChangingImports == ALL_IMPORTS_CAN_CHANGE_STATE; + std::string separator = ","; + stateChangingImports = separator + stateChangingImports + separator; + + // Scan the module. + ModuleAnalyzer analyzer(*module, [&](Name module, Name base) { + if (allImportsCanChangeState) { + return true; + } + std::string full = separator + module.str + '.' + base.str + separator; + return stateChangingImports.find(full) != std::string::npos; + }); + + // Add necessary globals before we emit code to use them. + addGlobals(module); + + // Instrument the flow of code, adding code instrumentation and + // skips for when rewinding. We do this on flat IR so that it is + // practical to add code around each call, without affecting + // anything else. + { + PassRunner runner(module); + runner.add("flatten"); + // Dce is useful here, since BysyncifyFlow makes control flow conditional, + // which may make unreachable code look reachable. We also do some other + // minimal optimization here in an unconditional way here, to counteract + // the flattening. + runner.add("dce"); + runner.add("simplify-locals-nonesting"); + runner.add("merge-blocks"); + runner.add("vacuum"); + runner.add<BysyncifyFlow>(&analyzer); + runner.setIsNested(true); + runner.setValidateGlobally(false); + runner.run(); + } + // Next, add local saving/restoring logic. We optimize before doing this, + // to undo the extra code generated by flattening, and to arrive at the + // minimal amount of locals (which is important as we must save and + // restore those locals). We also and optimize after as well to simplify + // the code as much as possible. + { + PassRunner runner(module); + if (optimize) { + runner.addDefaultFunctionOptimizationPasses(); + } + runner.add<BysyncifyLocals>(&analyzer); + if (optimize) { + runner.addDefaultFunctionOptimizationPasses(); + } + runner.setIsNested(true); + runner.setValidateGlobally(false); + runner.run(); + } + // Finally, add function support (that should not have been seen by + // the previous passes). + addFunctions(module); + } + +private: + void addGlobals(Module* module) { + Builder builder(*module); + module->addGlobal(builder.makeGlobal(BYSYNCIFY_STATE, + i32, + builder.makeConst(Literal(int32_t(0))), + Builder::Mutable)); + module->addGlobal(builder.makeGlobal(BYSYNCIFY_DATA, + i32, + builder.makeConst(Literal(int32_t(0))), + Builder::Mutable)); + } + + void addFunctions(Module* module) { + Builder builder(*module); + auto makeFunction = [&](Name name, + bool setData, + State state, + Expression* extra) { + std::vector<Type> params; + if (setData) { + params.push_back(i32); + } + auto* body = builder.makeBlock(); + if (setData) { + // Verify the data is valid. + auto* stackPos = builder.makeLoad(4, + false, + int32_t(DataOffset::BStackPos), + 4, + builder.makeLocalGet(0, i32), + i32); + auto* stackEnd = builder.makeLoad(4, + false, + int32_t(DataOffset::BStackEnd), + 4, + builder.makeLocalGet(0, i32), + i32); + body->list.push_back( + builder.makeIf(builder.makeBinary(GtUInt32, stackPos, stackEnd), + builder.makeUnreachable())); + } + body->list.push_back(builder.makeGlobalSet( + BYSYNCIFY_STATE, builder.makeConst(Literal(int32_t(state))))); + if (extra) { + body->list.push_back(extra); + } + body->finalize(); + auto* func = builder.makeFunction( + name, std::move(params), none, std::vector<Type>{}, body); + module->addFunction(func); + module->addExport(builder.makeExport(name, name, ExternalKind::Function)); + }; + + makeFunction( + BYSYNCIFY_START_UNWIND, + true, + State::Unwinding, + builder.makeGlobalSet(BYSYNCIFY_DATA, builder.makeLocalGet(0, i32))); + makeFunction( + BYSYNCIFY_START_REWIND, + true, + State::Rewinding, + builder.makeGlobalSet(BYSYNCIFY_DATA, builder.makeLocalGet(0, i32))); + makeFunction(BYSYNCIFY_STOP_REWIND, false, State::Normal, nullptr); + } +}; + +Pass* createBysyncifyPass() { return new Bysyncify(); } + +} // namespace wasm diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 9120328d3..6a993c125 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -7,6 +7,7 @@ SET(passes_SOURCES pass.cpp AlignmentLowering.cpp AvoidReinterprets.cpp + Bysyncify.cpp CoalesceLocals.cpp CodePushing.cpp CodeFolding.cpp diff --git a/src/passes/Inlining.cpp b/src/passes/Inlining.cpp index 2aa800d62..1e5089b97 100644 --- a/src/passes/Inlining.cpp +++ b/src/passes/Inlining.cpp @@ -160,8 +160,6 @@ struct Planner : public WalkerPass<PostWalker<Planner>> { } } - void doWalkFunction(Function* func) { walk(func->body); } - private: InliningState* state; }; diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index c29bb1e1a..b46a03b5e 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -77,6 +77,9 @@ void PassRegistry::registerPasses() { registerPass("avoid-reinterprets", "Tries to avoid reinterpret operations via more loads", createAvoidReinterpretsPass); + registerPass("bysyncify", + "async/await style transform, allowing pausing and resuming", + createBysyncifyPass); registerPass( "dae", "removes arguments to calls in an lto-like manner", createDAEPass); registerPass("dae-optimizing", diff --git a/src/passes/passes.h b/src/passes/passes.h index f7c8fae77..84aabf0e8 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -24,6 +24,7 @@ class Pass; // All passes: Pass* createAlignmentLoweringPass(); Pass* createAvoidReinterpretsPass(); +Pass* createBysyncifyPass(); Pass* createCoalesceLocalsPass(); Pass* createCoalesceLocalsWithLearningPass(); Pass* createCodeFoldingPass(); diff --git a/src/wasm2js.h b/src/wasm2js.h index ccd54b35f..a14aad2a4 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -311,7 +311,7 @@ Ref Wasm2JSBuilder::processWasm(Module* wasm, Name funcName) { runner.add("avoid-reinterprets"); } // Finally, get the code into the flat form we need for wasm2js itself, and - // optimize that a little in a way that keeps flat property. + // optimize that a little in a way that keeps that property. runner.add("flatten"); // Regardless of optimization level, run some simple optimizations to undo // some of the effects of flattening. diff --git a/test/passes/bysyncify.txt b/test/passes/bysyncify.txt new file mode 100644 index 000000000..1ec35057e --- /dev/null +++ b/test/passes/bysyncify.txt @@ -0,0 +1,2710 @@ +(module + (type $FUNCSIG$vi (func (param i32))) + (type $FUNCSIG$v (func)) + (memory $0 1 2) + (global $sleeping (mut i32) (i32.const 0)) + (global $__bysyncify_state (mut i32) (i32.const 0)) + (global $__bysyncify_data (mut i32) (i32.const 0)) + (export "bysyncify_start_unwind" (func $bysyncify_start_unwind)) + (export "bysyncify_start_rewind" (func $bysyncify_start_rewind)) + (export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind)) + (func $do_sleep (; 0 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -8) + ) + ) + (local.set $4 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $0 + (i32.load + (local.get $4) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $4) + ) + ) + ) + ) + (local.set $2 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $3 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (block + (local.set $0 + (global.get $sleeping) + ) + (local.set $1 + (i32.eqz + (local.get $0) + ) + ) + ) + ) + (nop) + (block + (if + (i32.or + (local.get $1) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (global.set $sleeping + (i32.const 1) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $3) + (i32.const 0) + ) + ) + (block + (call $bysyncify_start_unwind + (i32.const 4) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + ) + (if + (i32.or + (i32.eqz + (local.get $1) + ) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (block + (global.set $sleeping + (i32.const 0) + ) + (call $bysyncify_stop_rewind) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $2) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $5 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $5) + (local.get $0) + ) + (i32.store offset=4 + (local.get $5) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 8) + ) + ) + ) + ) + (func $work (; 1 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $stuff) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $do_sleep) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $stuff) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $stuff (; 2 ;) (type $FUNCSIG$v) + (nop) + ) + (func $first_event (; 3 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $work) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $second_event (; 4 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $bysyncify_start_rewind + (i32.const 4) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 1) + ) + ) + (block + (call $work) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 1) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $never_sleep (; 5 ;) (type $FUNCSIG$v) + (call $stuff) + (call $stuff) + (call $stuff) + ) + (func $bysyncify_start_unwind (; 6 ;) (param $0 i32) + (if + (i32.gt_u + (i32.load + (local.get $0) + ) + (i32.load offset=4 + (local.get $0) + ) + ) + (unreachable) + ) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) + ) + (func $bysyncify_start_rewind (; 7 ;) (param $0 i32) + (if + (i32.gt_u + (i32.load + (local.get $0) + ) + (i32.load offset=4 + (local.get $0) + ) + ) + (unreachable) + ) + (global.set $__bysyncify_state + (i32.const 2) + ) + (global.set $__bysyncify_data + (local.get $0) + ) + ) + (func $bysyncify_stop_rewind (; 8 ;) + (global.set $__bysyncify_state + (i32.const 0) + ) + ) +) +(module + (type $FUNCSIG$v (func)) + (type $FUNCSIG$i (func (result i32))) + (type $FUNCSIG$vi (func (param i32))) + (type $FUNCSIG$ii (func (param i32) (result i32))) + (import "env" "import" (func $import)) + (import "env" "import2" (func $import2 (result i32))) + (import "env" "import3" (func $import3 (param i32))) + (memory $0 1 2) + (global $__bysyncify_state (mut i32) (i32.const 0)) + (global $__bysyncify_data (mut i32) (i32.const 0)) + (export "bysyncify_start_unwind" (func $bysyncify_start_unwind)) + (export "bysyncify_start_rewind" (func $bysyncify_start_rewind)) + (export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind)) + (func $calls-import (; 3 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $calls-import2 (; 4 ;) (type $FUNCSIG$i) (result i32) + (local $temp i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (local $7 i32) + (local $8 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -20) + ) + ) + (local.set $7 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $temp + (i32.load + (local.get $7) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $7) + ) + ) + (local.set $2 + (i32.load offset=8 + (local.get $7) + ) + ) + (local.set $3 + (i32.load offset=12 + (local.get $7) + ) + ) + (local.set $4 + (i32.load offset=16 + (local.get $7) + ) + ) + ) + ) + (local.set $5 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $6 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $6) + (i32.const 0) + ) + ) + (block + (local.set $2 + (call $import2) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (return + (local.get $2) + ) + ) + ) + (unreachable) + ) + (unreachable) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $5) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $8 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $8) + (local.get $temp) + ) + (i32.store offset=4 + (local.get $8) + (local.get $1) + ) + (i32.store offset=8 + (local.get $8) + (local.get $2) + ) + (i32.store offset=12 + (local.get $8) + (local.get $3) + ) + (i32.store offset=16 + (local.get $8) + (local.get $4) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 20) + ) + ) + ) + (i32.const 0) + ) + (func $calls-import2-drop (; 5 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $3 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $0 + (i32.load + (local.get $3) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $2 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $2) + (i32.const 0) + ) + ) + (block + (local.set $0 + (call $import2) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $4 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $4) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + ) + (func $calls-nothing (; 6 ;) (type $FUNCSIG$v) + (local $0 i32) + (local.set $0 + (i32.eqz + (i32.const 17) + ) + ) + ) + (func $many-locals (; 7 ;) (type $FUNCSIG$ii) (param $x i32) (result i32) + (local $y i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (local $7 i32) + (local $8 i32) + (local $9 i32) + (local $10 i32) + (local $11 i32) + (local $12 i32) + (local $13 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -40) + ) + ) + (local.set $12 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $x + (i32.load + (local.get $12) + ) + ) + (local.set $y + (i32.load offset=4 + (local.get $12) + ) + ) + (local.set $2 + (i32.load offset=8 + (local.get $12) + ) + ) + (local.set $3 + (i32.load offset=12 + (local.get $12) + ) + ) + (local.set $4 + (i32.load offset=16 + (local.get $12) + ) + ) + (local.set $5 + (i32.load offset=20 + (local.get $12) + ) + ) + (local.set $6 + (i32.load offset=24 + (local.get $12) + ) + ) + (local.set $7 + (i32.load offset=28 + (local.get $12) + ) + ) + (local.set $8 + (i32.load offset=32 + (local.get $12) + ) + ) + (local.set $9 + (i32.load offset=36 + (local.get $12) + ) + ) + ) + ) + (local.set $10 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $11 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (loop $l + (local.set $4 + (i32.add + (local.get $y) + (i32.const 1) + ) + ) + (local.set $y + (i32.div_s + (local.get $4) + (i32.const 3) + ) + ) + (br_if $l + (local.get $y) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $11) + (i32.const 0) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (return + (local.get $y) + ) + ) + ) + (unreachable) + ) + (unreachable) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $10) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $13 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $13) + (local.get $x) + ) + (i32.store offset=4 + (local.get $13) + (local.get $y) + ) + (i32.store offset=8 + (local.get $13) + (local.get $2) + ) + (i32.store offset=12 + (local.get $13) + (local.get $3) + ) + (i32.store offset=16 + (local.get $13) + (local.get $4) + ) + (i32.store offset=20 + (local.get $13) + (local.get $5) + ) + (i32.store offset=24 + (local.get $13) + (local.get $6) + ) + (i32.store offset=28 + (local.get $13) + (local.get $7) + ) + (i32.store offset=32 + (local.get $13) + (local.get $8) + ) + (i32.store offset=36 + (local.get $13) + (local.get $9) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 40) + ) + ) + ) + (i32.const 0) + ) + (func $calls-import2-if (; 8 ;) (type $FUNCSIG$vi) (param $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -8) + ) + ) + (local.set $4 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $x + (i32.load + (local.get $4) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $4) + ) + ) + ) + ) + (local.set $2 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $3 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (i32.or + (local.get $x) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $3) + (i32.const 0) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $2) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $5 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $5) + (local.get $x) + ) + (i32.store offset=4 + (local.get $5) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 8) + ) + ) + ) + ) + (func $calls-import2-if-else (; 9 ;) (type $FUNCSIG$vi) (param $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -8) + ) + ) + (local.set $4 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $x + (i32.load + (local.get $4) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $4) + ) + ) + ) + ) + (local.set $2 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $3 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.or + (local.get $x) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $3) + (i32.const 0) + ) + ) + (block + (call $import3 + (i32.const 1) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + (if + (i32.or + (i32.eqz + (local.get $x) + ) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $3) + (i32.const 1) + ) + ) + (block + (call $import3 + (i32.const 2) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 1) + ) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $2) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $5 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $5) + (local.get $x) + ) + (i32.store offset=4 + (local.get $5) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 8) + ) + ) + ) + ) + (func $calls-import2-if-else-oneside (; 10 ;) (type $FUNCSIG$ii) (param $x i32) (result i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (local $7 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -16) + ) + ) + (local.set $6 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $x + (i32.load + (local.get $6) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $6) + ) + ) + (local.set $2 + (i32.load offset=8 + (local.get $6) + ) + ) + (local.set $3 + (i32.load offset=12 + (local.get $6) + ) + ) + ) + ) + (local.set $4 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $5 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (block + (if + (i32.or + (local.get $x) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (return + (i32.const 1) + ) + ) + ) + (if + (i32.or + (i32.eqz + (local.get $x) + ) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $5) + (i32.const 0) + ) + ) + (block + (call $import3 + (i32.const 2) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (return + (i32.const 3) + ) + ) + ) + (unreachable) + ) + (unreachable) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $4) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $7 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $7) + (local.get $x) + ) + (i32.store offset=4 + (local.get $7) + (local.get $1) + ) + (i32.store offset=8 + (local.get $7) + (local.get $2) + ) + (i32.store offset=12 + (local.get $7) + (local.get $3) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 16) + ) + ) + ) + (i32.const 0) + ) + (func $calls-import2-if-else-oneside2 (; 11 ;) (type $FUNCSIG$ii) (param $x i32) (result i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (local $7 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -16) + ) + ) + (local.set $6 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $x + (i32.load + (local.get $6) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $6) + ) + ) + (local.set $2 + (i32.load offset=8 + (local.get $6) + ) + ) + (local.set $3 + (i32.load offset=12 + (local.get $6) + ) + ) + ) + ) + (local.set $4 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $5 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (block + (if + (i32.or + (local.get $x) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $5) + (i32.const 0) + ) + ) + (block + (call $import3 + (i32.const 1) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + (if + (i32.or + (i32.eqz + (local.get $x) + ) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (return + (i32.const 2) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (return + (i32.const 3) + ) + ) + ) + (unreachable) + ) + (unreachable) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $4) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $7 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $7) + (local.get $x) + ) + (i32.store offset=4 + (local.get $7) + (local.get $1) + ) + (i32.store offset=8 + (local.get $7) + (local.get $2) + ) + (i32.store offset=12 + (local.get $7) + (local.get $3) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 16) + ) + ) + ) + (i32.const 0) + ) + (func $calls-loop (; 12 ;) (type $FUNCSIG$vi) (param $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (local $7 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -16) + ) + ) + (local.set $6 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $x + (i32.load + (local.get $6) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $6) + ) + ) + (local.set $2 + (i32.load offset=8 + (local.get $6) + ) + ) + (local.set $3 + (i32.load offset=12 + (local.get $6) + ) + ) + ) + ) + (local.set $4 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $5 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (loop $l + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $5) + (i32.const 0) + ) + ) + (block + (call $import3 + (i32.const 1) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (block + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (br_if $l + (local.get $x) + ) + ) + ) + (nop) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $4) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $7 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $7) + (local.get $x) + ) + (i32.store offset=4 + (local.get $7) + (local.get $1) + ) + (i32.store offset=8 + (local.get $7) + (local.get $2) + ) + (i32.store offset=12 + (local.get $7) + (local.get $3) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 16) + ) + ) + ) + ) + (func $calls-loop2 (; 13 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $3 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $0 + (i32.load + (local.get $3) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $2 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (loop $l + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $2) + (i32.const 0) + ) + ) + (block + (local.set $0 + (call $import2) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (br_if $l + (local.get $0) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $4 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $4) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + ) + (func $calls-mix (; 14 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $boring) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $boring) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 1) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 1) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $boring (; 15 ;) (type $FUNCSIG$v) + (nop) + ) + (func $calls-mix-deep (; 16 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $boring-deep) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $import-deep) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $boring) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 1) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 1) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $boring-deep (; 17 ;) (type $FUNCSIG$v) + (call $boring) + ) + (func $import-deep (; 18 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $bysyncify_start_unwind (; 19 ;) (param $0 i32) + (if + (i32.gt_u + (i32.load + (local.get $0) + ) + (i32.load offset=4 + (local.get $0) + ) + ) + (unreachable) + ) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) + ) + (func $bysyncify_start_rewind (; 20 ;) (param $0 i32) + (if + (i32.gt_u + (i32.load + (local.get $0) + ) + (i32.load offset=4 + (local.get $0) + ) + ) + (unreachable) + ) + (global.set $__bysyncify_state + (i32.const 2) + ) + (global.set $__bysyncify_data + (local.get $0) + ) + ) + (func $bysyncify_stop_rewind (; 21 ;) + (global.set $__bysyncify_state + (i32.const 0) + ) + ) +) diff --git a/test/passes/bysyncify.wast b/test/passes/bysyncify.wast new file mode 100644 index 000000000..7c92bd86f --- /dev/null +++ b/test/passes/bysyncify.wast @@ -0,0 +1,146 @@ +;; Pre-existing imports that the pass turns into the implementations. +(module + (memory 1 2) + (import "bysyncify" "start_unwind" (func $bysyncify_start_unwind (param i32))) + (import "bysyncify" "start_rewind" (func $bysyncify_start_rewind (param i32))) + (import "bysyncify" "stop_rewind" (func $bysyncify_stop_rewind)) + (global $sleeping (mut i32) (i32.const 0)) + ;; do a sleep operation: start a sleep if running, or resume after a sleep + ;; if we just rewound. + (func $do_sleep + (if + (i32.eqz (global.get $sleeping)) + (block + (global.set $sleeping (i32.const 1)) + ;; we should set up the data at address 4 around here + (call $bysyncify_start_unwind (i32.const 4)) + ) + (block + (global.set $sleeping (i32.const 0)) + (call $bysyncify_stop_rewind) + ) + ) + ) + ;; a function that does some work and has a sleep (async pause/resume) in the middle + (func $work + (call $stuff) ;; do some work + (call $do_sleep) ;; take a break + (call $stuff) ;; do some more work + ) + (func $stuff) + ;; the first event called from the main event loop: just call into $work + (func $first_event + (call $work) + ;; work will sleep, so we exit through here while it is paused + ) + ;; the second event called from the main event loop: to resume $work, + ;; initiate a rewind, and then do the call to start things back up + (func $second_event + (call $bysyncify_start_rewind (i32.const 4)) + (call $work) + ) + ;; a function that can't do a sleep + (func $never_sleep + (call $stuff) + (call $stuff) + (call $stuff) + ) +) +;; Calls to imports that will call into bysyncify themselves. +(module + (memory 1 2) + (import "env" "import" (func $import)) + (import "env" "import2" (func $import2 (result i32))) + (import "env" "import3" (func $import3 (param i32))) + (func $calls-import + (call $import) + ) + (func $calls-import2 (result i32) + (local $temp i32) + (local.set $temp (call $import2)) + (return (local.get $temp)) + ) + (func $calls-import2-drop + (drop (call $import2)) + ) + (func $calls-nothing + (drop (i32.eqz (i32.const 17))) + ) + (func $many-locals (param $x i32) (result i32) + (local $y i32) + (loop $l + (local.set $x + (i32.add (local.get $y) (i32.const 1)) + ) + (local.set $y + (i32.div_s (local.get $x) (i32.const 3)) + ) + (br_if $l (local.get $y)) + ) + (call $import) + (return (local.get $y)) + ) + (func $calls-import2-if (param $x i32) + (if (local.get $x) + (call $import) + ) + ) + (func $calls-import2-if-else (param $x i32) + (if (local.get $x) + (call $import3 (i32.const 1)) + (call $import3 (i32.const 2)) + ) + ) + (func $calls-import2-if-else-oneside (param $x i32) (result i32) + (if (local.get $x) + (return (i32.const 1)) + (call $import3 (i32.const 2)) + ) + (return (i32.const 3)) + ) + (func $calls-import2-if-else-oneside2 (param $x i32) (result i32) + (if (local.get $x) + (call $import3 (i32.const 1)) + (return (i32.const 2)) + ) + (return (i32.const 3)) + ) + (func $calls-loop (param $x i32) + (loop $l + (call $import3 (i32.const 1)) + (local.set $x + (i32.add (local.get $x) (i32.const 1)) + ) + (br_if $l + (local.get $x) + ) + ) + ) + (func $calls-loop2 + (loop $l + (br_if $l + (call $import2) + ) + ) + ) + (func $calls-mix + (call $boring) + (call $import) + (call $boring) + (call $import) + ) + (func $boring) + (func $calls-mix-deep + (call $boring-deep) + (call $import-deep) + (call $boring) + (call $import) + ) + (func $boring-deep + (call $boring) + ) + (func $import-deep + (call $import) + ) +) + diff --git a/test/passes/bysyncify_optimize-level=1.txt b/test/passes/bysyncify_optimize-level=1.txt new file mode 100644 index 000000000..6eb7217e4 --- /dev/null +++ b/test/passes/bysyncify_optimize-level=1.txt @@ -0,0 +1,1541 @@ +(module + (type $FUNCSIG$v (func)) + (type $FUNCSIG$i (func (result i32))) + (type $FUNCSIG$vi (func (param i32))) + (type $FUNCSIG$ii (func (param i32) (result i32))) + (import "env" "import" (func $import)) + (import "env" "import2" (func $import2 (result i32))) + (import "env" "import3" (func $import3 (param i32))) + (memory $0 1 2) + (global $__bysyncify_state (mut i32) (i32.const 0)) + (global $__bysyncify_data (mut i32) (i32.const 0)) + (export "bysyncify_start_unwind" (func $bysyncify_start_unwind)) + (export "bysyncify_start_rewind" (func $bysyncify_start_rewind)) + (export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind)) + (func $calls-import (; 3 ;) (type $FUNCSIG$v) + (local $0 i32) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (if + (select + (i32.eqz + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block (result i32) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + (local.get $0) + ) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (return) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (func $calls-import2 (; 4 ;) (type $FUNCSIG$i) (result i32) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $0 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (if + (select + (i32.eqz + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block (result i32) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + (local.get $1) + ) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (local.set $0 + (call $import2) + ) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (return + (local.get $0) + ) + ) + (unreachable) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.const 0) + ) + (func $calls-import2-drop (; 5 ;) (type $FUNCSIG$v) + (local $0 i32) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (if + (select + (i32.eqz + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block (result i32) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + (local.get $0) + ) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (drop + (call $import2) + ) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (return) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (func $calls-nothing (; 6 ;) (type $FUNCSIG$v) + (nop) + ) + (func $many-locals (; 7 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (local $1 i32) + (local $2 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -8) + ) + ) + (local.set $0 + (i32.load + (local.tee $1 + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $1) + ) + ) + ) + ) + (local.set $2 + (block $__bysyncify_unwind (result i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $2 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (loop $l + (br_if $l + (local.tee $1 + (i32.div_s + (i32.add + (local.get $1) + (i32.const 1) + ) + (i32.const 3) + ) + ) + ) + ) + ) + (if + (select + (i32.eqz + (local.get $2) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (return + (local.get $1) + ) + ) + (unreachable) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $2) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.store + (local.tee $2 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.get $0) + ) + (i32.store offset=4 + (local.get $2) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 8) + ) + ) + (i32.const 0) + ) + (func $calls-import2-if (; 8 ;) (type $FUNCSIG$vi) (param $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $0 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (i32.or + (local.get $0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (select + (i32.eqz + (local.get $1) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (func $calls-import2-if-else (; 9 ;) (type $FUNCSIG$vi) (param $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $0 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (i32.or + (local.get $0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (select + (i32.eqz + (local.get $1) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import3 + (i32.const 1) + ) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + ) + (if + (i32.or + (i32.eqz + (local.get $0) + ) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (select + (i32.eq + (local.get $1) + (i32.const 1) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import3 + (i32.const 2) + ) + (drop + (br_if $__bysyncify_unwind + (i32.const 1) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (func $calls-import2-if-else-oneside (; 10 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $0 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (i32.or + (local.get $0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (return + (i32.const 1) + ) + ) + ) + (if + (i32.or + (i32.eqz + (local.get $0) + ) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (select + (i32.eqz + (local.get $1) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import3 + (i32.const 2) + ) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (return + (i32.const 3) + ) + ) + (unreachable) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.const 0) + ) + (func $calls-import2-if-else-oneside2 (; 11 ;) (type $FUNCSIG$ii) (param $0 i32) (result i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $0 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (i32.or + (local.get $0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (select + (i32.eqz + (local.get $1) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import3 + (i32.const 1) + ) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + ) + (if + (i32.or + (i32.eqz + (local.get $0) + ) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (return + (i32.const 2) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (return + (i32.const 3) + ) + ) + (unreachable) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.const 0) + ) + (func $calls-loop (; 12 ;) (type $FUNCSIG$vi) (param $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $0 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (loop $l + (if + (select + (i32.eqz + (local.get $1) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import3 + (i32.const 1) + ) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (br_if $l + (local.tee $0 + (i32.add + (local.get $0) + (i32.const 1) + ) + ) + ) + ) + ) + (return) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (func $calls-loop2 (; 13 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $0 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (loop $l + (if + (select + (i32.eqz + (local.get $1) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (local.set $0 + (call $import2) + ) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (br_if $l + (local.get $0) + ) + ) + ) + (return) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (func $calls-mix (; 14 ;) (type $FUNCSIG$v) + (local $0 i32) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $0 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (call $boring) + ) + (if + (select + (i32.eqz + (local.get $0) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (call $boring) + ) + (if + (select + (i32.eq + (local.get $0) + (i32.const 1) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import) + (drop + (br_if $__bysyncify_unwind + (i32.const 1) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (return) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (func $boring (; 15 ;) (type $FUNCSIG$v) + (nop) + ) + (func $calls-mix-deep (; 16 ;) (type $FUNCSIG$v) + (local $0 i32) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $0 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (call $boring-deep) + ) + (if + (select + (i32.eqz + (local.get $0) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import-deep) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (if + (i32.eqz + (global.get $__bysyncify_state) + ) + (call $boring) + ) + (if + (select + (i32.eq + (local.get $0) + (i32.const 1) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import) + (drop + (br_if $__bysyncify_unwind + (i32.const 1) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (return) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (func $boring-deep (; 17 ;) (type $FUNCSIG$v) + (call $boring) + ) + (func $import-deep (; 18 ;) (type $FUNCSIG$v) + (local $0 i32) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (if + (select + (i32.eqz + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block (result i32) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + (local.get $0) + ) + ) + (i32.const 1) + (global.get $__bysyncify_state) + ) + (block + (call $import) + (drop + (br_if $__bysyncify_unwind + (i32.const 0) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + ) + ) + ) + ) + (return) + ) + ) + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (func $bysyncify_start_unwind (; 19 ;) (param $0 i32) + (if + (i32.gt_u + (i32.load + (local.get $0) + ) + (i32.load offset=4 + (local.get $0) + ) + ) + (unreachable) + ) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) + ) + (func $bysyncify_start_rewind (; 20 ;) (param $0 i32) + (if + (i32.gt_u + (i32.load + (local.get $0) + ) + (i32.load offset=4 + (local.get $0) + ) + ) + (unreachable) + ) + (global.set $__bysyncify_state + (i32.const 2) + ) + (global.set $__bysyncify_data + (local.get $0) + ) + ) + (func $bysyncify_stop_rewind (; 21 ;) + (global.set $__bysyncify_state + (i32.const 0) + ) + ) +) diff --git a/test/passes/bysyncify_optimize-level=1.wast b/test/passes/bysyncify_optimize-level=1.wast new file mode 100644 index 000000000..a89103e8a --- /dev/null +++ b/test/passes/bysyncify_optimize-level=1.wast @@ -0,0 +1,97 @@ +(module + (memory 1 2) + (import "env" "import" (func $import)) + (import "env" "import2" (func $import2 (result i32))) + (import "env" "import3" (func $import3 (param i32))) + (func $calls-import + (call $import) + ) + (func $calls-import2 (result i32) + (local $temp i32) + (local.set $temp (call $import2)) + (return (local.get $temp)) + ) + (func $calls-import2-drop + (drop (call $import2)) + ) + (func $calls-nothing + (drop (i32.eqz (i32.const 17))) + ) + (func $many-locals (param $x i32) (result i32) + (local $y i32) + (loop $l + (local.set $x + (i32.add (local.get $y) (i32.const 1)) + ) + (local.set $y + (i32.div_s (local.get $x) (i32.const 3)) + ) + (br_if $l (local.get $y)) + ) + (call $import) + (return (local.get $y)) + ) + (func $calls-import2-if (param $x i32) + (if (local.get $x) + (call $import) + ) + ) + (func $calls-import2-if-else (param $x i32) + (if (local.get $x) + (call $import3 (i32.const 1)) + (call $import3 (i32.const 2)) + ) + ) + (func $calls-import2-if-else-oneside (param $x i32) (result i32) + (if (local.get $x) + (return (i32.const 1)) + (call $import3 (i32.const 2)) + ) + (return (i32.const 3)) + ) + (func $calls-import2-if-else-oneside2 (param $x i32) (result i32) + (if (local.get $x) + (call $import3 (i32.const 1)) + (return (i32.const 2)) + ) + (return (i32.const 3)) + ) + (func $calls-loop (param $x i32) + (loop $l + (call $import3 (i32.const 1)) + (local.set $x + (i32.add (local.get $x) (i32.const 1)) + ) + (br_if $l + (local.get $x) + ) + ) + ) + (func $calls-loop2 + (loop $l + (br_if $l + (call $import2) + ) + ) + ) + (func $calls-mix + (call $boring) + (call $import) + (call $boring) + (call $import) + ) + (func $boring) + (func $calls-mix-deep + (call $boring-deep) + (call $import-deep) + (call $boring) + (call $import) + ) + (func $boring-deep + (call $boring) + ) + (func $import-deep + (call $import) + ) +) + diff --git a/test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.txt b/test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.txt new file mode 100644 index 000000000..e850da6cf --- /dev/null +++ b/test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.txt @@ -0,0 +1,1998 @@ +(module + (type $FUNCSIG$vi (func (param i32))) + (type $FUNCSIG$v (func)) + (memory $0 1 2) + (global $sleeping (mut i32) (i32.const 0)) + (global $__bysyncify_state (mut i32) (i32.const 0)) + (global $__bysyncify_data (mut i32) (i32.const 0)) + (export "bysyncify_start_unwind" (func $bysyncify_start_unwind)) + (export "bysyncify_start_rewind" (func $bysyncify_start_rewind)) + (export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind)) + (func $do_sleep (; 0 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -8) + ) + ) + (local.set $4 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $0 + (i32.load + (local.get $4) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $4) + ) + ) + ) + ) + (local.set $2 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $3 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (block + (local.set $0 + (global.get $sleeping) + ) + (local.set $1 + (i32.eqz + (local.get $0) + ) + ) + ) + ) + (nop) + (block + (if + (i32.or + (local.get $1) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (global.set $sleeping + (i32.const 1) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $3) + (i32.const 0) + ) + ) + (block + (call $bysyncify_start_unwind + (i32.const 4) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + ) + (if + (i32.or + (i32.eqz + (local.get $1) + ) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (block + (global.set $sleeping + (i32.const 0) + ) + (call $bysyncify_stop_rewind) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $2) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $5 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $5) + (local.get $0) + ) + (i32.store offset=4 + (local.get $5) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 8) + ) + ) + ) + ) + (func $work (; 1 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $stuff) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $do_sleep) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $stuff) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $stuff (; 2 ;) (type $FUNCSIG$v) + (nop) + ) + (func $first_event (; 3 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $work) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $second_event (; 4 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $bysyncify_start_rewind + (i32.const 4) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 1) + ) + ) + (block + (call $work) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 1) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $never_sleep (; 5 ;) (type $FUNCSIG$v) + (call $stuff) + (call $stuff) + (call $stuff) + ) + (func $bysyncify_start_unwind (; 6 ;) (param $0 i32) + (if + (i32.gt_u + (i32.load + (local.get $0) + ) + (i32.load offset=4 + (local.get $0) + ) + ) + (unreachable) + ) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) + ) + (func $bysyncify_start_rewind (; 7 ;) (param $0 i32) + (if + (i32.gt_u + (i32.load + (local.get $0) + ) + (i32.load offset=4 + (local.get $0) + ) + ) + (unreachable) + ) + (global.set $__bysyncify_state + (i32.const 2) + ) + (global.set $__bysyncify_data + (local.get $0) + ) + ) + (func $bysyncify_stop_rewind (; 8 ;) + (global.set $__bysyncify_state + (i32.const 0) + ) + ) +) +(module + (type $FUNCSIG$v (func)) + (type $FUNCSIG$i (func (result i32))) + (type $FUNCSIG$vi (func (param i32))) + (type $FUNCSIG$ii (func (param i32) (result i32))) + (import "env" "import" (func $import)) + (import "env" "import2" (func $import2 (result i32))) + (import "env" "import3" (func $import3 (param i32))) + (memory $0 1 2) + (global $__bysyncify_state (mut i32) (i32.const 0)) + (global $__bysyncify_data (mut i32) (i32.const 0)) + (export "bysyncify_start_unwind" (func $bysyncify_start_unwind)) + (export "bysyncify_start_rewind" (func $bysyncify_start_rewind)) + (export "bysyncify_stop_rewind" (func $bysyncify_stop_rewind)) + (func $calls-import (; 3 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $calls-import2 (; 4 ;) (type $FUNCSIG$i) (result i32) + (local $temp i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (local $7 i32) + (local $8 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -20) + ) + ) + (local.set $7 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $temp + (i32.load + (local.get $7) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $7) + ) + ) + (local.set $2 + (i32.load offset=8 + (local.get $7) + ) + ) + (local.set $3 + (i32.load offset=12 + (local.get $7) + ) + ) + (local.set $4 + (i32.load offset=16 + (local.get $7) + ) + ) + ) + ) + (local.set $5 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $6 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $6) + (i32.const 0) + ) + ) + (block + (local.set $2 + (call $import2) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (return + (local.get $2) + ) + ) + ) + (unreachable) + ) + (unreachable) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $5) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $8 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $8) + (local.get $temp) + ) + (i32.store offset=4 + (local.get $8) + (local.get $1) + ) + (i32.store offset=8 + (local.get $8) + (local.get $2) + ) + (i32.store offset=12 + (local.get $8) + (local.get $3) + ) + (i32.store offset=16 + (local.get $8) + (local.get $4) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 20) + ) + ) + ) + (i32.const 0) + ) + (func $calls-import2-drop (; 5 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $3 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $0 + (i32.load + (local.get $3) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $2 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $2) + (i32.const 0) + ) + ) + (block + (local.set $0 + (call $import2) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $4 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $4) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + ) + (func $calls-nothing (; 6 ;) (type $FUNCSIG$v) + (local $0 i32) + (local.set $0 + (i32.eqz + (i32.const 17) + ) + ) + ) + (func $many-locals (; 7 ;) (type $FUNCSIG$ii) (param $x i32) (result i32) + (local $y i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (local $6 i32) + (local $7 i32) + (local $8 i32) + (local $9 i32) + (local $10 i32) + (local $11 i32) + (local $12 i32) + (local $13 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -40) + ) + ) + (local.set $12 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $x + (i32.load + (local.get $12) + ) + ) + (local.set $y + (i32.load offset=4 + (local.get $12) + ) + ) + (local.set $2 + (i32.load offset=8 + (local.get $12) + ) + ) + (local.set $3 + (i32.load offset=12 + (local.get $12) + ) + ) + (local.set $4 + (i32.load offset=16 + (local.get $12) + ) + ) + (local.set $5 + (i32.load offset=20 + (local.get $12) + ) + ) + (local.set $6 + (i32.load offset=24 + (local.get $12) + ) + ) + (local.set $7 + (i32.load offset=28 + (local.get $12) + ) + ) + (local.set $8 + (i32.load offset=32 + (local.get $12) + ) + ) + (local.set $9 + (i32.load offset=36 + (local.get $12) + ) + ) + ) + ) + (local.set $10 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $11 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (loop $l + (local.set $4 + (i32.add + (local.get $y) + (i32.const 1) + ) + ) + (local.set $y + (i32.div_s + (local.get $4) + (i32.const 3) + ) + ) + (br_if $l + (local.get $y) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $11) + (i32.const 0) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (return + (local.get $y) + ) + ) + ) + (unreachable) + ) + (unreachable) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $10) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $13 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $13) + (local.get $x) + ) + (i32.store offset=4 + (local.get $13) + (local.get $y) + ) + (i32.store offset=8 + (local.get $13) + (local.get $2) + ) + (i32.store offset=12 + (local.get $13) + (local.get $3) + ) + (i32.store offset=16 + (local.get $13) + (local.get $4) + ) + (i32.store offset=20 + (local.get $13) + (local.get $5) + ) + (i32.store offset=24 + (local.get $13) + (local.get $6) + ) + (i32.store offset=28 + (local.get $13) + (local.get $7) + ) + (i32.store offset=32 + (local.get $13) + (local.get $8) + ) + (i32.store offset=36 + (local.get $13) + (local.get $9) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 40) + ) + ) + ) + (i32.const 0) + ) + (func $calls-import2-if (; 8 ;) (type $FUNCSIG$vi) (param $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (local $5 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -8) + ) + ) + (local.set $4 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $x + (i32.load + (local.get $4) + ) + ) + (local.set $1 + (i32.load offset=4 + (local.get $4) + ) + ) + ) + ) + (local.set $2 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $3 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (i32.or + (local.get $x) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $3) + (i32.const 0) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $2) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $5 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $5) + (local.get $x) + ) + (i32.store offset=4 + (local.get $5) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 8) + ) + ) + ) + ) + (func $calls-import2-if-else (; 9 ;) (type $FUNCSIG$vi) (param $x i32) + (local $1 i32) + (if + (local.get $x) + (call $import3 + (i32.const 1) + ) + (call $import3 + (i32.const 2) + ) + ) + ) + (func $calls-import2-if-else-oneside (; 10 ;) (type $FUNCSIG$ii) (param $x i32) (result i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (if + (local.get $x) + (return + (i32.const 1) + ) + (call $import3 + (i32.const 2) + ) + ) + (return + (i32.const 3) + ) + ) + (func $calls-import2-if-else-oneside2 (; 11 ;) (type $FUNCSIG$ii) (param $x i32) (result i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (if + (local.get $x) + (call $import3 + (i32.const 1) + ) + (return + (i32.const 2) + ) + ) + (return + (i32.const 3) + ) + ) + (func $calls-loop (; 12 ;) (type $FUNCSIG$vi) (param $x i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (loop $l + (call $import3 + (i32.const 1) + ) + (local.set $x + (i32.add + (local.get $x) + (i32.const 1) + ) + ) + (br_if $l + (local.get $x) + ) + ) + ) + (func $calls-loop2 (; 13 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (local $2 i32) + (local $3 i32) + (local $4 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $3 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (local.set $0 + (i32.load + (local.get $3) + ) + ) + ) + ) + (local.set $1 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $2 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (loop $l + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $2) + (i32.const 0) + ) + ) + (block + (local.set $0 + (call $import2) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (br_if $l + (local.get $0) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $1) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (block + (local.set $4 + (i32.load + (global.get $__bysyncify_data) + ) + ) + (i32.store + (local.get $4) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + ) + (func $calls-mix (; 14 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $boring) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $boring) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 1) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 1) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $boring (; 15 ;) (type $FUNCSIG$v) + (nop) + ) + (func $calls-mix-deep (; 16 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $boring-deep) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $import-deep) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (call $boring) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 1) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 1) + ) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $boring-deep (; 17 ;) (type $FUNCSIG$v) + (call $boring) + ) + (func $import-deep (; 18 ;) (type $FUNCSIG$v) + (local $0 i32) + (local $1 i32) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (nop) + ) + (local.set $0 + (block $__bysyncify_unwind (result i32) + (block + (block + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 2) + ) + (block + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const -4) + ) + ) + (local.set $1 + (i32.load + (i32.load + (global.get $__bysyncify_data) + ) + ) + ) + ) + ) + (if + (if (result i32) + (i32.eq + (global.get $__bysyncify_state) + (i32.const 0) + ) + (i32.const 1) + (i32.eq + (local.get $1) + (i32.const 0) + ) + ) + (block + (call $import) + (if + (i32.eq + (global.get $__bysyncify_state) + (i32.const 1) + ) + (br $__bysyncify_unwind + (i32.const 0) + ) + ) + ) + ) + ) + (return) + ) + ) + ) + (block + (i32.store + (i32.load + (global.get $__bysyncify_data) + ) + (local.get $0) + ) + (i32.store + (global.get $__bysyncify_data) + (i32.add + (i32.load + (global.get $__bysyncify_data) + ) + (i32.const 4) + ) + ) + ) + (nop) + ) + (func $bysyncify_start_unwind (; 19 ;) (param $0 i32) + (if + (i32.gt_u + (i32.load + (local.get $0) + ) + (i32.load offset=4 + (local.get $0) + ) + ) + (unreachable) + ) + (global.set $__bysyncify_state + (i32.const 1) + ) + (global.set $__bysyncify_data + (local.get $0) + ) + ) + (func $bysyncify_start_rewind (; 20 ;) (param $0 i32) + (if + (i32.gt_u + (i32.load + (local.get $0) + ) + (i32.load offset=4 + (local.get $0) + ) + ) + (unreachable) + ) + (global.set $__bysyncify_state + (i32.const 2) + ) + (global.set $__bysyncify_data + (local.get $0) + ) + ) + (func $bysyncify_stop_rewind (; 21 ;) + (global.set $__bysyncify_state + (i32.const 0) + ) + ) +) diff --git a/test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.wast b/test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.wast new file mode 100644 index 000000000..7c92bd86f --- /dev/null +++ b/test/passes/bysyncify_pass-arg=bysyncify@env.import,env.import2.wast @@ -0,0 +1,146 @@ +;; Pre-existing imports that the pass turns into the implementations. +(module + (memory 1 2) + (import "bysyncify" "start_unwind" (func $bysyncify_start_unwind (param i32))) + (import "bysyncify" "start_rewind" (func $bysyncify_start_rewind (param i32))) + (import "bysyncify" "stop_rewind" (func $bysyncify_stop_rewind)) + (global $sleeping (mut i32) (i32.const 0)) + ;; do a sleep operation: start a sleep if running, or resume after a sleep + ;; if we just rewound. + (func $do_sleep + (if + (i32.eqz (global.get $sleeping)) + (block + (global.set $sleeping (i32.const 1)) + ;; we should set up the data at address 4 around here + (call $bysyncify_start_unwind (i32.const 4)) + ) + (block + (global.set $sleeping (i32.const 0)) + (call $bysyncify_stop_rewind) + ) + ) + ) + ;; a function that does some work and has a sleep (async pause/resume) in the middle + (func $work + (call $stuff) ;; do some work + (call $do_sleep) ;; take a break + (call $stuff) ;; do some more work + ) + (func $stuff) + ;; the first event called from the main event loop: just call into $work + (func $first_event + (call $work) + ;; work will sleep, so we exit through here while it is paused + ) + ;; the second event called from the main event loop: to resume $work, + ;; initiate a rewind, and then do the call to start things back up + (func $second_event + (call $bysyncify_start_rewind (i32.const 4)) + (call $work) + ) + ;; a function that can't do a sleep + (func $never_sleep + (call $stuff) + (call $stuff) + (call $stuff) + ) +) +;; Calls to imports that will call into bysyncify themselves. +(module + (memory 1 2) + (import "env" "import" (func $import)) + (import "env" "import2" (func $import2 (result i32))) + (import "env" "import3" (func $import3 (param i32))) + (func $calls-import + (call $import) + ) + (func $calls-import2 (result i32) + (local $temp i32) + (local.set $temp (call $import2)) + (return (local.get $temp)) + ) + (func $calls-import2-drop + (drop (call $import2)) + ) + (func $calls-nothing + (drop (i32.eqz (i32.const 17))) + ) + (func $many-locals (param $x i32) (result i32) + (local $y i32) + (loop $l + (local.set $x + (i32.add (local.get $y) (i32.const 1)) + ) + (local.set $y + (i32.div_s (local.get $x) (i32.const 3)) + ) + (br_if $l (local.get $y)) + ) + (call $import) + (return (local.get $y)) + ) + (func $calls-import2-if (param $x i32) + (if (local.get $x) + (call $import) + ) + ) + (func $calls-import2-if-else (param $x i32) + (if (local.get $x) + (call $import3 (i32.const 1)) + (call $import3 (i32.const 2)) + ) + ) + (func $calls-import2-if-else-oneside (param $x i32) (result i32) + (if (local.get $x) + (return (i32.const 1)) + (call $import3 (i32.const 2)) + ) + (return (i32.const 3)) + ) + (func $calls-import2-if-else-oneside2 (param $x i32) (result i32) + (if (local.get $x) + (call $import3 (i32.const 1)) + (return (i32.const 2)) + ) + (return (i32.const 3)) + ) + (func $calls-loop (param $x i32) + (loop $l + (call $import3 (i32.const 1)) + (local.set $x + (i32.add (local.get $x) (i32.const 1)) + ) + (br_if $l + (local.get $x) + ) + ) + ) + (func $calls-loop2 + (loop $l + (br_if $l + (call $import2) + ) + ) + ) + (func $calls-mix + (call $boring) + (call $import) + (call $boring) + (call $import) + ) + (func $boring) + (func $calls-mix-deep + (call $boring-deep) + (call $import-deep) + (call $boring) + (call $import) + ) + (func $boring-deep + (call $boring) + ) + (func $import-deep + (call $import) + ) +) + diff --git a/test/unit/input/bysyncify.js b/test/unit/input/bysyncify.js new file mode 100644 index 000000000..97d7a0da5 --- /dev/null +++ b/test/unit/input/bysyncify.js @@ -0,0 +1,155 @@ + +function assert(x, y) { + if (!x) throw (y || 'assertion failed') + '\n' + new Error().stack; +} + +var fs = require('fs'); + +// Get and compile the wasm. + +var binary = fs.readFileSync('a.wasm'); + +var module = new WebAssembly.Module(binary); + +var DATA_ADDR = 4; + +var sleeps = 0; + +var sleeping = false; + +var instance = new WebAssembly.Instance(module, { + env: { + sleep: function() { + logMemory(); +assert(view[0] == 0); + if (!sleeping) { + // We are called in order to start a sleep/unwind. + console.log('sleep...'); + sleeps++; + // Unwinding. + exports.bysyncify_start_unwind(DATA_ADDR); + // Fill in the data structure. The first value has the stack location, + // which for simplicity we can start right after the data structure itself. + view[DATA_ADDR >> 2] = DATA_ADDR + 8; + // The end of the stack will not be reached here anyhow. + view[DATA_ADDR + 4 >> 2] = 1024; + sleeping = true; + } else { + // We are called as part of a resume/rewind. Stop sleeping. + console.log('resume...'); + exports.bysyncify_stop_rewind(); + // The stack should have been all used up, and so returned to the original state. + assert(view[DATA_ADDR >> 2] == DATA_ADDR + 8); + assert(view[DATA_ADDR + 4 >> 2] == 1024); + sleeping = false; + } + logMemory(); + }, + tunnel: function(x) { + console.log('tunneling, sleep == ' + sleeping); + return exports.end_tunnel(x); + } + } +}); + +var exports = instance.exports; +var view = new Int32Array(exports.memory.buffer); + +function logMemory() { + // Log the relevant memory locations for debugging purposes. + console.log('memory: ', view[0 >> 2], view[4 >> 2], view[8 >> 2], view[12 >> 2], view[16 >> 2], view[20 >> 2], view[24 >> 2]); +} + +function runTest(name, expectedSleeps, expectedResult, params) { + params = params || []; + + console.log('\n==== testing ' + name + ' ===='); + + sleeps = 0; + + logMemory(); + + // Run until the sleep. + var result = exports[name].apply(null, params); + logMemory(); + + if (expectedSleeps > 0) { + assert(!result, 'results during sleep are meaningless, just 0'); + + for (var i = 0; i < expectedSleeps - 1; i++) { + console.log('rewind, run until the next sleep'); + exports.bysyncify_start_rewind(DATA_ADDR); + result = exports[name](); // no need for params on later times + assert(!result, 'results during sleep are meaningless, just 0'); + assert(!result, 'bad first sleep result'); + logMemory(); + } + + console.log('rewind and run til the end.'); + exports.bysyncify_start_rewind(DATA_ADDR); + result = exports[name](); + } + + console.log('final result: ' + result); + assert(result == expectedResult, 'bad final result'); + logMemory(); + + assert(sleeps == expectedSleeps, 'expectedSleeps'); +} + +//================ +// Tests +//================ + +// A minimal single sleep. +runTest('minimal', 1, 21); + +// Two sleeps. +runTest('repeat', 2, 42); + +// A value in a local is preserved across a sleep. +runTest('local', 1, 10); + +// A local with more operations done on it. +runTest('local2', 1, 22); + +// A local with more operations done on it. +runTest('params', 1, 18); +runTest('params', 1, 21, [1, 2]); + +// Calls to multiple other functions, only one of whom +// sleeps, and keep locals and globals valid throughout. +runTest('deeper', 0, 27, [0]); +runTest('deeper', 1, 3, [1]); + +// A recursive factorial, that sleeps on each iteration +// above 1. +runTest('factorial-recursive', 0, 1, [1]); +runTest('factorial-recursive', 1, 2, [2]); +runTest('factorial-recursive', 2, 6, [3]); +runTest('factorial-recursive', 3, 24, [4]); +runTest('factorial-recursive', 4, 120, [5]); + +// A looping factorial, that sleeps on each iteration +// above 1. +runTest('factorial-loop', 0, 1, [1]); +runTest('factorial-loop', 1, 2, [2]); +runTest('factorial-loop', 2, 6, [3]); +runTest('factorial-loop', 3, 24, [4]); +runTest('factorial-loop', 4, 120, [5]); + +// Test calling into JS in the middle (which can work if +// the JS just forwards the call and has no side effects or +// state of its own that needs to be saved). +runTest('do_tunnel', 2, 72, [1]); + +// Test indirect function calls. +runTest('call_indirect', 3, 432, [1, 2]); + +// Test indirect function calls. +runTest('if_else', 3, 1460, [1, 1000]); +runTest('if_else', 3, 2520, [2, 2000]); + +// All done. +console.log('\ntests completed successfully'); + diff --git a/test/unit/input/bysyncify.wast b/test/unit/input/bysyncify.wast new file mode 100644 index 000000000..91fb5a327 --- /dev/null +++ b/test/unit/input/bysyncify.wast @@ -0,0 +1,200 @@ +(module + (memory 1 2) + (type $ii (func (param i32) (result i32))) + (import "env" "sleep" (func $sleep)) + (import "env" "tunnel" (func $tunnel (param $x i32) (result i32))) + (export "memory" (memory 0)) + (export "factorial-recursive" (func $factorial-recursive)) + (global $temp (mut i32) (i32.const 0)) + (table 10 funcref) + (elem (i32.const 5) $tablefunc) + (func "minimal" (result i32) + (call $sleep) + (i32.const 21) + ) + (func "repeat" (result i32) + ;; sleep twice, then return 42 + (call $sleep) + (call $sleep) + (i32.const 42) + ) + (func "local" (result i32) + (local $x i32) + (local.set $x (i32.load (i32.const 0))) ;; a zero that the optimizer won't see + (local.set $x + (i32.add (local.get $x) (i32.const 10)) ;; add 10 + ) + (call $sleep) + (local.get $x) + ) + (func "local2" (result i32) + (local $x i32) + (local.set $x (i32.load (i32.const 0))) ;; a zero that the optimizer won't see + (local.set $x + (i32.add (local.get $x) (i32.const 10)) ;; add 10 + ) + (call $sleep) + (local.set $x + (i32.add (local.get $x) (i32.const 12)) ;; add 12 more + ) + (local.get $x) + ) + (func "params" (param $x i32) (param $y i32) (result i32) + (local.set $x + (i32.add (local.get $x) (i32.const 17)) ;; add 10 + ) + (local.set $y + (i32.add (local.get $y) (i32.const 1)) ;; add 12 more + ) + (call $sleep) + (i32.add (local.get $x) (local.get $y)) + ) + (func $pre + (global.set $temp (i32.const 1)) + ) + (func $inner (param $x i32) + (if (i32.eqz (local.get $x)) (call $post)) + (if (local.get $x) (call $sleep)) + (if (i32.eqz (local.get $x)) (call $post)) + ) + (func $post + (global.set $temp + (i32.mul + (global.get $temp) + (i32.const 3) + ) + ) + ) + (func "deeper" (param $x i32) (result i32) + (call $pre) + (call $inner (local.get $x)) + (call $post) + (global.get $temp) + ) + (func $factorial-recursive (param $x i32) (result i32) + (if + (i32.eq + (local.get $x) + (i32.const 1) + ) + (return (i32.const 1)) + ) + (call $sleep) + (return + (i32.mul + (local.get $x) + (call $factorial-recursive + (i32.sub + (local.get $x) + (i32.const 1) + ) + ) + ) + ) + ) + (func "factorial-loop" (param $x i32) (result i32) + (local $i i32) + (local $ret i32) + (local.set $ret (i32.const 1)) + (local.set $i (i32.const 2)) + (loop $l + (if + (i32.gt_u + (local.get $i) + (local.get $x) + ) + (return (local.get $ret)) + ) + (local.set $ret + (i32.mul + (local.get $ret) + (local.get $i) + ) + ) + (call $sleep) + (local.set $i + (i32.add + (local.get $i) + (i32.const 1) + ) + ) + (br $l) + ) + ) + (func "end_tunnel" (param $x i32) (result i32) + (local.set $x + (i32.add (local.get $x) (i32.const 22)) + ) + (call $sleep) + (i32.add (local.get $x) (i32.const 5)) + ) + (func "do_tunnel" (param $x i32) (result i32) + (local.set $x + (i32.add (local.get $x) (i32.const 11)) + ) + (local.set $x + (call $tunnel (local.get $x)) ;; calls js which calls back into wasm for end_tunnel + ) + (call $sleep) + (i32.add (local.get $x) (i32.const 33)) + ) + (func $tablefunc (param $y i32) (result i32) + (local.set $y + (i32.add (local.get $y) (i32.const 10)) + ) + (call $sleep) + (i32.add (local.get $y) (i32.const 30)) + ) + (func "call_indirect" (param $x i32) (param $y i32) (result i32) + (local.set $x + (i32.add (local.get $x) (i32.const 1)) + ) + (call $sleep) + (local.set $x + (i32.add (local.get $x) (i32.const 3)) + ) + (local.set $y + (call_indirect (type $ii) (local.get $y) (local.get $x)) ;; call function pointer x + 4, which will be 5 + ) + (local.set $y + (i32.add (local.get $y) (i32.const 90)) + ) + (call $sleep) + (i32.add (local.get $y) (i32.const 300)) ;; total is 10+30+90+300=430 + y's original value + ) + (func "if_else" (param $x i32) (param $y i32) (result i32) + (if (i32.eq (local.get $x) (i32.const 1)) + (local.set $y + (i32.add (local.get $y) (i32.const 10)) + ) + (local.set $y + (i32.add (local.get $y) (i32.const 20)) + ) + ) + (if (i32.eq (local.get $x) (i32.const 1)) + (local.set $y + (i32.add (local.get $y) (i32.const 40)) + ) + (call $sleep) + ) + (if (i32.eq (local.get $x) (i32.const 1)) + (call $sleep) + (local.set $y + (i32.add (local.get $y) (i32.const 90)) + ) + ) + (if (i32.eq (local.get $x) (i32.const 1)) + (call $sleep) + (call $sleep) + ) + (local.set $y + (i32.add (local.get $y) (i32.const 160)) + ) + (call $sleep) + (local.set $y + (i32.add (local.get $y) (i32.const 250)) + ) + (local.get $y) + ) +) + diff --git a/test/unit/test_bysyncify.py b/test/unit/test_bysyncify.py new file mode 100644 index 000000000..5373a4def --- /dev/null +++ b/test/unit/test_bysyncify.py @@ -0,0 +1,20 @@ +import os + +from scripts.test.shared import WASM_OPT, NODEJS, run_process +from utils import BinaryenTestCase + + +class BysyncifyTest(BinaryenTestCase): + def test_bysyncify(self): + def test(args): + print(args) + run_process(WASM_OPT + args + [self.input_path('bysyncify.wast'), '--bysyncify', '-o', 'a.wasm']) + print(' file size: %d' % os.path.getsize('a.wasm')) + run_process([NODEJS, self.input_path('bysyncify.js')]) + + test(['-g']) + test([]) + test(['-O1']) + test(['--optimize-level=1']) + test(['-O3']) + test(['-Os', '-g']) |