diff options
-rw-r--r-- | src/ir/export-utils.h | 10 | ||||
-rw-r--r-- | src/ir/module-splitting.cpp | 55 | ||||
-rw-r--r-- | src/ir/module-splitting.h | 5 | ||||
-rw-r--r-- | src/passes/JSPI.cpp | 39 | ||||
-rw-r--r-- | src/tools/wasm-split/split-options.cpp | 17 | ||||
-rw-r--r-- | src/tools/wasm-split/split-options.h | 2 | ||||
-rw-r--r-- | src/tools/wasm-split/wasm-split.cpp | 16 | ||||
-rw-r--r-- | test/lit/help/wasm-split.test | 8 | ||||
-rw-r--r-- | test/lit/passes/jspi-split-module.wast | 30 | ||||
-rw-r--r-- | test/lit/wasm-split/asyncify.wast | 174 | ||||
-rw-r--r-- | test/lit/wasm-split/jspi-secondary-export.wast | 83 | ||||
-rw-r--r-- | test/lit/wasm-split/jspi.wast | 98 |
12 files changed, 334 insertions, 203 deletions
diff --git a/src/ir/export-utils.h b/src/ir/export-utils.h index f50c0afa5..ce6db7663 100644 --- a/src/ir/export-utils.h +++ b/src/ir/export-utils.h @@ -24,6 +24,16 @@ namespace wasm::ExportUtils { std::vector<Function*> getExportedFunctions(Module& wasm); std::vector<Global*> getExportedGlobals(Module& wasm); +inline bool isExported(const Module& module, const Function& func) { + for (auto& exportFunc : module.exports) { + if (exportFunc->kind == ExternalKind::Function && + exportFunc->value == func.name) { + return true; + } + } + return false; +}; + } // namespace wasm::ExportUtils #endif // wasm_ir_export_h diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index a350f0e33..3d876bf7a 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -69,6 +69,7 @@ #include "ir/module-splitting.h" #include "ir/element-utils.h" +#include "ir/export-utils.h" #include "ir/manipulation.h" #include "ir/module-utils.h" #include "ir/names.h" @@ -80,6 +81,8 @@ namespace wasm::ModuleSplitting { namespace { +static const Name LOAD_SECONDARY_STATUS = "load_secondary_module_status"; + template<class F> void forEachElement(Module& module, F f) { ModuleUtils::iterActiveElementSegments(module, [&](ElementSegment* segment) { Name base = ""; @@ -273,6 +276,9 @@ struct ModuleSplitter { // Map placeholder indices to the names of the functions they replace. std::map<size_t, Name> placeholderMap; + // Internal name of the LOAD_SECONDARY_MODULE function. + Name internalLoadSecondaryModule; + // Initialization helpers static std::unique_ptr<Module> initSecondary(const Module& primary); static std::pair<std::set<Name>, std::set<Name>> @@ -281,8 +287,10 @@ struct ModuleSplitter { // Other helpers void exportImportFunction(Name func); + Expression* maybeLoadSecondary(Builder& builder, Expression* callIndirect); // Main splitting steps + void setupJSPI(); void moveSecondaryFunctions(); void thunkExportedSecondaryFunctions(); void indirectCallsToSecondaryFunctions(); @@ -297,6 +305,9 @@ struct ModuleSplitter { primaryFuncs(classifiedFuncs.first), secondaryFuncs(classifiedFuncs.second), tableManager(primary), exportedPrimaryFuncs(initExportedPrimaryFuncs(primary)) { + if (config.jspi) { + setupJSPI(); + } moveSecondaryFunctions(); thunkExportedSecondaryFunctions(); indirectCallsToSecondaryFunctions(); @@ -306,6 +317,23 @@ struct ModuleSplitter { } }; +void ModuleSplitter::setupJSPI() { + assert(primary.getExportOrNull(LOAD_SECONDARY_MODULE) && + "The load secondary module function must exist"); + // Remove the exported LOAD_SECONDARY_MODULE function since it's only needed + // internally. + internalLoadSecondaryModule = primary.getExport(LOAD_SECONDARY_MODULE)->value; + primary.removeExport(LOAD_SECONDARY_MODULE); + Builder builder(primary); + // Add a global to track whether the secondary module has been loaded yet. + primary.addGlobal(builder.makeGlobal(LOAD_SECONDARY_STATUS, + Type::i32, + builder.makeConst(int32_t(0)), + Builder::Mutable)); + primary.addExport(builder.makeExport( + LOAD_SECONDARY_STATUS, LOAD_SECONDARY_STATUS, ExternalKind::Global)); +} + std::unique_ptr<Module> ModuleSplitter::initSecondary(const Module& primary) { // Create the secondary module and copy trivial properties. auto secondary = std::make_unique<Module>(); @@ -318,7 +346,12 @@ std::pair<std::set<Name>, std::set<Name>> ModuleSplitter::classifyFunctions(const Module& primary, const Config& config) { std::set<Name> primaryFuncs, secondaryFuncs; for (auto& func : primary.functions) { - if (func->imported() || config.primaryFuncs.count(func->name)) { + // In JSPI mode exported functions cannot be moved to the secondary + // module since that would make them async when they may not have the JSPI + // wrapper. Exported JSPI functions can still benefit from splitting though + // since only the JSPI wrapper stub will remain in the primary module. + if (func->imported() || config.primaryFuncs.count(func->name) || + (config.jspi && ExportUtils::isExported(primary, *func))) { primaryFuncs.insert(func->name); } else { assert(func->name != primary.start && "The start function must be kept"); @@ -409,6 +442,20 @@ void ModuleSplitter::thunkExportedSecondaryFunctions() { } } +Expression* ModuleSplitter::maybeLoadSecondary(Builder& builder, + Expression* callIndirect) { + if (!config.jspi) { + return callIndirect; + } + // Check if the secondary module is loaded and if it isn't, call the + // function to load it. + auto* loadSecondary = builder.makeIf( + builder.makeUnary(EqZInt32, + builder.makeGlobalGet(LOAD_SECONDARY_STATUS, Type::i32)), + builder.makeCall(internalLoadSecondaryModule, {}, Type::none)); + return builder.makeSequence(loadSecondary, callIndirect); +} + void ModuleSplitter::indirectCallsToSecondaryFunctions() { // Update direct calls of secondary functions to be indirect calls of their // corresponding table indices instead. @@ -425,12 +472,14 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { } auto* func = parent.secondary.getFunction(curr->target); auto tableSlot = parent.tableManager.getSlot(curr->target, func->type); - replaceCurrent( + + replaceCurrent(parent.maybeLoadSecondary( + builder, builder.makeCallIndirect(tableSlot.tableName, tableSlot.makeExpr(parent.primary), curr->operands, func->type, - curr->isReturn)); + curr->isReturn))); } void visitRefFunc(RefFunc* curr) { assert(false && "TODO: handle ref.func as well"); diff --git a/src/ir/module-splitting.h b/src/ir/module-splitting.h index 2cc6760c0..dc5bb1998 100644 --- a/src/ir/module-splitting.h +++ b/src/ir/module-splitting.h @@ -44,6 +44,8 @@ namespace wasm::ModuleSplitting { +static const Name LOAD_SECONDARY_MODULE("__load_secondary_module"); + struct Config { // The set of functions to keep in the primary module. All others are split // out into the new secondary module. Must include the start function if it @@ -64,6 +66,9 @@ struct Config { // false, the original function names will be used (after `newExportPrefix`) // as the new export names. bool minimizeNewExportNames = false; + // When JSPI support is enabled the secondary module loading is handled by an + // imported function. + bool jspi = false; }; struct Results { diff --git a/src/passes/JSPI.cpp b/src/passes/JSPI.cpp index aa1546984..01532bb38 100644 --- a/src/passes/JSPI.cpp +++ b/src/passes/JSPI.cpp @@ -18,6 +18,7 @@ #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" @@ -49,6 +50,12 @@ // Wrap each export in the comma-separated list. Similar to jspi-imports, // wildcards and separate files are supported. // +// --pass-arg=jspi-split-module +// +// Enables integration with wasm-split. A JSPI'ed function named +// `__load_secondary_module` will be injected that is used by wasm-split to +// load a secondary module. +// namespace wasm { @@ -87,6 +94,22 @@ struct JSPI : public Pass { options.getArgumentOrDefault("jspi-exports", ""))); String::Split listedExports(stateChangingExports, ","); + bool wasmSplit = options.hasArgument("jspi-split-module"); + if (wasmSplit) { + // Make an import for the load secondary module function so a JSPI wrapper + // version will be created. + auto import = + Builder::makeFunction(ModuleSplitting::LOAD_SECONDARY_MODULE, + Signature(Type::none, Type::none), + {}); + import->module = ENV; + import->base = ModuleSplitting::LOAD_SECONDARY_MODULE; + module->addFunction(std::move(import)); + listedImports.push_back( + ENV.toString() + "." + + ModuleSplitting::LOAD_SECONDARY_MODULE.toString()); + } + // 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"); @@ -128,7 +151,7 @@ struct JSPI : public Pass { if (im->imported() && canChangeState(getFullFunctionName(im->module, im->base), listedImports)) { - makeWrapperForImport(im, module, suspender); + makeWrapperForImport(im, module, suspender, wasmSplit); } } } @@ -181,7 +204,10 @@ private: return module->addFunction(std::move(wrapperFunc))->name; } - void makeWrapperForImport(Function* im, Module* module, Name suspender) { + void makeWrapperForImport(Function* im, + Module* module, + Name suspender, + bool wasmSplit) { Builder builder(*module); auto wrapperIm = make_unique<Function>(); wrapperIm->name = Names::getValidFunctionName( @@ -234,6 +260,15 @@ private: stub->body = block; wrapperIm->type = Signature(Type(params), call->type); + if (wasmSplit && im->name == ModuleSplitting::LOAD_SECONDARY_MODULE) { + // In non-debug builds the name of the JSPI wrapper function for loading + // the secondary module will be removed. Create an export of it so that + // wasm-split can find it. + module->addExport( + builder.makeExport(ModuleSplitting::LOAD_SECONDARY_MODULE, + ModuleSplitting::LOAD_SECONDARY_MODULE, + ExternalKind::Function)); + } module->removeFunction(im->name); module->addFunction(std::move(stub)); module->addFunction(std::move(wrapperIm)); diff --git a/src/tools/wasm-split/split-options.cpp b/src/tools/wasm-split/split-options.cpp index d077c70bc..b166b575c 100644 --- a/src/tools/wasm-split/split-options.cpp +++ b/src/tools/wasm-split/split-options.cpp @@ -205,15 +205,14 @@ WasmSplitOptions::WasmSplitOptions() [&](Options* o, const std::string& argument) { placeholderNamespace = argument; }) - .add( - "--asyncify", - "", - "Transform the module to support unwinding the stack from placeholder " - "functions and rewinding it once the secondary module has been loaded.", - WasmSplitOption, - {Mode::Split}, - Options::Arguments::Zero, - [&](Options* o, const std::string& argument) { asyncify = true; }) + .add("--jspi", + "", + "Transform the module to support asynchronously loading the secondary " + "module before any placeholder functions have been called.", + WasmSplitOption, + {Mode::Split}, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { jspi = true; }) .add( "--export-prefix", "", diff --git a/src/tools/wasm-split/split-options.h b/src/tools/wasm-split/split-options.h index 5aba0ba39..6aa5b0011 100644 --- a/src/tools/wasm-split/split-options.h +++ b/src/tools/wasm-split/split-options.h @@ -46,7 +46,7 @@ struct WasmSplitOptions : ToolOptions { bool emitBinary = true; bool symbolMap = false; bool placeholderMap = false; - bool asyncify = false; + bool jspi = false; // TODO: Remove this. See the comment in wasm-binary.h. bool emitModuleNames = false; diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index 6a042679f..bea3ddce7 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -248,6 +248,11 @@ void splitModule(const WasmSplitOptions& options) { } } + if (options.jspi) { + // The load secondary module function must be kept in the main module. + keepFuncs.insert(ModuleSplitting::LOAD_SECONDARY_MODULE); + } + if (!options.quiet && keepFuncs.size() == 0) { std::cerr << "warning: not keeping any functions in the primary module\n"; } @@ -300,22 +305,13 @@ void splitModule(const WasmSplitOptions& options) { config.newExportPrefix = options.exportPrefix; } config.minimizeNewExportNames = !options.passOptions.debugInfo; + config.jspi = options.jspi; auto splitResults = ModuleSplitting::splitFunctions(wasm, config); auto& secondary = splitResults.secondary; adjustTableSize(wasm, options.initialTableSize); adjustTableSize(*secondary, options.initialTableSize); - // Run asyncify on the primary module - if (options.asyncify) { - PassOptions passOptions; - passOptions.optimizeLevel = 1; - passOptions.arguments.insert({"asyncify-ignore-imports", ""}); - PassRunner runner(&wasm, passOptions); - runner.add("asyncify"); - runner.run(); - } - if (options.symbolMap) { writeSymbolMap(wasm, options.primaryOutput + ".symbols"); writeSymbolMap(*secondary, options.secondaryOutput + ".symbols"); diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 118117d62..e0cdf3ca4 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -66,10 +66,10 @@ ;; CHECK-NEXT: import placeholder functions into the ;; CHECK-NEXT: primary module. ;; CHECK-NEXT: -;; CHECK-NEXT: --asyncify [split] Transform the module to support -;; CHECK-NEXT: unwinding the stack from placeholder -;; CHECK-NEXT: functions and rewinding it once the -;; CHECK-NEXT: secondary module has been loaded. +;; CHECK-NEXT: --jspi [split] Transform the module to support +;; CHECK-NEXT: asynchronously loading the secondary +;; CHECK-NEXT: module before any placeholder functions +;; CHECK-NEXT: have been called. ;; CHECK-NEXT: ;; CHECK-NEXT: --export-prefix [split] An identifying prefix to prepend ;; CHECK-NEXT: to new export names created by module diff --git a/test/lit/passes/jspi-split-module.wast b/test/lit/passes/jspi-split-module.wast new file mode 100644 index 000000000..d3484bacf --- /dev/null +++ b/test/lit/passes/jspi-split-module.wast @@ -0,0 +1,30 @@ +;; 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-split-module -all -S -o - | filecheck %s + +;; The following should be generated besides the usual JSPI wasm: +;; - function import +;; - JSPI'ed version of the import +;; - export of the above +(module) +;; CHECK: (type $none_=>_none (func)) + +;; CHECK: (type $externref_=>_none (func (param externref))) + +;; CHECK: (import "env" "__load_secondary_module" (func $import$__load_secondary_module (param externref))) + +;; CHECK: (global $suspender (mut externref) (ref.null noextern)) + +;; CHECK: (export "__load_secondary_module" (func $__load_secondary_module)) + +;; CHECK: (func $__load_secondary_module (type $none_=>_none) +;; CHECK-NEXT: (local $0 externref) +;; CHECK-NEXT: (local.set $0 +;; CHECK-NEXT: (global.get $suspender) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (call $import$__load_secondary_module +;; CHECK-NEXT: (global.get $suspender) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $suspender +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) diff --git a/test/lit/wasm-split/asyncify.wast b/test/lit/wasm-split/asyncify.wast deleted file mode 100644 index 4894f58a7..000000000 --- a/test/lit/wasm-split/asyncify.wast +++ /dev/null @@ -1,174 +0,0 @@ -;; RUN: wasm-split %s --export-prefix='%' -g -o1 %t.1.wasm -o2 %t.2.wasm --keep-funcs=foo --asyncify -;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY -;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY - -;; Check that the --asyncify option instruments the primary module but not the -;; secondary module. - -(module - (func $foo (param i32) (result i32) - (call $bar (i32.const 0)) - ) - (func $bar (param i32) (result i32) - (call $foo (i32.const 1)) - ) -) - -;; PRIMARY: (module -;; PRIMARY-NEXT: (type $i32_=>_i32 (func (param i32) (result i32))) -;; PRIMARY-NEXT: (type $i32_=>_none (func (param i32))) -;; PRIMARY-NEXT: (type $none_=>_none (func)) -;; PRIMARY-NEXT: (type $none_=>_i32 (func (result i32))) -;; PRIMARY-NEXT: (import "placeholder" "0" (func $placeholder_0 (param i32) (result i32))) -;; PRIMARY-NEXT: (global $global$0 (mut i32) (i32.const 0)) -;; PRIMARY-NEXT: (global $global$1 (mut i32) (i32.const 0)) -;; PRIMARY-NEXT: (memory $0 1 1) -;; PRIMARY-NEXT: (table $0 1 funcref) -;; PRIMARY-NEXT: (elem (i32.const 0) $placeholder_0) -;; PRIMARY-NEXT: (export "%foo" (func $foo)) -;; PRIMARY-NEXT: (export "%table" (table $0)) -;; PRIMARY-NEXT: (export "asyncify_start_unwind" (func $asyncify_start_unwind)) -;; PRIMARY-NEXT: (export "asyncify_stop_unwind" (func $asyncify_stop_unwind)) -;; PRIMARY-NEXT: (export "asyncify_start_rewind" (func $asyncify_start_rewind)) -;; PRIMARY-NEXT: (export "asyncify_stop_rewind" (func $asyncify_stop_rewind)) -;; PRIMARY-NEXT: (export "asyncify_get_state" (func $asyncify_get_state)) -;; PRIMARY-NEXT: (func $foo (param $0 i32) (result i32) -;; PRIMARY-NEXT: (local $1 i32) -;; PRIMARY-NEXT: (if -;; PRIMARY-NEXT: (i32.eq -;; PRIMARY-NEXT: (global.get $global$0) -;; PRIMARY-NEXT: (i32.const 2) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (block -;; PRIMARY-NEXT: (i32.store -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: (i32.sub -;; PRIMARY-NEXT: (i32.load -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.const 4) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (local.set $0 -;; PRIMARY-NEXT: (i32.load -;; PRIMARY-NEXT: (i32.load -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (local.set $1 -;; PRIMARY-NEXT: (block $label$2 (result i32) -;; PRIMARY-NEXT: (if -;; PRIMARY-NEXT: (i32.eqz -;; PRIMARY-NEXT: (select -;; PRIMARY-NEXT: (if (result i32) -;; PRIMARY-NEXT: (i32.eq -;; PRIMARY-NEXT: (global.get $global$0) -;; PRIMARY-NEXT: (i32.const 2) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (block (result i32) -;; PRIMARY-NEXT: (i32.store -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: (i32.sub -;; PRIMARY-NEXT: (i32.load -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.const 4) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.load -;; PRIMARY-NEXT: (i32.load -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (local.get $1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.const 0) -;; PRIMARY-NEXT: (global.get $global$0) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (block -;; PRIMARY-NEXT: (local.set $1 -;; PRIMARY-NEXT: (call_indirect (type $i32_=>_i32) -;; PRIMARY-NEXT: (i32.const 0) -;; PRIMARY-NEXT: (i32.const 0) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (drop -;; PRIMARY-NEXT: (br_if $label$2 -;; PRIMARY-NEXT: (i32.const 0) -;; PRIMARY-NEXT: (i32.eq -;; PRIMARY-NEXT: (global.get $global$0) -;; PRIMARY-NEXT: (i32.const 1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (local.set $0 -;; PRIMARY-NEXT: (local.get $1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (if -;; PRIMARY-NEXT: (i32.eqz -;; PRIMARY-NEXT: (global.get $global$0) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (return -;; PRIMARY-NEXT: (local.get $0) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (unreachable) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.store -;; PRIMARY-NEXT: (i32.load -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (local.get $1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.store -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: (i32.add -;; PRIMARY-NEXT: (i32.load -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.const 4) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.store -;; PRIMARY-NEXT: (i32.load -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (local.get $0) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.store -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: (i32.add -;; PRIMARY-NEXT: (i32.load -;; PRIMARY-NEXT: (global.get $global$1) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.const 4) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: (i32.const 0) -;; PRIMARY-NEXT: ) -;; PRIMARY: (func $asyncify_start_unwind (param $0 i32) -;; PRIMARY: (func $asyncify_stop_unwind -;; PRIMARY: (func $asyncify_start_rewind (param $0 i32) -;; PRIMARY: (func $asyncify_stop_rewind -;; PRIMARY: (func $asyncify_get_state (result i32) -;; PRIMARY: ) - -;; SECONDARY: (module -;; SECONDARY-NEXT: (type $i32_=>_i32 (func (param i32) (result i32))) -;; SECONDARY-NEXT: (import "primary" "%table" (table $timport$0 1 funcref)) -;; SECONDARY-NEXT: (import "primary" "%foo" (func $foo (param i32) (result i32))) -;; SECONDARY-NEXT: (elem (i32.const 0) $bar) -;; SECONDARY-NEXT: (func $bar (param $0 i32) (result i32) -;; SECONDARY-NEXT: (call $foo -;; SECONDARY-NEXT: (i32.const 1) -;; SECONDARY-NEXT: ) -;; SECONDARY-NEXT: ) -;; SECONDARY-NEXT: ) diff --git a/test/lit/wasm-split/jspi-secondary-export.wast b/test/lit/wasm-split/jspi-secondary-export.wast new file mode 100644 index 000000000..6d6e54e15 --- /dev/null +++ b/test/lit/wasm-split/jspi-secondary-export.wast @@ -0,0 +1,83 @@ +;; 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-exports@foo --pass-arg=jspi-split-module -all -S -o %t.jspi.wast +;; RUN: wasm-split %t.jspi.wast --export-prefix='%' -g -o1 %t.1.wasm -o2 %t.2.wasm --jspi --enable-reference-types +;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY +;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY + +;; Ensure exported functions are not moved to secondary module when JSPI is +;; enabled. + +(module + ;; PRIMARY: (type $i32_=>_i32 (func (param i32) (result i32))) + + ;; PRIMARY: (type $externref_=>_none (func (param externref))) + + ;; PRIMARY: (type $externref_i32_=>_i32 (func (param externref i32) (result i32))) + + ;; PRIMARY: (type $none_=>_none (func)) + + ;; PRIMARY: (import "env" "__load_secondary_module" (func $import$__load_secondary_module (param externref))) + + ;; PRIMARY: (import "placeholder" "0" (func $placeholder_0 (param i32) (result i32))) + + ;; PRIMARY: (global $suspender (mut externref) (ref.null noextern)) + + ;; PRIMARY: (global $global$1 (mut i32) (i32.const 0)) + + ;; PRIMARY: (table $0 1 funcref) + + ;; PRIMARY: (elem (i32.const 0) $placeholder_0) + + ;; PRIMARY: (export "foo" (func $export$foo)) + (export "foo" (func $foo)) + ;; SECONDARY: (type $i32_=>_i32 (func (param i32) (result i32))) + + ;; SECONDARY: (import "primary" "%table" (table $timport$0 1 funcref)) + + ;; SECONDARY: (import "primary" "%global" (global $suspender (mut externref))) + + ;; SECONDARY: (import "primary" "load_secondary_module_status" (global $gimport$1 (mut i32))) + + ;; SECONDARY: (elem (i32.const 0) $foo) + + ;; SECONDARY: (func $foo (param $0 i32) (result i32) + ;; SECONDARY-NEXT: (i32.const 0) + ;; SECONDARY-NEXT: ) + (func $foo (param i32) (result i32) + (i32.const 0) + ) +) +;; PRIMARY: (export "load_secondary_module_status" (global $global$1)) + +;; PRIMARY: (export "%table" (table $0)) + +;; PRIMARY: (export "%global" (global $suspender)) + +;; PRIMARY: (func $export$foo (param $susp externref) (param $0 i32) (result i32) +;; PRIMARY-NEXT: (global.set $suspender +;; PRIMARY-NEXT: (local.get $susp) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: (if +;; PRIMARY-NEXT: (i32.eqz +;; PRIMARY-NEXT: (global.get $global$1) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: (call $__load_secondary_module) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: (call_indirect (type $i32_=>_i32) +;; PRIMARY-NEXT: (local.get $0) +;; PRIMARY-NEXT: (i32.const 0) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) + +;; PRIMARY: (func $__load_secondary_module +;; PRIMARY-NEXT: (local $0 externref) +;; PRIMARY-NEXT: (local.set $0 +;; PRIMARY-NEXT: (global.get $suspender) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: (call $import$__load_secondary_module +;; PRIMARY-NEXT: (global.get $suspender) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: (global.set $suspender +;; PRIMARY-NEXT: (local.get $0) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) diff --git a/test/lit/wasm-split/jspi.wast b/test/lit/wasm-split/jspi.wast new file mode 100644 index 000000000..c202b9cb1 --- /dev/null +++ b/test/lit/wasm-split/jspi.wast @@ -0,0 +1,98 @@ +;; 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-exports@foo --pass-arg=jspi-split-module -all -S -o %t.jspi.wast +;; RUN: wasm-split %t.jspi.wast --export-prefix='%' -g -o1 %t.1.wasm -o2 %t.2.wasm --keep-funcs=foo --jspi --enable-reference-types +;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY +;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY + +;; Check that the call to bar first checks if the secondary module is loaded and +;; that bar is moved to the secondary module. + +(module + ;; PRIMARY: (type $i32_=>_i32 (func (param i32) (result i32))) + + ;; PRIMARY: (type $externref_=>_none (func (param externref))) + + ;; PRIMARY: (type $externref_i32_=>_i32 (func (param externref i32) (result i32))) + + ;; PRIMARY: (type $none_=>_none (func)) + + ;; PRIMARY: (import "env" "__load_secondary_module" (func $import$__load_secondary_module (param externref))) + + ;; PRIMARY: (import "placeholder" "0" (func $placeholder_0 (param i32) (result i32))) + + ;; PRIMARY: (global $suspender (mut externref) (ref.null noextern)) + + ;; PRIMARY: (global $global$1 (mut i32) (i32.const 0)) + + ;; PRIMARY: (table $0 1 funcref) + + ;; PRIMARY: (elem (i32.const 0) $placeholder_0) + + ;; PRIMARY: (export "foo" (func $export$foo)) + (export "foo" (func $foo)) + ;; PRIMARY: (export "load_secondary_module_status" (global $global$1)) + + ;; PRIMARY: (export "%foo" (func $foo)) + + ;; PRIMARY: (export "%table" (table $0)) + + ;; PRIMARY: (export "%global" (global $suspender)) + + ;; PRIMARY: (func $foo (param $0 i32) (result i32) + ;; PRIMARY-NEXT: (if + ;; PRIMARY-NEXT: (i32.eqz + ;; PRIMARY-NEXT: (global.get $global$1) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: (call $__load_secondary_module) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: (call_indirect (type $i32_=>_i32) + ;; PRIMARY-NEXT: (i32.const 0) + ;; PRIMARY-NEXT: (i32.const 0) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: ) + (func $foo (param i32) (result i32) + (call $bar (i32.const 0)) + ) + ;; SECONDARY: (type $i32_=>_i32 (func (param i32) (result i32))) + + ;; SECONDARY: (import "primary" "%table" (table $timport$0 1 funcref)) + + ;; SECONDARY: (import "primary" "%global" (global $suspender (mut externref))) + + ;; SECONDARY: (import "primary" "load_secondary_module_status" (global $gimport$1 (mut i32))) + + ;; SECONDARY: (import "primary" "%foo" (func $foo (param i32) (result i32))) + + ;; SECONDARY: (elem (i32.const 0) $bar) + + ;; SECONDARY: (func $bar (param $0 i32) (result i32) + ;; SECONDARY-NEXT: (call $foo + ;; SECONDARY-NEXT: (i32.const 1) + ;; SECONDARY-NEXT: ) + ;; SECONDARY-NEXT: ) + (func $bar (param i32) (result i32) + (call $foo (i32.const 1)) + ) +) + +;; PRIMARY: (func $export$foo (param $susp externref) (param $0 i32) (result i32) +;; PRIMARY-NEXT: (global.set $suspender +;; PRIMARY-NEXT: (local.get $susp) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: (call $foo +;; PRIMARY-NEXT: (local.get $0) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) + +;; PRIMARY: (func $__load_secondary_module +;; PRIMARY-NEXT: (local $0 externref) +;; PRIMARY-NEXT: (local.set $0 +;; PRIMARY-NEXT: (global.get $suspender) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: (call $import$__load_secondary_module +;; PRIMARY-NEXT: (global.get $suspender) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: (global.set $suspender +;; PRIMARY-NEXT: (local.get $0) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) |