diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 12aaa9512f8..43ad6ccde58 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -16,17 +16,19 @@ // The process of module splitting involves these steps: // -// 1. Create the new secondary module. +// 1. Create the new secondary modules. // -// 2. Move the deferred functions from the primary to the secondary module. +// 2. Move the deferred functions from the primary to each of the secondary +// modules. // // 3. For any secondary function exported from the primary module, export in // its place a trampoline function that makes an indirect call to its // placeholder function (and eventually to the original secondary // function), allocating a new table slot for the placeholder if necessary. // -// 4. Replace all references to secondary functions in the primary module's -// table segments with references to imported placeholder functions. +// 4. Replace all references to each secondary module's functions in the +// primary module's and each other secondary module's table segments with +// references to imported placeholder functions. // // 5. Rewrite direct calls from primary functions to secondary functions to be // indirect calls to their placeholder functions (and eventually to their @@ -35,24 +37,24 @@ // // 6. For each primary function directly called from a secondary function, // export the primary function if it is not already exported and import it -// into the secondary module. +// into each secondary module using it. // -// 7. Create new active table segments in the secondary module that will -// replace all the placeholder function references in the table with -// references to their corresponding secondary functions upon +// 7. For each secondary module, create new active table segments in the +// module that will replace all the placeholder function references in the +// table with references to their corresponding secondary functions upon // instantiation. // // 8. Export globals, tags, tables, and memories from the primary module and -// import them in the secondary module. +// import them in the secondary modules. // -// 9. Run RemoveUnusedModuleElements pass on the secondary module in order to +// 9. Run RemoveUnusedModuleElements pass on the secondary modules in order to // remove unused imports. // // Functions can be used or referenced three ways in a WebAssembly module: they // can be exported, called, or referenced with ref.func. The above procedure // introduces a layer of indirection to each of those mechanisms that removes // all references to secondary functions from the primary module but restores -// the original program's semantics once the secondary module is instantiated. +// the original program's semantics once the secondary modules are instantiated. // // The code as currently written makes a couple assumptions about the module // that is being split: @@ -290,14 +292,13 @@ TableSlotManager::Slot TableSlotManager::getSlot(Name func, HeapType type) { struct ModuleSplitter { const Config& config; - std::unique_ptr secondaryPtr; + std::vector> secondaries; Module& primary; - Module& secondary; - const std::pair, std::set> classifiedFuncs; - const std::set& primaryFuncs; - const std::set& secondaryFuncs; + std::unordered_set primaryFuncs; + std::unordered_set allSecondaryFuncs; + std::unordered_map funcToSecondaryIndex; TableSlotManager tableManager; @@ -305,7 +306,7 @@ struct ModuleSplitter { // Map from internal function names to (one of) their corresponding export // names. - std::map exportedPrimaryFuncs; + std::unordered_map exportedPrimaryFuncs; // For each table, map placeholder indices to the names of the functions they // replace. @@ -319,33 +320,30 @@ struct ModuleSplitter { // Initialization helpers static std::unique_ptr initSecondary(const Module& primary); - static std::pair, std::set> - classifyFunctions(Module& primary, const Config& config); - static std::map initExportedPrimaryFuncs(const Module& primary); + static std::unordered_map + initExportedPrimaryFuncs(const Module& primary); // Other helpers - void exportImportFunction(Name func); + void exportImportFunction(Name func, const std::set& modules); Expression* maybeLoadSecondary(Builder& builder, Expression* callIndirect); Name getTrampoline(Name funcName); // Main splitting steps + void classifyFunctions(); void setupJSPI(); void moveSecondaryFunctions(); void thunkExportedSecondaryFunctions(); - void indirectCallsToSecondaryFunctions(); void indirectReferencesToSecondaryFunctions(); + void indirectCallsToSecondaryFunctions(); void exportImportCalledPrimaryFunctions(); void setupTablePatching(); void shareImportableItems(); void removeUnusedSecondaryElements(); ModuleSplitter(Module& primary, const Config& config) - : config(config), secondaryPtr(initSecondary(primary)), primary(primary), - secondary(*secondaryPtr), - classifiedFuncs(classifyFunctions(primary, config)), - primaryFuncs(classifiedFuncs.first), - secondaryFuncs(classifiedFuncs.second), tableManager(primary), + : config(config), primary(primary), tableManager(primary), exportedPrimaryFuncs(initExportedPrimaryFuncs(primary)) { + classifyFunctions(); if (config.jspi) { setupJSPI(); } @@ -398,8 +396,7 @@ std::unique_ptr ModuleSplitter::initSecondary(const Module& primary) { return secondary; } -std::pair, std::set> -ModuleSplitter::classifyFunctions(Module& primary, const Config& config) { +void ModuleSplitter::classifyFunctions() { // Find functions that refer to data or element segments. These functions must // remain in the primary module because segments cannot be exported to be // accessed from the secondary module. @@ -457,27 +454,29 @@ ModuleSplitter::classifyFunctions(Module& primary, const Config& config) { segmentReferrers.insert(referrers.begin(), referrers.end()); } - std::set primaryFuncs, secondaryFuncs; + std::unordered_set configSecondaryFuncs; + for (auto& funcs : config.secondaryFuncs) { + configSecondaryFuncs.insert(funcs.begin(), funcs.end()); + } for (auto& func : primary.functions) { // 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.secondaryFuncs.count(func->name) || + if (func->imported() || !configSecondaryFuncs.count(func->name) || (config.jspi && ExportUtils::isExported(primary, *func)) || segmentReferrers.count(func->name)) { primaryFuncs.insert(func->name); } else { assert(func->name != primary.start && "The start function must be kept"); - secondaryFuncs.insert(func->name); + allSecondaryFuncs.insert(func->name); } } - return std::make_pair(std::move(primaryFuncs), std::move(secondaryFuncs)); } -std::map +std::unordered_map ModuleSplitter::initExportedPrimaryFuncs(const Module& primary) { - std::map functionExportNames; + std::unordered_map functionExportNames; for (auto& ex : primary.exports) { if (ex->kind == ExternalKind::Function) { functionExportNames[*ex->getInternalName()] = ex->name; @@ -486,7 +485,8 @@ ModuleSplitter::initExportedPrimaryFuncs(const Module& primary) { return functionExportNames; } -void ModuleSplitter::exportImportFunction(Name funcName) { +void ModuleSplitter::exportImportFunction(Name funcName, + const std::set& modules) { Name exportName; // If the function is already exported, use the existing export name. // Otherwise, create a new export for it. @@ -508,22 +508,31 @@ void ModuleSplitter::exportImportFunction(Name funcName) { } // Import the function if it is not already imported into the secondary // module. - if (secondary.getFunctionOrNull(funcName) == nullptr) { - auto primaryFunc = primary.getFunction(funcName); - auto func = Builder::makeFunction(funcName, primaryFunc->type, {}); - func->hasExplicitName = primaryFunc->hasExplicitName; - func->module = config.importNamespace; - func->base = exportName; - secondary.addFunction(std::move(func)); + for (auto* secondary : modules) { + if (secondary->getFunctionOrNull(funcName) == nullptr) { + auto primaryFunc = primary.getFunction(funcName); + auto func = Builder::makeFunction(funcName, primaryFunc->type, {}); + func->hasExplicitName = primaryFunc->hasExplicitName; + func->module = config.importNamespace; + func->base = exportName; + secondary->addFunction(std::move(func)); + } } } void ModuleSplitter::moveSecondaryFunctions() { - // Move the specified functions from the primary to the secondary module. - for (auto funcName : secondaryFuncs) { - auto* func = primary.getFunction(funcName); - ModuleUtils::copyFunction(func, secondary); - primary.removeFunction(funcName); + // Move the specified functions from the primary to the secondary modules. + for (auto& funcNames : config.secondaryFuncs) { + auto secondary = initSecondary(primary); + for (auto funcName : funcNames) { + if (allSecondaryFuncs.count(funcName)) { + auto* func = primary.getFunction(funcName); + ModuleUtils::copyFunction(func, *secondary); + primary.removeFunction(funcName); + funcToSecondaryIndex[funcName] = secondaries.size(); + } + } + secondaries.push_back(std::move(secondary)); } } @@ -534,6 +543,7 @@ Name ModuleSplitter::getTrampoline(Name funcName) { } Builder builder(primary); + Module& secondary = *secondaries.at(funcToSecondaryIndex.at(funcName)); auto* oldFunc = secondary.getFunction(funcName); auto trampoline = Names::getValidFunctionName( primary, std::string("trampoline_") + funcName.toString()); @@ -549,6 +559,7 @@ Name ModuleSplitter::getTrampoline(Name funcName) { auto func = builder.makeFunction(trampoline, oldFunc->type, {}, call); func->hasExplicitName = oldFunc->hasExplicitName; primary.addFunction(std::move(func)); + primaryFuncs.insert(trampoline); return trampoline; } @@ -561,7 +572,7 @@ void ModuleSplitter::thunkExportedSecondaryFunctions() { Builder builder(primary); for (auto& ex : primary.exports) { if (ex->kind != ExternalKind::Function || - !secondaryFuncs.count(*ex->getInternalName())) { + !allSecondaryFuncs.count(*ex->getInternalName())) { continue; } Name trampoline = getTrampoline(*ex->getInternalName()); @@ -598,12 +609,23 @@ void ModuleSplitter::indirectReferencesToSecondaryFunctions() { InsertOrderedMap> map; void visitRefFunc(RefFunc* curr) { - if (parent.secondaryFuncs.count(curr->func)) { + Module* currModule = getModule(); + // Add ref.func to the map when + // 1. ref.func's target func is in one of the secondary modules and + // 2. the current module is a different module (either the primary module + // or a different secondary module) + if (parent.allSecondaryFuncs.count(curr->func) && + (currModule == &parent.primary || + parent.secondaries.at(parent.funcToSecondaryIndex.at(curr->func)) + .get() != currModule)) { map[curr->func].push_back(curr); } } } gatherer(*this); gatherer.walkModule(&primary); + for (auto& secondaryPtr : secondaries) { + gatherer.walkModule(secondaryPtr.get()); + } // Ignore references to secondary functions that occur in the active segment // that will contain the imported placeholders. Indirect calls to table slots @@ -649,16 +671,24 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { // corresponding table indices instead. struct CallIndirector : public PostWalker { ModuleSplitter& parent; - Builder builder; - CallIndirector(ModuleSplitter& parent) - : parent(parent), builder(parent.primary) {} - // Avoid visitRefFunc on element segment data - void walkElementSegment(ElementSegment* segment) {} + CallIndirector(ModuleSplitter& parent) : parent(parent) {} void visitCall(Call* curr) { - if (!parent.secondaryFuncs.count(curr->target)) { + // Return if the call's target is not in one of the secondary module. + if (!parent.allSecondaryFuncs.count(curr->target)) { return; } - auto* func = parent.secondary.getFunction(curr->target); + // Return if the current module is the same module as the call's target, + // because we don't need a call_indirect within the same module. + Module* currModule = getModule(); + if (currModule != &parent.primary && + parent.secondaries.at(parent.funcToSecondaryIndex.at(curr->target)) + .get() == currModule) { + return; + } + + Builder builder(*getModule()); + Index secIndex = parent.funcToSecondaryIndex.at(curr->target); + auto* func = parent.secondaries.at(secIndex)->getFunction(curr->target); auto tableSlot = parent.tableManager.getSlot(curr->target, func->type); replaceCurrent(parent.maybeLoadSecondary( @@ -670,42 +700,52 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { curr->isReturn))); } }; - CallIndirector(*this).walkModule(&primary); + CallIndirector callIndirector(*this); + callIndirector.walkModule(&primary); + for (auto& secondaryPtr : secondaries) { + callIndirector.walkModule(secondaryPtr.get()); + } } void ModuleSplitter::exportImportCalledPrimaryFunctions() { - // Find primary functions called/referred in the secondary module. - ModuleUtils::ParallelFunctionAnalysis> callCollector( - secondary, [&](Function* func, std::vector& calledPrimaryFuncs) { - struct CallCollector : PostWalker { - const std::set& primaryFuncs; - std::vector& calledPrimaryFuncs; - CallCollector(const std::set& primaryFuncs, - std::vector& calledPrimaryFuncs) - : primaryFuncs(primaryFuncs), calledPrimaryFuncs(calledPrimaryFuncs) { - } - void visitCall(Call* curr) { - if (primaryFuncs.count(curr->target)) { - calledPrimaryFuncs.push_back(curr->target); + // Find primary functions called/referred to from the secondary modules. + using CalledPrimaryToModules = std::map>; + for (auto& secondaryPtr : secondaries) { + Module* secondary = secondaryPtr.get(); + ModuleUtils::ParallelFunctionAnalysis callCollector( + *secondary, + [&](Function* func, CalledPrimaryToModules& calledPrimaryToModules) { + struct CallCollector : PostWalker { + const std::unordered_set& primaryFuncs; + CalledPrimaryToModules& calledPrimaryToModules; + CallCollector(const std::unordered_set& primaryFuncs, + CalledPrimaryToModules& calledPrimaryToModules) + : primaryFuncs(primaryFuncs), + calledPrimaryToModules(calledPrimaryToModules) {} + void visitCall(Call* curr) { + if (primaryFuncs.count(curr->target)) { + calledPrimaryToModules[curr->target].insert(getModule()); + } } - } - void visitRefFunc(RefFunc* curr) { - if (primaryFuncs.count(curr->func)) { - calledPrimaryFuncs.push_back(curr->func); + void visitRefFunc(RefFunc* curr) { + if (primaryFuncs.count(curr->func)) { + calledPrimaryToModules[curr->func].insert(getModule()); + } } - } - }; - CallCollector(primaryFuncs, calledPrimaryFuncs).walkFunction(func); - }); - std::set calledPrimaryFuncs; - for (auto& entry : callCollector.map) { - auto& calledFuncs = entry.second; - calledPrimaryFuncs.insert(calledFuncs.begin(), calledFuncs.end()); - } + }; + CallCollector(primaryFuncs, calledPrimaryToModules) + .walkFunctionInModule(func, secondary); + }); + + CalledPrimaryToModules calledPrimaryToModules; + for (auto& [_, map] : callCollector.map) { + calledPrimaryToModules.merge(map); + } - // Ensure each called primary function is exported and imported - for (auto func : calledPrimaryFuncs) { - exportImportFunction(func); + // Ensure each called primary function is exported and imported + for (auto& [func, modules] : calledPrimaryToModules) { + exportImportFunction(func, modules); + } } } @@ -714,7 +754,7 @@ void ModuleSplitter::setupTablePatching() { return; } - std::map replacedElems; + std::map> moduleToReplacedElems; // Replace table references to secondary functions with an imported // placeholder that encodes the table index in its name: // `importNamespace`.`index`. @@ -724,14 +764,15 @@ void ModuleSplitter::setupTablePatching() { if (!ref) { return; } - if (!secondaryFuncs.count(ref->func)) { + if (!allSecondaryFuncs.count(ref->func)) { return; } assert(table == tableManager.activeTable->name); placeholderMap[table][index] = ref->func; + Module& secondary = *secondaries.at(funcToSecondaryIndex.at(ref->func)); auto* secondaryFunc = secondary.getFunction(ref->func); - replacedElems[index] = secondaryFunc; + moduleToReplacedElems[&secondary][index] = secondaryFunc; if (!config.usePlaceholders) { // TODO: This can create active element segments with lots of nulls. We // should optimize them like we do data segments with zeros. @@ -749,83 +790,90 @@ void ModuleSplitter::setupTablePatching() { primary.addFunction(std::move(placeholder)); }); - if (replacedElems.size() == 0) { + if (moduleToReplacedElems.size() == 0) { // No placeholders to patch out of the table return; } - auto secondaryTable = - ModuleUtils::copyTable(tableManager.activeTable, secondary); - - if (tableManager.activeBase.global.size()) { - assert(tableManager.activeTableSegments.size() == 1 && - "Unexpected number of segments with non-const base"); - assert(secondary.tables.size() == 1 && secondary.elementSegments.empty()); - // Since addition is not currently allowed in initializer expressions, we - // need to start the new secondary segment where the primary segment starts. - // The secondary segment will contain the same primary functions as the - // primary module except in positions where it needs to overwrite a - // placeholder function. All primary functions in the table therefore need - // to be imported into the second module. TODO: use better strategies here, - // such as using ref.func in the start function or standardizing addition in - // initializer expressions. - ElementSegment* primarySeg = tableManager.activeTableSegments.front(); - std::vector secondaryElems; - secondaryElems.reserve(primarySeg->data.size()); - - // Copy functions from the primary segment to the secondary segment, - // replacing placeholders and creating new exports and imports as necessary. - auto replacement = replacedElems.begin(); - for (Index i = 0; - i < primarySeg->data.size() && replacement != replacedElems.end(); - ++i) { - if (replacement->first == i) { - // primarySeg->data[i] is a placeholder, so use the secondary function. - auto* func = replacement->second; - auto* ref = Builder(secondary).makeRefFunc(func->name, func->type); - secondaryElems.push_back(ref); - ++replacement; - } else if (auto* get = primarySeg->data[i]->dynCast()) { - exportImportFunction(get->func); - auto* copied = - ExpressionManipulator::copy(primarySeg->data[i], secondary); - secondaryElems.push_back(copied); + for (auto& [secondaryPtr, replacedElems] : moduleToReplacedElems) { + Module& secondary = *secondaryPtr; + auto secondaryTable = + ModuleUtils::copyTable(tableManager.activeTable, secondary); + + if (tableManager.activeBase.global.size()) { + assert(tableManager.activeTableSegments.size() == 1 && + "Unexpected number of segments with non-const base"); + assert(secondary.tables.size() == 1 && secondary.elementSegments.empty()); + // Since addition is not currently allowed in initializer expressions, we + // need to start the new secondary segment where the primary segment + // starts. The secondary segment will contain the same primary functions + // as the primary module except in positions where it needs to overwrite a + // placeholder function. All primary functions in the table therefore need + // to be imported into the second module. TODO: use better strategies + // here, such as using ref.func in the start function or standardizing + // addition in initializer expressions. + ElementSegment* primarySeg = tableManager.activeTableSegments.front(); + std::vector secondaryElems; + secondaryElems.reserve(primarySeg->data.size()); + + // Copy functions from the primary segment to the secondary segment, + // replacing placeholders and creating new exports and imports as + // necessary. + auto replacement = replacedElems.begin(); + for (Index i = 0; + i < primarySeg->data.size() && replacement != replacedElems.end(); + ++i) { + if (replacement->first == i) { + // primarySeg->data[i] is a placeholder, so use the secondary + // function. + auto* func = replacement->second; + auto* ref = Builder(secondary).makeRefFunc(func->name, func->type); + secondaryElems.push_back(ref); + ++replacement; + } else if (auto* get = primarySeg->data[i]->dynCast()) { + exportImportFunction(get->func, {&secondary}); + auto* copied = + ExpressionManipulator::copy(primarySeg->data[i], secondary); + secondaryElems.push_back(copied); + } } - } - auto offset = ExpressionManipulator::copy(primarySeg->offset, secondary); - auto secondarySeg = std::make_unique( - secondaryTable->name, offset, secondaryTable->type, secondaryElems); - secondarySeg->setName(primarySeg->name, primarySeg->hasExplicitName); - secondary.addElementSegment(std::move(secondarySeg)); - return; - } + auto offset = ExpressionManipulator::copy(primarySeg->offset, secondary); + auto secondarySeg = std::make_unique( + secondaryTable->name, offset, secondaryTable->type, secondaryElems); + secondarySeg->setName(primarySeg->name, primarySeg->hasExplicitName); + secondary.addElementSegment(std::move(secondarySeg)); + return; + } - // Create active table segments in the secondary module to patch in the - // original functions when it is instantiated. - Index currBase = replacedElems.begin()->first; - std::vector currData; - auto finishSegment = [&]() { - auto* offset = Builder(secondary).makeConst( - Literal::makeFromInt32(currBase, secondaryTable->addressType)); - auto secondarySeg = std::make_unique( - secondaryTable->name, offset, secondaryTable->type, currData); - Name name = Names::getValidElementSegmentName( - secondary, Name::fromInt(secondary.elementSegments.size())); - secondarySeg->setName(name, false); - secondary.addElementSegment(std::move(secondarySeg)); - }; - for (auto curr = replacedElems.begin(); curr != replacedElems.end(); ++curr) { - if (curr->first != currBase + currData.size()) { + // Create active table segments in the secondary module to patch in the + // original functions when it is instantiated. + Index currBase = replacedElems.begin()->first; + std::vector currData; + auto finishSegment = [&]() { + auto* offset = Builder(secondary).makeConst( + Literal::makeFromInt32(currBase, secondaryTable->addressType)); + auto secondarySeg = std::make_unique( + secondaryTable->name, offset, secondaryTable->type, currData); + Name name = Names::getValidElementSegmentName( + secondary, Name::fromInt(secondary.elementSegments.size())); + secondarySeg->setName(name, false); + secondary.addElementSegment(std::move(secondarySeg)); + }; + for (auto curr = replacedElems.begin(); curr != replacedElems.end(); + ++curr) { + if (curr->first != currBase + currData.size()) { + finishSegment(); + currBase = curr->first; + currData.clear(); + } + auto* func = curr->second; + currData.push_back( + Builder(secondary).makeRefFunc(func->name, func->type)); + } + if (currData.size()) { finishSegment(); - currBase = curr->first; - currData.clear(); } - auto* func = curr->second; - currData.push_back(Builder(secondary).makeRefFunc(func->name, func->type)); - } - if (currData.size()) { - finishSegment(); } } @@ -860,47 +908,53 @@ void ModuleSplitter::shareImportableItems() { Name exportName = Names::getValidExportName(primary, baseName); primary.addExport(new Export(exportName, kind, primaryItem.name)); secondaryItem.base = exportName; + exports[std::make_pair(kind, primaryItem.name)] = exportName; } }; // TODO: Be more selective by only sharing global items that are actually used // in the secondary module, just like we do for functions. - for (auto& memory : primary.memories) { - auto secondaryMemory = ModuleUtils::copyMemory(memory.get(), secondary); - makeImportExport(*memory, *secondaryMemory, "memory", ExternalKind::Memory); - } + for (auto& secondaryPtr : secondaries) { + Module& secondary = *secondaryPtr; + for (auto& memory : primary.memories) { + auto secondaryMemory = ModuleUtils::copyMemory(memory.get(), secondary); + makeImportExport( + *memory, *secondaryMemory, "memory", ExternalKind::Memory); + } + + for (auto& table : primary.tables) { + auto secondaryTable = secondary.getTableOrNull(table->name); + if (!secondaryTable) { + secondaryTable = ModuleUtils::copyTable(table.get(), secondary); + } - for (auto& table : primary.tables) { - auto secondaryTable = secondary.getTableOrNull(table->name); - if (!secondaryTable) { - secondaryTable = ModuleUtils::copyTable(table.get(), secondary); + makeImportExport(*table, *secondaryTable, "table", ExternalKind::Table); } - makeImportExport(*table, *secondaryTable, "table", ExternalKind::Table); - } + for (auto& global : primary.globals) { + if (global->mutable_) { + assert(primary.features.hasMutableGlobals() && + "TODO: add wrapper functions for disallowed mutable globals"); + } + auto secondaryGlobal = std::make_unique(); + secondaryGlobal->type = global->type; + secondaryGlobal->mutable_ = global->mutable_; + secondaryGlobal->init = + global->init == nullptr + ? nullptr + : ExpressionManipulator::copy(global->init, secondary); + makeImportExport( + *global, *secondaryGlobal, "global", ExternalKind::Global); + secondary.addGlobal(std::move(secondaryGlobal)); + } - for (auto& global : primary.globals) { - if (global->mutable_) { - assert(primary.features.hasMutableGlobals() && - "TODO: add wrapper functions for disallowed mutable globals"); + for (auto& tag : primary.tags) { + auto secondaryTag = std::make_unique(); + secondaryTag->type = tag->type; + makeImportExport(*tag, *secondaryTag, "tag", ExternalKind::Tag); + secondary.addTag(std::move(secondaryTag)); } - auto secondaryGlobal = std::make_unique(); - secondaryGlobal->type = global->type; - secondaryGlobal->mutable_ = global->mutable_; - secondaryGlobal->init = - global->init == nullptr - ? nullptr - : ExpressionManipulator::copy(global->init, secondary); - makeImportExport(*global, *secondaryGlobal, "global", ExternalKind::Global); - secondary.addGlobal(std::move(secondaryGlobal)); - } - - for (auto& tag : primary.tags) { - auto secondaryTag = std::make_unique(); - secondaryTag->type = tag->type; - makeImportExport(*tag, *secondaryTag, "tag", ExternalKind::Tag); - secondary.addTag(std::move(secondaryTag)); } } @@ -908,16 +962,18 @@ void ModuleSplitter::removeUnusedSecondaryElements() { // TODO: It would be better to be more selective about only exporting and // importing those items that the secondary module needs. This would reduce // code size in the primary module as well. - PassRunner runner(&secondary); - runner.add("remove-unused-module-elements"); - runner.run(); + for (auto& secondaryPtr : secondaries) { + PassRunner runner(secondaryPtr.get()); + runner.add("remove-unused-module-elements"); + runner.run(); + } } } // anonymous namespace Results splitFunctions(Module& primary, const Config& config) { ModuleSplitter split(primary, config); - return {std::move(split.secondaryPtr), std::move(split.placeholderMap)}; + return {std::move(split.secondaries), std::move(split.placeholderMap)}; } } // namespace wasm::ModuleSplitting diff --git a/src/ir/module-splitting.h b/src/ir/module-splitting.h index b21a4f2bc8f..d6c525f7c3b 100644 --- a/src/ir/module-splitting.h +++ b/src/ir/module-splitting.h @@ -47,11 +47,12 @@ namespace wasm::ModuleSplitting { static const Name LOAD_SECONDARY_MODULE("__load_secondary_module"); struct Config { - // The set of functions to split into the secondary module. All others are - // kept in the primary module. Must not include the start function if it - // exists. May or may not include imported functions, which are always kept in - // the primary module regardless. - std::set secondaryFuncs; + // A vector of set of functions to split into that secondary. Each function + // set belongs to a single secondary module. All others are kept in the + // primary module. Must not include the start function if it exists. May or + // may not include imported functions, which are always kept in the primary + // module regardless. + std::vector> secondaryFuncs; // Whether to import placeholder functions into the primary module that will // be called when a secondary function is called before the secondary module // has been loaded. @@ -76,7 +77,7 @@ struct Config { }; struct Results { - std::unique_ptr secondary; + std::vector> secondaries; std::unordered_map> placeholderMap; }; diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index 669101fc4b5..1e17b1d5b3a 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -329,7 +329,7 @@ void splitModule(const WasmSplitOptions& options) { // Actually perform the splitting ModuleSplitting::Config config; - config.secondaryFuncs = std::move(splitFuncs); + config.secondaryFuncs.push_back(std::move(splitFuncs)); if (options.importNamespace.size()) { config.importNamespace = options.importNamespace; } @@ -343,7 +343,7 @@ void splitModule(const WasmSplitOptions& options) { config.minimizeNewExportNames = !options.passOptions.debugInfo; config.jspi = options.jspi; auto splitResults = ModuleSplitting::splitFunctions(wasm, config); - auto& secondary = splitResults.secondary; + auto& secondary = *splitResults.secondaries.begin(); adjustTableSize(wasm, options.initialTableSize); adjustTableSize(*secondary, options.initialTableSize, /*secondary=*/true); @@ -389,27 +389,37 @@ void multiSplitModule(const WasmSplitOptions& options) { Module wasm; parseInput(wasm, options); - // Map module names to the functions that should be in the modules. - std::map> moduleFuncs; // The module for which we are currently parsing a set of functions. Name currModule; // The set of functions we are currently inserting into. - std::unordered_set* currFuncs = nullptr; + std::set* currFuncs = nullptr; // Map functions to their modules to ensure no function is assigned to // multiple modules. std::unordered_map funcModules; + ModuleSplitting::Config config; std::string line; bool newSection = true; + std::vector moduleNames; + std::unordered_set moduleNameSet; while (std::getline(manifest, line)) { if (line.empty()) { newSection = true; + if (currFuncs->empty() && !options.quiet) { + std::cerr << "warning: Module " << currModule << " will be empty\n"; + } continue; } Name name = WasmBinaryReader::escape(line); if (newSection) { + if (moduleNameSet.count(name)) { + Fatal() << "Module name " << name << " is listed more than once\n"; + } currModule = name; - currFuncs = &moduleFuncs[name]; + moduleNameSet.insert(currModule); + moduleNames.push_back(currModule); + config.secondaryFuncs.emplace_back(std::set()); + currFuncs = &config.secondaryFuncs.back(); newSection = false; continue; } @@ -426,7 +436,6 @@ void multiSplitModule(const WasmSplitOptions& options) { } } - ModuleSplitting::Config config; config.usePlaceholders = options.usePlaceholders; config.importNamespace = options.importNamespace; config.minimizeNewExportNames = !options.passOptions.debugInfo; @@ -434,34 +443,26 @@ void multiSplitModule(const WasmSplitOptions& options) { wasm.name = Path::getBaseName(options.output); } - std::unordered_map> placeholderMap; - for (auto& [mod, funcs] : moduleFuncs) { - if (options.verbose) { - std::cerr << "Splitting module " << mod << '\n'; - } - if (!options.quiet && funcs.empty()) { - std::cerr << "warning: Module " << mod << " will be empty\n"; - } - config.secondaryFuncs = std::set(funcs.begin(), funcs.end()); - auto splitResults = ModuleSplitting::splitFunctions(wasm, config); - auto moduleName = options.outPrefix + mod.toString() + + auto splitResults = ModuleSplitting::splitFunctions(wasm, config); + assert(moduleNames.size() == splitResults.secondaries.size()); + for (Index i = 0, n = moduleNames.size(); i < n; i++) { + auto& secondary = *splitResults.secondaries[i]; + auto moduleName = options.outPrefix + moduleNames[i].toString() + (options.emitBinary ? ".wasm" : ".wast"); if (options.symbolMap) { - writeSymbolMap(*splitResults.secondary, moduleName + ".symbols"); - } - if (options.placeholderMap) { - placeholderMap.merge(splitResults.placeholderMap); + writeSymbolMap(secondary, moduleName + ".symbols"); } if (options.emitModuleNames) { - splitResults.secondary->name = Path::getBaseName(moduleName); + secondary.name = Path::getBaseName(moduleName); } - writeModule(*splitResults.secondary, moduleName, options); + writeModule(secondary, moduleName, options); } if (options.symbolMap) { writeSymbolMap(wasm, options.output + ".symbols"); } if (options.placeholderMap) { - writePlaceholderMap(wasm, placeholderMap, options.output + ".placeholders"); + writePlaceholderMap( + wasm, splitResults.placeholderMap, options.output + ".placeholders"); } writeModule(wasm, options.output, options); } diff --git a/test/example/module-splitting.cpp b/test/example/module-splitting.cpp index b55beb1d3fa..96b4101fc97 100644 --- a/test/example/module-splitting.cpp +++ b/test/example/module-splitting.cpp @@ -51,9 +51,10 @@ void do_test(const std::set& keptFuncs, std::string&& module) { std::cout << "\n"; ModuleSplitting::Config config; - config.secondaryFuncs = std::move(splitFuncs); + config.secondaryFuncs.push_back(std::move(splitFuncs)); config.newExportPrefix = "%"; - auto secondary = splitFunctions(*primary, config).secondary; + auto results = splitFunctions(*primary, config); + auto& secondary = *results.secondaries.begin(); std::cout << "After:\n"; std::cout << *primary.get(); @@ -475,11 +476,12 @@ void test_minimized_exports() { primary.addFunction(Builder::makeFunction("call", funcType, {}, callBody)); ModuleSplitting::Config config; - config.secondaryFuncs = {"call"}; + config.secondaryFuncs.push_back({"call"}); config.newExportPrefix = "%"; config.minimizeNewExportNames = true; - auto secondary = splitFunctions(primary, config).secondary; + auto results = splitFunctions(primary, config); + auto& secondary = *results.secondaries.begin(); std::cout << "Minimized names primary:\n"; std::cout << primary << "\n"; std::cout << "Minimized names secondary:\n"; diff --git a/test/lit/wasm-split/multi-split-escape-names.wast b/test/lit/wasm-split/multi-split-escape-names.wast index 561858e3e12..54e20f18676 100644 --- a/test/lit/wasm-split/multi-split-escape-names.wast +++ b/test/lit/wasm-split/multi-split-escape-names.wast @@ -7,11 +7,13 @@ ;; RUN: wasm-dis %t3.wasm | filecheck %s --check-prefix=MOD3 (module + ;; PRIMARY: (type $ret-i64 (func (result i64))) + + ;; PRIMARY: (type $ret-f32 (func (result f32))) + ;; PRIMARY: (type $ret-i32 (func (result i32))) (type $ret-i32 (func (result i32))) - ;; PRIMARY: (type $ret-i64 (func (result i64))) (type $ret-i64 (func (result i64))) - ;; PRIMARY: (type $ret-f32 (func (result f32))) (type $ret-f32 (func (result f32))) ;; MOD1: (type $0 (func (result f32))) @@ -20,13 +22,13 @@ ;; MOD1: (type $2 (func (result i32))) - ;; MOD1: (import "" "table" (table $timport$0 1 funcref)) + ;; MOD1: (import "" "table" (table $timport$0 3 funcref)) - ;; MOD1: (import "" "std::operator<<\\28std::__2::basic_ostream>&\\2c\\20wasm::Module&\\29" (func $std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29 (result f32))) + ;; MOD1: (import "" "trampoline_std::operator<<\\28std::__2::basic_ostream>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29 (result f32))) - ;; MOD1: (import "" "wasm::Literal::Literal\\28std::__2::array\\20const&\\29" (func $wasm::Literal::Literal\28std::__2::array\20const&\29 (result i64))) + ;; MOD1: (import "" "trampoline_wasm::Literal::Literal\\28std::__2::array\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29 (result i64))) - ;; MOD1: (elem $0 (i32.const 0) $wasm::Type::getFeatures\28\29\20const) + ;; MOD1: (elem $0 (i32.const 2) $wasm::Type::getFeatures\28\29\20const) ;; MOD1: (func $wasm::Type::getFeatures\28\29\20const (result i32) ;; MOD1-NEXT: (drop @@ -36,12 +38,12 @@ ;; MOD1-NEXT: ) ;; MOD1-NEXT: (drop ;; MOD1-NEXT: (call_ref $1 - ;; MOD1-NEXT: (ref.func $wasm::Literal::Literal\28std::__2::array\20const&\29) + ;; MOD1-NEXT: (ref.func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29) ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: (drop ;; MOD1-NEXT: (call_ref $0 - ;; MOD1-NEXT: (ref.func $std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29) + ;; MOD1-NEXT: (ref.func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29) ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: (i32.const 0) @@ -71,9 +73,9 @@ ;; MOD2: (type $2 (func (result i64))) - ;; MOD2: (import "" "table_4" (table $timport$0 1 funcref)) + ;; MOD2: (import "" "table" (table $timport$0 3 funcref)) - ;; MOD2: (import "" "std::operator<<\\28std::__2::basic_ostream>&\\2c\\20wasm::Module&\\29" (func $std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29 (result f32))) + ;; MOD2: (import "" "trampoline_std::operator<<\\28std::__2::basic_ostream>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29 (result f32))) ;; MOD2: (import "" "trampoline_wasm::Type::getFeatures\\28\\29\\20const" (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32))) @@ -92,7 +94,7 @@ ;; MOD2-NEXT: ) ;; MOD2-NEXT: (drop ;; MOD2-NEXT: (call_ref $0 - ;; MOD2-NEXT: (ref.func $std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29) + ;; MOD2-NEXT: (ref.func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29) ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: (i64.const 0) @@ -122,13 +124,13 @@ ;; MOD3: (type $2 (func (result f32))) - ;; MOD3: (import "" "table_5" (table $timport$0 1 funcref)) + ;; MOD3: (import "" "table" (table $timport$0 3 funcref)) - ;; MOD3: (import "" "wasm::Literal::Literal\\28std::__2::array\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29 (result i64))) + ;; MOD3: (import "" "trampoline_wasm::Literal::Literal\\28std::__2::array\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29 (result i64))) ;; MOD3: (import "" "trampoline_wasm::Type::getFeatures\\28\\29\\20const" (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32))) - ;; MOD3: (elem $0 (i32.const 0) $std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29) + ;; MOD3: (elem $0 (i32.const 1) $std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29) ;; MOD3: (func $std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29 (result f32) ;; MOD3-NEXT: (drop @@ -167,50 +169,38 @@ (f32.const 0) ) ) -;; PRIMARY: (import "placeholder" "0" (func $placeholder_0 (result i32))) +;; PRIMARY: (import "placeholder" "0" (func $placeholder_0 (result i64))) -;; PRIMARY: (import "placeholder" "0" (func $placeholder_0_4 (result i64))) +;; PRIMARY: (import "placeholder" "1" (func $placeholder_1 (result f32))) -;; PRIMARY: (import "placeholder" "0" (func $placeholder_0_5 (result f32))) +;; PRIMARY: (import "placeholder" "2" (func $placeholder_2 (result i32))) -;; PRIMARY: (table $0 1 funcref) +;; PRIMARY: (table $0 3 funcref) -;; PRIMARY: (table $1 1 funcref) +;; PRIMARY: (elem $0 (i32.const 0) $placeholder_0 $placeholder_1 $placeholder_2) -;; PRIMARY: (table $2 1 funcref) +;; PRIMARY: (export "trampoline_std::operator<<\\28std::__2::basic_ostream>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29)) -;; PRIMARY: (elem $0 (table $0) (i32.const 0) func $placeholder_0) - -;; PRIMARY: (elem $1 (table $1) (i32.const 0) func $placeholder_0_4) - -;; PRIMARY: (elem $2 (table $2) (i32.const 0) func $placeholder_0_5) - -;; PRIMARY: (export "std::operator<<\\28std::__2::basic_ostream>&\\2c\\20wasm::Module&\\29" (func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29)) - -;; PRIMARY: (export "wasm::Literal::Literal\\28std::__2::array\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29)) - -;; PRIMARY: (export "table" (table $0)) +;; PRIMARY: (export "trampoline_wasm::Literal::Literal\\28std::__2::array\\20const&\\29" (func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29)) ;; PRIMARY: (export "trampoline_wasm::Type::getFeatures\\28\\29\\20const" (func $trampoline_wasm::Type::getFeatures\28\29\20const)) -;; PRIMARY: (export "table_4" (table $1)) - -;; PRIMARY: (export "table_5" (table $2)) +;; PRIMARY: (export "table" (table $0)) -;; PRIMARY: (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32) -;; PRIMARY-NEXT: (call_indirect (type $ret-i32) +;; PRIMARY: (func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29 (result i64) +;; PRIMARY-NEXT: (call_indirect (type $ret-i64) ;; PRIMARY-NEXT: (i32.const 0) ;; PRIMARY-NEXT: ) ;; PRIMARY-NEXT: ) -;; PRIMARY: (func $trampoline_wasm::Literal::Literal\28std::__2::array\20const&\29 (result i64) -;; PRIMARY-NEXT: (call_indirect $1 (type $ret-i64) -;; PRIMARY-NEXT: (i32.const 0) +;; PRIMARY: (func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29 (result f32) +;; PRIMARY-NEXT: (call_indirect (type $ret-f32) +;; PRIMARY-NEXT: (i32.const 1) ;; PRIMARY-NEXT: ) ;; PRIMARY-NEXT: ) -;; PRIMARY: (func $trampoline_std::operator<<\28std::__2::basic_ostream>&\2c\20wasm::Module&\29 (result f32) -;; PRIMARY-NEXT: (call_indirect $2 (type $ret-f32) -;; PRIMARY-NEXT: (i32.const 0) +;; PRIMARY: (func $trampoline_wasm::Type::getFeatures\28\29\20const (result i32) +;; PRIMARY-NEXT: (call_indirect (type $ret-i32) +;; PRIMARY-NEXT: (i32.const 2) ;; PRIMARY-NEXT: ) ;; PRIMARY-NEXT: ) diff --git a/test/lit/wasm-split/multi-split.wast b/test/lit/wasm-split/multi-split.wast index 1354d34d4ec..c8419732da1 100644 --- a/test/lit/wasm-split/multi-split.wast +++ b/test/lit/wasm-split/multi-split.wast @@ -14,14 +14,18 @@ ;; RUN: wasm-dis %t3.wasm | filecheck %s --check-prefix=MOD3-OPTIONS (module + ;; PRIMARY: (type $ret-i64 (func (result i64))) + + ;; PRIMARY: (type $ret-f32 (func (result f32))) + ;; PRIMARY: (type $ret-i32 (func (result i32))) + ;; PRIMARY-OPTIONS: (type $ret-i64 (func (result i64))) + + ;; PRIMARY-OPTIONS: (type $ret-f32 (func (result f32))) + ;; PRIMARY-OPTIONS: (type $ret-i32 (func (result i32))) (type $ret-i32 (func (result i32))) - ;; PRIMARY: (type $ret-i64 (func (result i64))) - ;; PRIMARY-OPTIONS: (type $ret-i64 (func (result i64))) (type $ret-i64 (func (result i64))) - ;; PRIMARY: (type $ret-f32 (func (result f32))) - ;; PRIMARY-OPTIONS: (type $ret-f32 (func (result f32))) (type $ret-f32 (func (result f32))) ;; MOD1: (type $0 (func (result i64))) @@ -30,13 +34,13 @@ ;; MOD1: (type $2 (func (result i32))) - ;; MOD1: (import "" "table" (table $timport$0 1 funcref)) + ;; MOD1: (import "" "table" (table $timport$0 3 funcref)) - ;; MOD1: (import "" "B" (func $B (result i64))) + ;; MOD1: (import "" "trampoline_B" (func $trampoline_B (result i64))) - ;; MOD1: (import "" "C" (func $C (result f32))) + ;; MOD1: (import "" "trampoline_C" (func $trampoline_C (result f32))) - ;; MOD1: (elem $0 (i32.const 0) $A) + ;; MOD1: (elem $0 (i32.const 2) $A) ;; MOD1: (func $A (result i32) ;; MOD1-NEXT: (drop @@ -46,12 +50,12 @@ ;; MOD1-NEXT: ) ;; MOD1-NEXT: (drop ;; MOD1-NEXT: (call_ref $0 - ;; MOD1-NEXT: (ref.func $B) + ;; MOD1-NEXT: (ref.func $trampoline_B) ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: (drop ;; MOD1-NEXT: (call_ref $1 - ;; MOD1-NEXT: (ref.func $C) + ;; MOD1-NEXT: (ref.func $trampoline_C) ;; MOD1-NEXT: ) ;; MOD1-NEXT: ) ;; MOD1-NEXT: (i32.const 0) @@ -62,13 +66,13 @@ ;; MOD1-OPTIONS: (type $2 (func (result i32))) - ;; MOD1-OPTIONS: (import "custom_env" "table" (table $timport$0 1 funcref)) + ;; MOD1-OPTIONS: (import "custom_env" "table" (table $timport$0 3 funcref)) - ;; MOD1-OPTIONS: (import "custom_env" "B" (func $B (result i64))) + ;; MOD1-OPTIONS: (import "custom_env" "trampoline_B" (func $trampoline_B (result i64))) - ;; MOD1-OPTIONS: (import "custom_env" "C" (func $C (result f32))) + ;; MOD1-OPTIONS: (import "custom_env" "trampoline_C" (func $trampoline_C (result f32))) - ;; MOD1-OPTIONS: (elem $0 (i32.const 0) $A) + ;; MOD1-OPTIONS: (elem $0 (i32.const 2) $A) ;; MOD1-OPTIONS: (func $A (result i32) ;; MOD1-OPTIONS-NEXT: (drop @@ -78,12 +82,12 @@ ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: (drop ;; MOD1-OPTIONS-NEXT: (call_ref $0 - ;; MOD1-OPTIONS-NEXT: (ref.func $B) + ;; MOD1-OPTIONS-NEXT: (ref.func $trampoline_B) ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: (drop ;; MOD1-OPTIONS-NEXT: (call_ref $1 - ;; MOD1-OPTIONS-NEXT: (ref.func $C) + ;; MOD1-OPTIONS-NEXT: (ref.func $trampoline_C) ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: ) ;; MOD1-OPTIONS-NEXT: (i32.const 0) @@ -107,23 +111,23 @@ (i32.const 0) ) - ;; MOD2: (type $0 (func (result f32))) + ;; MOD2: (type $0 (func (result i32))) - ;; MOD2: (type $1 (func (result i32))) + ;; MOD2: (type $1 (func (result f32))) ;; MOD2: (type $2 (func (result i64))) - ;; MOD2: (import "" "table_4" (table $timport$0 1 funcref)) - - ;; MOD2: (import "" "C" (func $C (result f32))) + ;; MOD2: (import "" "table" (table $timport$0 3 funcref)) ;; MOD2: (import "" "trampoline_A" (func $trampoline_A (result i32))) + ;; MOD2: (import "" "trampoline_C" (func $trampoline_C (result f32))) + ;; MOD2: (elem $0 (i32.const 0) $B) ;; MOD2: (func $B (result i64) ;; MOD2-NEXT: (drop - ;; MOD2-NEXT: (call_ref $1 + ;; MOD2-NEXT: (call_ref $0 ;; MOD2-NEXT: (ref.func $trampoline_A) ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) @@ -133,29 +137,29 @@ ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: (drop - ;; MOD2-NEXT: (call_ref $0 - ;; MOD2-NEXT: (ref.func $C) + ;; MOD2-NEXT: (call_ref $1 + ;; MOD2-NEXT: (ref.func $trampoline_C) ;; MOD2-NEXT: ) ;; MOD2-NEXT: ) ;; MOD2-NEXT: (i64.const 0) ;; MOD2-NEXT: ) - ;; MOD2-OPTIONS: (type $0 (func (result f32))) + ;; MOD2-OPTIONS: (type $0 (func (result i32))) - ;; MOD2-OPTIONS: (type $1 (func (result i32))) + ;; MOD2-OPTIONS: (type $1 (func (result f32))) ;; MOD2-OPTIONS: (type $2 (func (result i64))) - ;; MOD2-OPTIONS: (import "custom_env" "table_4" (table $timport$0 1 funcref)) - - ;; MOD2-OPTIONS: (import "custom_env" "C" (func $C (result f32))) + ;; MOD2-OPTIONS: (import "custom_env" "table" (table $timport$0 3 funcref)) ;; MOD2-OPTIONS: (import "custom_env" "trampoline_A" (func $trampoline_A (result i32))) + ;; MOD2-OPTIONS: (import "custom_env" "trampoline_C" (func $trampoline_C (result f32))) + ;; MOD2-OPTIONS: (elem $0 (i32.const 0) $B) ;; MOD2-OPTIONS: (func $B (result i64) ;; MOD2-OPTIONS-NEXT: (drop - ;; MOD2-OPTIONS-NEXT: (call_ref $1 + ;; MOD2-OPTIONS-NEXT: (call_ref $0 ;; MOD2-OPTIONS-NEXT: (ref.func $trampoline_A) ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: ) @@ -165,8 +169,8 @@ ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: (drop - ;; MOD2-OPTIONS-NEXT: (call_ref $0 - ;; MOD2-OPTIONS-NEXT: (ref.func $C) + ;; MOD2-OPTIONS-NEXT: (call_ref $1 + ;; MOD2-OPTIONS-NEXT: (ref.func $trampoline_C) ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: ) ;; MOD2-OPTIONS-NEXT: (i64.const 0) @@ -196,13 +200,13 @@ ;; MOD3: (type $2 (func (result f32))) - ;; MOD3: (import "" "table_5" (table $timport$0 1 funcref)) + ;; MOD3: (import "" "table" (table $timport$0 3 funcref)) ;; MOD3: (import "" "trampoline_A" (func $trampoline_A (result i32))) - ;; MOD3: (import "" "B" (func $trampoline_B (result i64))) + ;; MOD3: (import "" "trampoline_B" (func $trampoline_B (result i64))) - ;; MOD3: (elem $0 (i32.const 0) $C) + ;; MOD3: (elem $0 (i32.const 1) $C) ;; MOD3: (func $C (result f32) ;; MOD3-NEXT: (drop @@ -228,13 +232,13 @@ ;; MOD3-OPTIONS: (type $2 (func (result f32))) - ;; MOD3-OPTIONS: (import "custom_env" "table_5" (table $timport$0 1 funcref)) + ;; MOD3-OPTIONS: (import "custom_env" "table" (table $timport$0 3 funcref)) ;; MOD3-OPTIONS: (import "custom_env" "trampoline_A" (func $trampoline_A (result i32))) - ;; MOD3-OPTIONS: (import "custom_env" "B" (func $trampoline_B (result i64))) + ;; MOD3-OPTIONS: (import "custom_env" "trampoline_B" (func $trampoline_B (result i64))) - ;; MOD3-OPTIONS: (elem $0 (i32.const 0) $C) + ;; MOD3-OPTIONS: (elem $0 (i32.const 1) $C) ;; MOD3-OPTIONS: (func $C (result f32) ;; MOD3-OPTIONS-NEXT: (drop @@ -273,92 +277,68 @@ (f32.const 0) ) ) -;; PRIMARY: (import "placeholder" "0" (func $placeholder_0 (result i32))) - -;; PRIMARY: (import "placeholder" "0" (func $placeholder_0_4 (result i64))) - -;; PRIMARY: (import "placeholder" "0" (func $placeholder_0_5 (result f32))) - -;; PRIMARY: (table $0 1 funcref) +;; PRIMARY: (import "placeholder" "0" (func $placeholder_0 (result i64))) -;; PRIMARY: (table $1 1 funcref) +;; PRIMARY: (import "placeholder" "1" (func $placeholder_1 (result f32))) -;; PRIMARY: (table $2 1 funcref) +;; PRIMARY: (import "placeholder" "2" (func $placeholder_2 (result i32))) -;; PRIMARY: (elem $0 (table $0) (i32.const 0) func $placeholder_0) +;; PRIMARY: (table $0 3 funcref) -;; PRIMARY: (elem $1 (table $1) (i32.const 0) func $placeholder_0_4) +;; PRIMARY: (elem $0 (i32.const 0) $placeholder_0 $placeholder_1 $placeholder_2) -;; PRIMARY: (elem $2 (table $2) (i32.const 0) func $placeholder_0_5) +;; PRIMARY: (export "trampoline_B" (func $trampoline_B)) -;; PRIMARY: (export "B" (func $trampoline_B)) - -;; PRIMARY: (export "C" (func $trampoline_C)) - -;; PRIMARY: (export "table" (table $0)) +;; PRIMARY: (export "trampoline_C" (func $trampoline_C)) ;; PRIMARY: (export "trampoline_A" (func $trampoline_A)) -;; PRIMARY: (export "table_4" (table $1)) - -;; PRIMARY: (export "table_5" (table $2)) - -;; PRIMARY: (func $trampoline_A (result i32) -;; PRIMARY-NEXT: (call_indirect (type $ret-i32) -;; PRIMARY-NEXT: (i32.const 0) -;; PRIMARY-NEXT: ) -;; PRIMARY-NEXT: ) +;; PRIMARY: (export "table" (table $0)) ;; PRIMARY: (func $trampoline_B (result i64) -;; PRIMARY-NEXT: (call_indirect $1 (type $ret-i64) +;; PRIMARY-NEXT: (call_indirect (type $ret-i64) ;; PRIMARY-NEXT: (i32.const 0) ;; PRIMARY-NEXT: ) ;; PRIMARY-NEXT: ) ;; PRIMARY: (func $trampoline_C (result f32) -;; PRIMARY-NEXT: (call_indirect $2 (type $ret-f32) -;; PRIMARY-NEXT: (i32.const 0) +;; PRIMARY-NEXT: (call_indirect (type $ret-f32) +;; PRIMARY-NEXT: (i32.const 1) ;; PRIMARY-NEXT: ) ;; PRIMARY-NEXT: ) -;; PRIMARY-OPTIONS: (table $0 1 funcref) - -;; PRIMARY-OPTIONS: (table $1 1 funcref) - -;; PRIMARY-OPTIONS: (table $2 1 funcref) - -;; PRIMARY-OPTIONS: (elem $0 (table $0) (i32.const 0) funcref (item (ref.null nofunc))) - -;; PRIMARY-OPTIONS: (elem $1 (table $1) (i32.const 0) funcref (item (ref.null nofunc))) +;; PRIMARY: (func $trampoline_A (result i32) +;; PRIMARY-NEXT: (call_indirect (type $ret-i32) +;; PRIMARY-NEXT: (i32.const 2) +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) -;; PRIMARY-OPTIONS: (elem $2 (table $2) (i32.const 0) funcref (item (ref.null nofunc))) +;; PRIMARY-OPTIONS: (table $0 3 funcref) -;; PRIMARY-OPTIONS: (export "B" (func $trampoline_B)) +;; PRIMARY-OPTIONS: (elem $0 (table $0) (i32.const 0) funcref (item (ref.null nofunc)) (item (ref.null nofunc)) (item (ref.null nofunc))) -;; PRIMARY-OPTIONS: (export "C" (func $trampoline_C)) +;; PRIMARY-OPTIONS: (export "trampoline_B" (func $trampoline_B)) -;; PRIMARY-OPTIONS: (export "table" (table $0)) +;; PRIMARY-OPTIONS: (export "trampoline_C" (func $trampoline_C)) ;; PRIMARY-OPTIONS: (export "trampoline_A" (func $trampoline_A)) -;; PRIMARY-OPTIONS: (export "table_4" (table $1)) - -;; PRIMARY-OPTIONS: (export "table_5" (table $2)) +;; PRIMARY-OPTIONS: (export "table" (table $0)) -;; PRIMARY-OPTIONS: (func $trampoline_A (result i32) -;; PRIMARY-OPTIONS-NEXT: (call_indirect (type $ret-i32) +;; PRIMARY-OPTIONS: (func $trampoline_B (result i64) +;; PRIMARY-OPTIONS-NEXT: (call_indirect (type $ret-i64) ;; PRIMARY-OPTIONS-NEXT: (i32.const 0) ;; PRIMARY-OPTIONS-NEXT: ) ;; PRIMARY-OPTIONS-NEXT: ) -;; PRIMARY-OPTIONS: (func $trampoline_B (result i64) -;; PRIMARY-OPTIONS-NEXT: (call_indirect $1 (type $ret-i64) -;; PRIMARY-OPTIONS-NEXT: (i32.const 0) +;; PRIMARY-OPTIONS: (func $trampoline_C (result f32) +;; PRIMARY-OPTIONS-NEXT: (call_indirect (type $ret-f32) +;; PRIMARY-OPTIONS-NEXT: (i32.const 1) ;; PRIMARY-OPTIONS-NEXT: ) ;; PRIMARY-OPTIONS-NEXT: ) -;; PRIMARY-OPTIONS: (func $trampoline_C (result f32) -;; PRIMARY-OPTIONS-NEXT: (call_indirect $2 (type $ret-f32) -;; PRIMARY-OPTIONS-NEXT: (i32.const 0) +;; PRIMARY-OPTIONS: (func $trampoline_A (result i32) +;; PRIMARY-OPTIONS-NEXT: (call_indirect (type $ret-i32) +;; PRIMARY-OPTIONS-NEXT: (i32.const 2) ;; PRIMARY-OPTIONS-NEXT: ) ;; PRIMARY-OPTIONS-NEXT: ) diff --git a/test/lit/wasm-split/placeholdermap-multi-split.wast b/test/lit/wasm-split/placeholdermap-multi-split.wast index 1af96452c41..0346d407db6 100644 --- a/test/lit/wasm-split/placeholdermap-multi-split.wast +++ b/test/lit/wasm-split/placeholdermap-multi-split.wast @@ -2,13 +2,9 @@ ;; RUN: filecheck %s --check-prefix MAP < %t.wasm.placeholders ;; MAP: table 0 -;; MAP-NEXT: 0:A -;; MAP-NEXT: -;; MAP-NEXT: table 1 ;; MAP-NEXT: 0:B -;; MAP-NEXT: -;; MAP-NEXT: table 2 -;; MAP-NEXT: 0:C +;; MAP-NEXT: 1:C +;; MAP-NEXT: 2:A (module (type $ret-i32 (func (result i32))) diff --git a/test/lit/wasm-split/symbolmap-multi-split.wast b/test/lit/wasm-split/symbolmap-multi-split.wast index 77cbff0c8f2..479256db508 100644 --- a/test/lit/wasm-split/symbolmap-multi-split.wast +++ b/test/lit/wasm-split/symbolmap-multi-split.wast @@ -5,18 +5,18 @@ ;; RUN: filecheck %s --check-prefix MOD3-MAP < %t3.wasm.symbols ;; PRIMARY-MAP: 0:placeholder_0 -;; PRIMARY-MAP: 1:placeholder_0_4 -;; PRIMARY-MAP: 2:placeholder_0_5 -;; PRIMARY-MAP: 3:trampoline_A -;; PRIMARY-MAP: 4:trampoline_B -;; PRIMARY-MAP: 5:trampoline_C +;; PRIMARY-MAP: 1:placeholder_1 +;; PRIMARY-MAP: 2:placeholder_2 +;; PRIMARY-MAP: 3:trampoline_B +;; PRIMARY-MAP: 4:trampoline_C +;; PRIMARY-MAP: 5:trampoline_A -;; MOD1-MAP: 0:B -;; MOD1-MAP: 1:C +;; MOD1-MAP: 0:trampoline_B +;; MOD1-MAP: 1:trampoline_C ;; MOD1-MAP: 2:A -;; MOD2-MAP: 0:C -;; MOD2-MAP: 1:trampoline_A +;; MOD2-MAP: 0:trampoline_A +;; MOD2-MAP: 1:trampoline_C ;; MOD2-MAP: 2:B ;; MOD3-MAP: 0:trampoline_A