diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp index 0f30e4eecb9..cadf0871b4d 100644 --- a/src/passes/Asyncify.cpp +++ b/src/passes/Asyncify.cpp @@ -96,6 +96,13 @@ // Overall, this should allow good performance with small overhead that is // mostly noticed at rewind time. // +// Exceptions handling (-fwasm-exceptions) is partially supported, everything +// except for handling unwinding from within a catch block. If assertions mode +// is enabled then this pass will check for that problem, and if so, throw an +// unreachable exception. (If "ignore unwind from catch" mode is enabled then +// Asyncify will silently skip any unwind call from within catch blocks, see +// below.) +// // After this pass is run a new i32 global "__asyncify_state" is added, which // has the following values: // @@ -239,6 +246,12 @@ // an unwind/rewind in an invalid place (this can be helpful for manual // tweaking of the only-list / remove-list, see later). // +// --pass-arg=asyncify-ignore-unwind-from-catch +// +// When an unwind operation is triggered from inside a wasm-exceptions +// catch block, which is not supported, silently ignore it rather than +// fail during rewinding later. (This is unsafe in general.) +// // --pass-arg=asyncify-verbose // // Logs out instrumentation decisions to the console. This can help figure @@ -1138,6 +1151,18 @@ struct AsyncifyFlow : public Pass { // here as well. results.push_back(makeCallSupport(curr)); continue; + } else if (auto* try_ = curr->dynCast()) { + if (item.phase == Work::Scan) { + work.push_back(Work{curr, Work::Finish}); + work.push_back(Work{try_->body, Work::Scan}); + // catchBodies are ignored because we assume that pause/resume will + // not happen inside them + continue; + } + try_->body = results.back(); + results.pop_back(); + results.push_back(try_); + continue; } // 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 @@ -1316,6 +1341,93 @@ struct AsyncifyAssertInNonInstrumented : public Pass { Module* module; }; +struct AsyncifyUnwindWalker + : WalkerPass> { + Function* function; + Module* module; + + // Adds a check for Call that is inside a Catch block (we do not handle + // unwinding there). + template void replaceCallWithCheck(T* call) { + auto builder = std::make_unique(*module); + auto check = builder->makeIf( + builder->makeBinary(NeInt32, + builder->makeGlobalGet(ASYNCIFY_STATE, Type::i32), + builder->makeConst(int32_t(State::Normal))), + builder->makeUnreachable()); + if (call->type.isConcrete()) { + auto temp = builder->addVar(function, call->type); + replaceCurrent(builder->makeBlock( + { + builder->makeLocalSet(temp, call), + check, + builder->makeLocalGet(temp, call->type), + }, + call->type)); + } else { + replaceCurrent(builder->makeBlock( + { + call, + check, + }, + call->type)); + } + } + + template void visitCallLike(T* curr) { + assert(!expressionStack.empty()); + // A return_call (curr->isReturn) can be ignored here: It returns first, + // leaving the Catch, before calling. + if (curr->isReturn) { + return; + } + // Go up the stack and see if we are in a Catch. + Index i = expressionStack.size() - 1; + while (i > 0) { + auto* expr = expressionStack[i]; + if (Try* aTry = expr->template dynCast()) { + // check if curr is inside body of aTry (which is safe), + // otherwise do replace a call + assert(i + 1 < expressionStack.size()); + if (expressionStack[i + 1] != aTry->body) { + replaceCallWithCheck(curr); + } + break; + } + i--; + } + } + + void visitCall(Call* curr) { visitCallLike(curr); } + + void visitCallRef(CallRef* curr) { visitCallLike(curr); } + + void visitCallIndirect(CallIndirect* curr) { visitCallLike(curr); } +}; + +struct AsyncifyAssertUnwindCorrectness : Pass { + bool isFunctionParallel() override { return true; } + + ModuleAnalyzer* analyzer; + Module* module; + + AsyncifyAssertUnwindCorrectness(ModuleAnalyzer* analyzer, Module* module) { + this->analyzer = analyzer; + this->module = module; + } + + std::unique_ptr create() override { + return std::make_unique(analyzer, module); + } + + void runOnFunction(Module* module_, Function* function) override { + AsyncifyUnwindWalker walker; + walker.function = function; + walker.module = module_; + walker.walk(function->body); + } +}; + // Instrument local saving/restoring. struct AsyncifyLocals : public WalkerPass> { bool isFunctionParallel() override { return true; } @@ -1761,6 +1873,8 @@ struct Asyncify : public Pass { PassRunner runner(module); runner.add(std::make_unique( &analyzer, pointerType, asyncifyMemory)); + runner.add( + std::make_unique(&analyzer, module)); runner.setIsNested(true); runner.setValidateGlobally(false); runner.run(); diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-eh-asserts.wast b/test/lit/passes/asyncify_pass-arg=asyncify-eh-asserts.wast new file mode 100644 index 00000000000..2a9a2b244ba --- /dev/null +++ b/test/lit/passes/asyncify_pass-arg=asyncify-eh-asserts.wast @@ -0,0 +1,897 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. + +;; RUN: foreach %s %t wasm-opt --enable-exception-handling --asyncify --pass-arg=asyncify-asserts --pass-arg=asyncify-onlylist@level0,level1 -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (param i32))) + + ;; CHECK: (type $2 (func (result i32))) + + ;; CHECK: (import "extmod" "exttag" (tag $default (type $1) (param i32))) + (import "extmod" "exttag" (tag $default (param i32))) + ;; CHECK: (import "extmod" "exttag" (tag $ret (type $1) (param i32))) + (import "extmod" "exttag" (tag $ret (param i32))) + ;; CHECK: (import "extmod" "exttag" (tag $brif (type $1) (param i32))) + (import "extmod" "exttag" (tag $brif (param i32))) + + (memory 1 2) + + ;; CHECK: (global $__asyncify_state (mut i32) (i32.const 0)) + + ;; CHECK: (global $__asyncify_data (mut i32) (i32.const 0)) + + ;; CHECK: (memory $0 1 2) + + ;; CHECK: (export "asyncify_start_unwind" (func $asyncify_start_unwind)) + + ;; CHECK: (export "asyncify_stop_unwind" (func $asyncify_stop_unwind)) + + ;; CHECK: (export "asyncify_start_rewind" (func $asyncify_start_rewind)) + + ;; CHECK: (export "asyncify_stop_rewind" (func $asyncify_stop_rewind)) + + ;; CHECK: (export "asyncify_get_state" (func $asyncify_get_state)) + + ;; CHECK: (func $level0 + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 i32) + ;; CHECK-NEXT: (local $10 i32) + ;; CHECK-NEXT: (local $11 i32) + ;; CHECK-NEXT: (local $12 i32) + ;; CHECK-NEXT: (local $13 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $12 + ;; CHECK-NEXT: (block $__asyncify_unwind + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $13 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $label + ;; CHECK-NEXT: (try $try1 + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $fn1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $default + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (call $fn2) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $ret + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (call $fn2) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $brif + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $10 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $11 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (call $fn2) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (call $fn3) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $level0 + (local $0 i32) + (try $try1 + (do + (br_if $try1 + (i32.eqz + (local.get $0) + ) + ) + (if + (i32.eqz + (local.get $0) + ) + (then + (return) + ) + ) + (call $fn1) + ) + (catch $default + (local.set $0 + (pop i32) + ) + (call $fn2) + ) + (catch $ret + (local.set $0 + (pop i32) + ) + (if + (i32.eqz + (local.get $0) + ) + (then + (return) + ) + ) + (call $fn2) + ) + (catch $brif + (local.set $0 + (pop i32) + ) + (br_if $try1 + (i32.eqz + (local.get $0) + ) + ) + (call $fn2) + ) + (catch_all + (call $fn3) + ) + ) + ) + + ;; CHECK: (func $level1 + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 i32) + ;; CHECK-NEXT: (local $10 i32) + ;; CHECK-NEXT: (local $11 i32) + ;; CHECK-NEXT: (local $12 i32) + ;; CHECK-NEXT: (local $13 i32) + ;; CHECK-NEXT: (local $14 i32) + ;; CHECK-NEXT: (local $15 i32) + ;; CHECK-NEXT: (local $16 i32) + ;; CHECK-NEXT: (local $17 i32) + ;; CHECK-NEXT: (local $18 i32) + ;; CHECK-NEXT: (local $19 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $18 + ;; CHECK-NEXT: (block $__asyncify_unwind + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $19 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (block $label + ;; CHECK-NEXT: (try $try1 + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $fn1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (call $fn2) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $fn3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $default + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (call $fn1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (call $fn3) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (call $fn2) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $ret + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (call $fn1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (call $fn3) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $10 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (call $fn2) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $brif + ;; CHECK-NEXT: (local.set $11 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $nottop + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (call $fn1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (local.set $12 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $13 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $nottop + ;; CHECK-NEXT: (local.get $13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $14 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $15 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (call $fn3) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $16 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $17 + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br_if $label + ;; CHECK-NEXT: (local.get $17) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (call $fn2) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (call $fn1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (call $fn2) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (call $fn3) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.ne + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $18) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $level1 + (local $0 i32) + (try $try1 + (do + (try + (do + (br_if $try1 + (i32.eqz + (local.get $0) + ) + ) + (if + (i32.eqz + (local.get $0) + ) + (then + (return) + ) + ) + (call $fn1) + ) + (catch_all + (call $fn2) + ) + ) + (call $fn3) + ) + (catch $default + (local.set $0 + (pop i32) + ) + (try + (do + (call $fn1) + ) + (catch_all + (call $fn3) + ) + ) + (call $fn2) + ) + (catch $ret + (local.set $0 + (pop i32) + ) + (try + (do + (call $fn1) + ) + (catch_all + (if + (i32.eqz + (local.get $0) + ) + (then + (return) + ) + ) + (call $fn3) + ) + ) + (if + (i32.eqz + (local.get $0) + ) + (then + (return) + ) + ) + (call $fn2) + ) + (catch $brif + (local.set $0 + (pop i32) + ) + (block $nottop + (try + (do + (call $fn1) + ) + (catch_all + (br_if $nottop + (i32.eqz + (local.get $0) + ) + ) + (br_if $try1 + (i32.eqz + (local.get $0) + ) + ) + (call $fn3) + ) + ) + ) + (br_if $try1 + (i32.eqz + (local.get $0) + ) + ) + (call $fn2) + ) + (catch_all + (try + (do + (call $fn1) + ) + (catch_all + (call $fn2) + ) + ) + (call $fn3) + ) + ) + ) + ;; CHECK: (func $fn1 + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fn1 + ) + + ;; CHECK: (func $fn2 + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fn2 + ) + + ;; CHECK: (func $fn3 + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $fn3 + ) +) +;; CHECK: (func $asyncify_start_unwind (param $0 i32) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_data +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_stop_unwind +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_start_rewind (param $0 i32) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_data +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_stop_rewind +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_get_state (result i32) +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: ) diff --git a/test/lit/passes/asyncify_pass-arg=asyncify-eh.wast b/test/lit/passes/asyncify_pass-arg=asyncify-eh.wast new file mode 100644 index 00000000000..e3d932bb500 --- /dev/null +++ b/test/lit/passes/asyncify_pass-arg=asyncify-eh.wast @@ -0,0 +1,242 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. + +;; RUN: foreach %s %t wasm-opt --enable-exception-handling --asyncify --pass-arg=asyncify-onlylist@foo -S -o - | filecheck %s + +(module + ;; CHECK: (type $0 (func)) + + ;; CHECK: (type $1 (func (param i32))) + + ;; CHECK: (type $2 (func (result i32))) + + ;; CHECK: (import "extmod" "exttag" (tag $default (type $1) (param i32))) + (import "extmod" "exttag" (tag $default (param i32))) + + (memory 1 2) + + ;; CHECK: (global $__asyncify_state (mut i32) (i32.const 0)) + + ;; CHECK: (global $__asyncify_data (mut i32) (i32.const 0)) + + ;; CHECK: (memory $0 1 2) + + ;; CHECK: (export "asyncify_start_unwind" (func $asyncify_start_unwind)) + + ;; CHECK: (export "asyncify_stop_unwind" (func $asyncify_stop_unwind)) + + ;; CHECK: (export "asyncify_start_rewind" (func $asyncify_start_rewind)) + + ;; CHECK: (export "asyncify_stop_rewind" (func $asyncify_stop_rewind)) + + ;; CHECK: (export "asyncify_get_state" (func $asyncify_get_state)) + + ;; CHECK: (func $foo + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.tee $2 + ;; CHECK-NEXT: (block $__asyncify_unwind + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (call $fn1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $default + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $fn2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch_all + ;; CHECK-NEXT: (call $fn3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $foo + (local $0 i32) + (try + (do + (call $fn1) + ) + (catch $default + (local.set $0 + (pop i32) + ) + (call $fn2) + ) + (catch_all + (call $fn3) + ) + ) + ) + + ;; CHECK: (func $fn1 + ;; CHECK-NEXT: ) + (func $fn1 + ) + + ;; CHECK: (func $fn2 + ;; CHECK-NEXT: ) + (func $fn2 + ) + + ;; CHECK: (func $fn3 + ;; CHECK-NEXT: ) + (func $fn3 + ) +) +;; CHECK: (func $asyncify_start_unwind (param $0 i32) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_data +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_stop_unwind +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_start_rewind (param $0 i32) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_data +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_stop_rewind +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_get_state (result i32) +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: )