summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ir/export-utils.h10
-rw-r--r--src/ir/module-splitting.cpp55
-rw-r--r--src/ir/module-splitting.h5
-rw-r--r--src/passes/JSPI.cpp39
-rw-r--r--src/tools/wasm-split/split-options.cpp17
-rw-r--r--src/tools/wasm-split/split-options.h2
-rw-r--r--src/tools/wasm-split/wasm-split.cpp16
7 files changed, 119 insertions, 25 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");