diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index e9818681a56..a763006ab66 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -57,7 +57,6 @@ set(passes_SOURCES Intrinsics.cpp J2CLItableMerging.cpp J2CLOpts.cpp - JSPI.cpp LegalizeJSInterface.cpp LimitSegments.cpp LLVMMemoryCopyFillLowering.cpp diff --git a/src/passes/JSPI.cpp b/src/passes/JSPI.cpp deleted file mode 100644 index 192b5dfa9e6..00000000000 --- a/src/passes/JSPI.cpp +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright 2022 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. - */ - -#include "asmjs/shared-constants.h" -#include "ir/element-utils.h" -#include "ir/import-utils.h" -#include "ir/literal-utils.h" -#include "ir/module-splitting.h" -#include "ir/names.h" -#include "ir/utils.h" -#include "pass.h" -#include "shared-constants.h" -#include "support/file.h" -#include "support/string.h" -#include "wasm-builder.h" -#include "wasm.h" -#include - -// -// Convert a module to be compatible with JavaScript promise integration (JSPI). -// Promising exports will be wrapped with a function that will handle storing -// the suspender that is passed in as the first param from a "promising" -// `WebAssembly.Function`. Suspending imports will also be wrapped, but they -// will take the stored suspender and pass it as the first param to the imported -// function that should be created from a "suspending" `WebAssembly.Function`. -// -// By default all imports and exports will be wrapped, but this can be -// controlled with the following options: -// -// --pass-arg=jspi-imports@module1.base1,module2.base2,module3.base3 -// -// Wrap each import in the comma-separated list. Wildcards and a separate -// files are supported. See `asyncify-imports` for more details. -// -// --pass-arg=jspi-exports@function_one,function_two,function_three -// -// Wrap each export in the comma-separated list. Similar to jspi-imports, -// wildcards and separate files are supported. -// - -namespace wasm { - -static std::string getFullFunctionName(Name module, Name base) { - return std::string(module.str) + '.' + base.toString(); -} - -static bool canChangeState(std::string name, String::Split stateChangers) { - // When no state changers are given default to everything changes state. - if (stateChangers.empty()) { - return true; - } - for (auto& stateChanger : stateChangers) { - if (String::wildcardMatch(stateChanger, name)) { - return true; - } - } - return false; -} - -struct JSPI : public Pass { - - Type externref = Type(HeapType::ext, Nullable); - - void run(Module* module) override { - Builder builder(*module); - - // Find which imports can suspend. - auto stateChangingImports = String::trim( - read_possible_response_file(getArgumentOrDefault("jspi-imports", ""))); - String::Split listedImports(stateChangingImports, ","); - - // Find which exports should create a promise. - auto stateChangingExports = String::trim( - read_possible_response_file(getArgumentOrDefault("jspi-exports", ""))); - String::Split listedExports(stateChangingExports, ","); - - // Create a global to store the suspender that is passed into exported - // functions and will then need to be passed out to the imported functions. - Name suspender = Names::getValidGlobalName(*module, "suspender"); - module->addGlobal(builder.makeGlobal(suspender, - externref, - builder.makeRefNull(HeapType::noext), - Builder::Mutable)); - - // Keep track of already wrapped functions since they can be exported - // multiple times, but only one wrapper is needed. - std::unordered_map wrappedExports; - - // Wrap each exported function in a function that stores the suspender - // and calls the original export. - for (auto& ex : module->exports) { - if (ex->kind == ExternalKind::Function && - canChangeState(ex->name.toString(), listedExports)) { - auto* name = ex->getInternalName(); - auto* func = module->getFunction(*name); - Name wrapperName; - auto iter = wrappedExports.find(func->name); - if (iter == wrappedExports.end()) { - wrapperName = makeWrapperForExport(func, module, suspender); - wrappedExports[func->name] = wrapperName; - } else { - wrapperName = iter->second; - } - *name = wrapperName; - } - } - - // Replace any references to the original exports that are in the elements. - for (auto& segment : module->elementSegments) { - if (!segment->type.isFunction()) { - continue; - } - for (Index i = 0; i < segment->data.size(); i++) { - if (auto* get = segment->data[i]->dynCast()) { - auto iter = wrappedExports.find(get->func); - if (iter == wrappedExports.end()) { - continue; - } - auto* replacementRef = builder.makeRefFunc(iter->second); - segment->data[i] = replacementRef; - } - } - } - - // Avoid iterator invalidation later. - std::vector originalFunctions; - for (auto& func : module->functions) { - originalFunctions.push_back(func.get()); - } - // Wrap each imported function in a function that gets the global suspender - // and passes it on to the imported function. - for (auto* im : originalFunctions) { - if (im->imported() && - canChangeState(getFullFunctionName(im->module, im->base), - listedImports)) { - makeWrapperForImport(im, module, suspender); - } - } - } - -private: - Name makeWrapperForExport(Function* func, Module* module, Name suspender) { - Name wrapperName = Names::getValidFunctionName( - *module, std::string("export$") + func->name.toString()); - - Builder builder(*module); - - auto* call = module->allocator.alloc(); - call->target = func->name; - call->type = func->getResults(); - - // Add an externref param as the first argument and copy all the original - // params to new export. - std::vector wrapperParams; - std::vector namedWrapperParams; - wrapperParams.push_back(externref); - namedWrapperParams.emplace_back(Names::getValidLocalName(*func, "susp"), - externref); - Index i = 0; - for (const auto& param : func->getParams()) { - call->operands.push_back( - builder.makeLocalGet(wrapperParams.size(), param)); - wrapperParams.push_back(param); - namedWrapperParams.emplace_back(func->getLocalNameOrGeneric(i), param); - i++; - } - auto* block = builder.makeBlock(); - block->list.push_back( - builder.makeGlobalSet(suspender, builder.makeLocalGet(0, externref))); - block->list.push_back(call); - Type resultsType = func->getResults(); - if (resultsType == Type::none) { - // A void return is not currently allowed by v8. Add an i32 return value - // that is ignored. - // https://bugs.chromium.org/p/v8/issues/detail?id=13231 - resultsType = Type::i32; - block->list.push_back(builder.makeConst(0)); - } - block->finalize(); - auto wrapperFunc = Builder::makeFunction( - wrapperName, - std::move(namedWrapperParams), - Type(Signature(Type(wrapperParams), resultsType), NonNullable, Exact), - {}, - block); - return module->addFunction(std::move(wrapperFunc))->name; - } - - void makeWrapperForImport(Function* im, Module* module, Name suspender) { - Builder builder(*module); - auto wrapperIm = std::make_unique(); - wrapperIm->name = Names::getValidFunctionName( - *module, std::string("import$") + im->name.toString()); - wrapperIm->module = im->module; - wrapperIm->base = im->base; - auto stub = std::make_unique(); - stub->name = Name(im->name.str); - stub->type = im->type.with(Exact); - - auto* call = module->allocator.alloc(); - call->target = wrapperIm->name; - - // Add an externref as the first argument to the imported function. - std::vector params; - params.push_back(externref); - call->operands.push_back(builder.makeGlobalGet(suspender, externref)); - Index i = 0; - for (const auto& param : im->getParams()) { - call->operands.push_back(builder.makeLocalGet(i, param)); - params.push_back(param); - ++i; - } - auto* block = builder.makeBlock(); - // Store the suspender so it can be restored after the call in case it is - // modified by another entry into a Wasm export. - auto supsenderCopyIndex = Builder::addVar(stub.get(), externref); - // If there's a return value we need to store it so it can be returned - // after restoring the suspender. - std::optional returnIndex; - if (stub->getResults().isConcrete()) { - returnIndex = Builder::addVar(stub.get(), stub->getResults()); - } - block->list.push_back(builder.makeLocalSet( - supsenderCopyIndex, builder.makeGlobalGet(suspender, externref))); - if (returnIndex) { - block->list.push_back(builder.makeLocalSet(*returnIndex, call)); - } else { - block->list.push_back(call); - } - // Restore the suspender. - block->list.push_back(builder.makeGlobalSet( - suspender, builder.makeLocalGet(supsenderCopyIndex, externref))); - if (returnIndex) { - block->list.push_back( - builder.makeLocalGet(*returnIndex, stub->getResults())); - } - block->finalize(); - call->type = im->getResults(); - stub->body = block; - wrapperIm->type = - Type(Signature(Type(params), call->type), NonNullable, Inexact); - - module->removeFunction(im->name); - module->addFunction(std::move(stub)); - module->addFunction(std::move(wrapperIm)); - } -}; - -Pass* createJSPIPass() { return new JSPI(); } - -} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index e4c0d6cf9bb..75dd6f5c45b 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -234,9 +234,6 @@ void PassRegistry::registerPasses() { registerPass("intrinsic-lowering", "lower away binaryen intrinsics", createIntrinsicLoweringPass); - registerPass("jspi", - "wrap imports and exports for JavaScript promise integration", - createJSPIPass); registerPass("legalize-js-interface", "legalizes i64 types on the import/export boundary", createLegalizeJSInterfacePass); diff --git a/src/passes/passes.h b/src/passes/passes.h index e8223e0bac8..23e7ad20062 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -71,7 +71,6 @@ Pass* createInlineMainPass(); Pass* createInliningPass(); Pass* createInliningOptimizingPass(); Pass* createJ2CLItableMergingPass(); -Pass* createJSPIPass(); Pass* createJ2CLOptsPass(); Pass* createLegalizeAndPruneJSInterfacePass(); Pass* createLegalizeJSInterfacePass(); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index d205670d046..c94b502fb95 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -221,9 +221,6 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --intrinsic-lowering lower away binaryen intrinsics ;; CHECK-NEXT: -;; CHECK-NEXT: --jspi wrap imports and exports for -;; CHECK-NEXT: JavaScript promise integration -;; CHECK-NEXT: ;; CHECK-NEXT: --legalize-and-prune-js-interface legalizes the import/export ;; CHECK-NEXT: boundary and prunes when needed ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 1958a3d9102..e797469894c 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -253,9 +253,6 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --intrinsic-lowering lower away binaryen intrinsics ;; CHECK-NEXT: -;; CHECK-NEXT: --jspi wrap imports and exports for -;; CHECK-NEXT: JavaScript promise integration -;; CHECK-NEXT: ;; CHECK-NEXT: --legalize-and-prune-js-interface legalizes the import/export ;; CHECK-NEXT: boundary and prunes when needed ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 21a069d595d..c8a1ab89c94 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -185,9 +185,6 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --intrinsic-lowering lower away binaryen intrinsics ;; CHECK-NEXT: -;; CHECK-NEXT: --jspi wrap imports and exports for -;; CHECK-NEXT: JavaScript promise integration -;; CHECK-NEXT: ;; CHECK-NEXT: --legalize-and-prune-js-interface legalizes the import/export ;; CHECK-NEXT: boundary and prunes when needed ;; CHECK-NEXT: diff --git a/test/lit/passes/jspi-args.wast b/test/lit/passes/jspi-args.wast deleted file mode 100644 index 8229e34e971..00000000000 --- a/test/lit/passes/jspi-args.wast +++ /dev/null @@ -1,71 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: wasm-opt %s --jspi --pass-arg=jspi-imports@js.sleep_async --pass-arg=jspi-exports@update_state_async -all -S -o - | filecheck %s - -(module - ;; sleep_async should have a wrapper function built. - (import "js" "sleep_async" (func $sleep_async (param f64) (result i32))) - ;; CHECK: (type $0 (func (param f64) (result i32))) - - ;; CHECK: (type $1 (func (param externref f64) (result i32))) - - ;; CHECK: (import "js" "sleep_sync" (func $sleep_sync (type $0) (param f64) (result i32))) - (import "js" "sleep_sync" (func $sleep_sync (param f64) (result i32))) - (export "update_state_async" (func $update_state_async)) - ;; CHECK: (import "js" "sleep_async" (func $import$sleep_async (type $1) (param externref f64) (result i32))) - - ;; CHECK: (global $suspender (mut externref) (ref.null noextern)) - - ;; CHECK: (export "update_state_async" (func $export$update_state_async)) - - ;; CHECK: (export "update_state_sync" (func $update_state_sync)) - (export "update_state_sync" (func $update_state_sync)) - ;; This function calls an async sleep so a wrapper should be created for it. - ;; CHECK: (func $update_state_async (type $0) (param $param f64) (result i32) - ;; CHECK-NEXT: (call $sleep_async - ;; CHECK-NEXT: (f64.sub - ;; CHECK-NEXT: (f64.const 1.1) - ;; CHECK-NEXT: (local.get $param) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $update_state_async (param $param f64) (result i32) - (call $sleep_async (f64.sub (f64.const 1.1) (local.get $param))) - ) - ;; CHECK: (func $update_state_sync (type $0) (param $param f64) (result i32) - ;; CHECK-NEXT: (call $sleep_sync - ;; CHECK-NEXT: (f64.sub - ;; CHECK-NEXT: (f64.const 1.1) - ;; CHECK-NEXT: (local.get $param) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $update_state_sync (param $param f64) (result i32) - (call $sleep_sync (f64.sub (f64.const 1.1) (local.get $param))) - ) -) -;; CHECK: (func $export$update_state_async (type $1) (param $susp externref) (param $param f64) (result i32) -;; CHECK-NEXT: (global.set $suspender -;; CHECK-NEXT: (local.get $susp) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (call $update_state_async -;; CHECK-NEXT: (local.get $param) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $sleep_async (type $0) (param $0 f64) (result i32) -;; CHECK-NEXT: (local $1 externref) -;; CHECK-NEXT: (local $2 i32) -;; CHECK-NEXT: (local.set $1 -;; CHECK-NEXT: (global.get $suspender) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $2 -;; CHECK-NEXT: (call $import$sleep_async -;; CHECK-NEXT: (global.get $suspender) -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (global.set $suspender -;; CHECK-NEXT: (local.get $1) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.get $2) -;; CHECK-NEXT: ) diff --git a/test/lit/passes/jspi-table.wast b/test/lit/passes/jspi-table.wast deleted file mode 100644 index c702357c014..00000000000 --- a/test/lit/passes/jspi-table.wast +++ /dev/null @@ -1,35 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: wasm-opt %s --jspi -all -S -o - | filecheck %s - -(module - ;; CHECK: (type $0 (func (param f64) (result i32))) - - ;; CHECK: (type $1 (func (param externref f64) (result i32))) - - ;; CHECK: (global $suspender (mut externref) (ref.null noextern)) - - ;; CHECK: (table $0 1 1 funcref) - (table $0 1 1 funcref) - (elem (i32.const 1) func $update_state) - (export "update_state" (func $update_state)) - - ;; CHECK: (elem $0 (i32.const 1) $export$update_state) - - ;; CHECK: (export "update_state" (func $export$update_state)) - - ;; CHECK: (func $update_state (type $0) (param $param f64) (result i32) - ;; CHECK-NEXT: (i32.const 42) - ;; CHECK-NEXT: ) - (func $update_state (param $param f64) (result i32) - (i32.const 42) - ) -) -;; CHECK: (func $export$update_state (type $1) (param $susp externref) (param $param f64) (result i32) -;; CHECK-NEXT: (global.set $suspender -;; CHECK-NEXT: (local.get $susp) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (call $update_state -;; CHECK-NEXT: (local.get $param) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) diff --git a/test/lit/passes/jspi.wast b/test/lit/passes/jspi.wast deleted file mode 100644 index 12396cc6d71..00000000000 --- a/test/lit/passes/jspi.wast +++ /dev/null @@ -1,174 +0,0 @@ -;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. - -;; RUN: wasm-opt %s --jspi -all -S -o - | filecheck %s - -(module - - (import "js" "compute_delta" (func $compute_delta (param f64) (result i32))) - (import "js" "import_and_export" (func $import_and_export (param i32) (result i32))) - (import "js" "import_void_return" (func $import_void_return (param i32))) - (export "update_state_void" (func $update_state_void)) - (export "update_state" (func $update_state)) - ;; Test duplicating an export. - (export "update_state_again" (func $update_state)) - ;; Test that a name collision on the parameters is handled. - (export "update_state_param_collision" (func $update_state_param_collision)) - ;; Test function that is imported and exported. - (export "import_and_export" (func $import_and_export)) - - - ;; CHECK: (type $0 (func (param externref f64) (result i32))) - - ;; CHECK: (type $1 (func (param f64) (result i32))) - - ;; CHECK: (type $2 (func (param externref i32) (result i32))) - - ;; CHECK: (type $3 (func (param f64))) - - ;; CHECK: (type $4 (func (param i32) (result i32))) - - ;; CHECK: (type $5 (func (param i32))) - - ;; CHECK: (type $6 (func (param externref i32))) - - ;; CHECK: (import "js" "compute_delta" (func $import$compute_delta (type $0) (param externref f64) (result i32))) - - ;; CHECK: (import "js" "import_and_export" (func $import$import_and_export (type $2) (param externref i32) (result i32))) - - ;; CHECK: (import "js" "import_void_return" (func $import$import_void_return (type $6) (param externref i32))) - - ;; CHECK: (global $suspender (mut externref) (ref.null noextern)) - - ;; CHECK: (export "update_state_void" (func $export$update_state_void)) - - ;; CHECK: (export "update_state" (func $export$update_state)) - - ;; CHECK: (export "update_state_again" (func $export$update_state)) - - ;; CHECK: (export "update_state_param_collision" (func $export$update_state_param_collision)) - - ;; CHECK: (export "import_and_export" (func $export$import_and_export)) - - ;; CHECK: (func $update_state (type $1) (param $param f64) (result i32) - ;; CHECK-NEXT: (call $compute_delta - ;; CHECK-NEXT: (f64.sub - ;; CHECK-NEXT: (f64.const 1.1) - ;; CHECK-NEXT: (local.get $param) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $update_state (param $param f64) (result i32) - (call $compute_delta (f64.sub (f64.const 1.1) (local.get $param))) - ) - - ;; CHECK: (func $update_state_void (type $3) (param $0 f64) - ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (call $compute_delta - ;; CHECK-NEXT: (f64.const 1.1) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $update_state_void (param f64) - ;; This function doesn't return anything, but the JSPI pass should add a - ;; fake return value to make v8 happy. - (drop (call $compute_delta (f64.const 1.1))) - ) - - ;; CHECK: (func $update_state_param_collision (type $1) (param $susp f64) (result i32) - ;; CHECK-NEXT: (call $update_state_param_collision - ;; CHECK-NEXT: (f64.sub - ;; CHECK-NEXT: (f64.const 1.1) - ;; CHECK-NEXT: (local.get $susp) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $update_state_param_collision (param $susp f64) (result i32) - (call $update_state_param_collision (f64.sub (f64.const 1.1) (local.get $susp))) - ) -) -;; CHECK: (func $export$update_state_void (type $0) (param $susp externref) (param $0 f64) (result i32) -;; CHECK-NEXT: (global.set $suspender -;; CHECK-NEXT: (local.get $susp) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (call $update_state_void -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (i32.const 0) -;; CHECK-NEXT: ) - -;; CHECK: (func $export$update_state (type $0) (param $susp externref) (param $param f64) (result i32) -;; CHECK-NEXT: (global.set $suspender -;; CHECK-NEXT: (local.get $susp) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (call $update_state -;; CHECK-NEXT: (local.get $param) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $export$update_state_param_collision (type $0) (param $susp_1 externref) (param $susp f64) (result i32) -;; CHECK-NEXT: (global.set $suspender -;; CHECK-NEXT: (local.get $susp_1) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (call $update_state_param_collision -;; CHECK-NEXT: (local.get $susp) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $export$import_and_export (type $2) (param $susp externref) (param $0 i32) (result i32) -;; CHECK-NEXT: (global.set $suspender -;; CHECK-NEXT: (local.get $susp) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (call $import_and_export -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $compute_delta (type $1) (param $0 f64) (result i32) -;; CHECK-NEXT: (local $1 externref) -;; CHECK-NEXT: (local $2 i32) -;; CHECK-NEXT: (local.set $1 -;; CHECK-NEXT: (global.get $suspender) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $2 -;; CHECK-NEXT: (call $import$compute_delta -;; CHECK-NEXT: (global.get $suspender) -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (global.set $suspender -;; CHECK-NEXT: (local.get $1) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.get $2) -;; CHECK-NEXT: ) - -;; CHECK: (func $import_and_export (type $4) (param $0 i32) (result i32) -;; CHECK-NEXT: (local $1 externref) -;; CHECK-NEXT: (local $2 i32) -;; CHECK-NEXT: (local.set $1 -;; CHECK-NEXT: (global.get $suspender) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $2 -;; CHECK-NEXT: (call $import$import_and_export -;; CHECK-NEXT: (global.get $suspender) -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (global.set $suspender -;; CHECK-NEXT: (local.get $1) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.get $2) -;; CHECK-NEXT: ) - -;; CHECK: (func $import_void_return (type $5) (param $0 i32) -;; CHECK-NEXT: (local $1 externref) -;; CHECK-NEXT: (local.set $1 -;; CHECK-NEXT: (global.get $suspender) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (call $import$import_void_return -;; CHECK-NEXT: (global.get $suspender) -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (global.set $suspender -;; CHECK-NEXT: (local.get $1) -;; CHECK-NEXT: ) -;; CHECK-NEXT: )