6969
7070#include " ir/module-splitting.h"
7171#include " ir/element-utils.h"
72+ #include " ir/export-utils.h"
7273#include " ir/manipulation.h"
7374#include " ir/module-utils.h"
7475#include " ir/names.h"
@@ -80,6 +81,8 @@ namespace wasm::ModuleSplitting {
8081
8182namespace {
8283
84+ static const Name LOAD_SECONDARY_STATUS = " load_secondary_module_status" ;
85+
8386template <class F > void forEachElement (Module& module , F f) {
8487 ModuleUtils::iterActiveElementSegments (module , [&](ElementSegment* segment) {
8588 Name base = " " ;
@@ -273,6 +276,9 @@ struct ModuleSplitter {
273276 // Map placeholder indices to the names of the functions they replace.
274277 std::map<size_t , Name> placeholderMap;
275278
279+ // Internal name of the LOAD_SECONDARY_MODULE function.
280+ Name internalLoadSecondaryModule;
281+
276282 // Initialization helpers
277283 static std::unique_ptr<Module> initSecondary (const Module& primary);
278284 static std::pair<std::set<Name>, std::set<Name>>
@@ -281,8 +287,10 @@ struct ModuleSplitter {
281287
282288 // Other helpers
283289 void exportImportFunction (Name func);
290+ Expression* maybeLoadSecondary (Builder& builder, Expression* callIndirect);
284291
285292 // Main splitting steps
293+ void setupJSPI ();
286294 void moveSecondaryFunctions ();
287295 void thunkExportedSecondaryFunctions ();
288296 void indirectCallsToSecondaryFunctions ();
@@ -297,6 +305,9 @@ struct ModuleSplitter {
297305 primaryFuncs(classifiedFuncs.first),
298306 secondaryFuncs(classifiedFuncs.second), tableManager(primary),
299307 exportedPrimaryFuncs(initExportedPrimaryFuncs(primary)) {
308+ if (config.jspi ) {
309+ setupJSPI ();
310+ }
300311 moveSecondaryFunctions ();
301312 thunkExportedSecondaryFunctions ();
302313 indirectCallsToSecondaryFunctions ();
@@ -306,6 +317,23 @@ struct ModuleSplitter {
306317 }
307318};
308319
320+ void ModuleSplitter::setupJSPI () {
321+ assert (primary.getExportOrNull (LOAD_SECONDARY_MODULE) &&
322+ " The load secondary module function must exist" );
323+ // Remove the exported LOAD_SECONDARY_MODULE function since it's only needed
324+ // internally.
325+ internalLoadSecondaryModule = primary.getExport (LOAD_SECONDARY_MODULE)->value ;
326+ primary.removeExport (LOAD_SECONDARY_MODULE);
327+ Builder builder (primary);
328+ // Add a global to track whether the secondary module has been loaded yet.
329+ primary.addGlobal (builder.makeGlobal (LOAD_SECONDARY_STATUS,
330+ Type::i32 ,
331+ builder.makeConst (int32_t (0 )),
332+ Builder::Mutable));
333+ primary.addExport (builder.makeExport (
334+ LOAD_SECONDARY_STATUS, LOAD_SECONDARY_STATUS, ExternalKind::Global));
335+ }
336+
309337std::unique_ptr<Module> ModuleSplitter::initSecondary (const Module& primary) {
310338 // Create the secondary module and copy trivial properties.
311339 auto secondary = std::make_unique<Module>();
@@ -318,7 +346,12 @@ std::pair<std::set<Name>, std::set<Name>>
318346ModuleSplitter::classifyFunctions (const Module& primary, const Config& config) {
319347 std::set<Name> primaryFuncs, secondaryFuncs;
320348 for (auto & func : primary.functions ) {
321- if (func->imported () || config.primaryFuncs .count (func->name )) {
349+ // In JSPI mode exported functions cannot be moved to the secondary
350+ // module since that would make them async when they may not have the JSPI
351+ // wrapper. Exported JSPI functions can still benefit from splitting though
352+ // since only the JSPI wrapper stub will remain in the primary module.
353+ if (func->imported () || config.primaryFuncs .count (func->name ) ||
354+ (config.jspi && ExportUtils::isExported (primary, *func))) {
322355 primaryFuncs.insert (func->name );
323356 } else {
324357 assert (func->name != primary.start && " The start function must be kept" );
@@ -409,6 +442,20 @@ void ModuleSplitter::thunkExportedSecondaryFunctions() {
409442 }
410443}
411444
445+ Expression* ModuleSplitter::maybeLoadSecondary (Builder& builder,
446+ Expression* callIndirect) {
447+ if (!config.jspi ) {
448+ return callIndirect;
449+ }
450+ // Check if the secondary module is loaded and if it isn't, call the
451+ // function to load it.
452+ auto * loadSecondary = builder.makeIf (
453+ builder.makeUnary (EqZInt32,
454+ builder.makeGlobalGet (LOAD_SECONDARY_STATUS, Type::i32 )),
455+ builder.makeCall (internalLoadSecondaryModule, {}, Type::none));
456+ return builder.makeSequence (loadSecondary, callIndirect);
457+ }
458+
412459void ModuleSplitter::indirectCallsToSecondaryFunctions () {
413460 // Update direct calls of secondary functions to be indirect calls of their
414461 // corresponding table indices instead.
@@ -425,12 +472,14 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() {
425472 }
426473 auto * func = parent.secondary .getFunction (curr->target );
427474 auto tableSlot = parent.tableManager .getSlot (curr->target , func->type );
428- replaceCurrent (
475+
476+ replaceCurrent (parent.maybeLoadSecondary (
477+ builder,
429478 builder.makeCallIndirect (tableSlot.tableName ,
430479 tableSlot.makeExpr (parent.primary ),
431480 curr->operands ,
432481 func->type ,
433- curr->isReturn ));
482+ curr->isReturn ))) ;
434483 }
435484 void visitRefFunc (RefFunc* curr) {
436485 assert (false && " TODO: handle ref.func as well" );
0 commit comments