From 9bfbbe6bb1d827824b472463b7043b8666294b38 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 24 Sep 2025 21:01:15 +0000 Subject: [PATCH 01/11] [wasm-split] Do multi-split at once This does multi-splitting of modules at once, rather than splitting them one by one by doing 2-way split n times. Previously when we did multi-splitting we split the 1st module as the "secondary" module assuming all other functions belonging to 2nd-nth modules as "primary" module. And then we repeat the same task for the 2nd module, assuming 3rd-nth module functions belong to the "primary" module. This unnecessarily repeated some tasks that could have been done once. This reduces the running time on a reproducer provided by @biggs0125 before (to fix #7725) from 236s to 88s, reducing it by around 63%. Some side-products of this PR are: - Now we only create a single table to host placeholders (or `ref.null`s in case of `--no-placeholders`) even when reference-types is enabled. Previously we created a table per secondary module, resulting in n tables. - The names of trampoline functions have been changed in the tests, but semantically they are the same. (e.g. in `test/lit/wasm-split/multi-split.wast`) The reason for the change is, previously we split modules one by one, by the time we split the first module, it assumed functions belonging to other secondary modules were primary functions, but they later changed to trampolines as well. Now they are all named as trampolines, arguably enhacing readability. --- Some detailed analysis run using the reproducer of #7725, a case where we split a module into 301 (1 primary + 300 secondary) modules: - Before this PR: Time: 236.8s Task breakdown: ``` Task Total Time (ms) Percentage --------------------------------------------------------------------------- shareImportableItems 62661.1860 28.24% classifyFunctions 42366.7451 19.09% removeUnusedSecondaryElements 33083.6602 14.91% indirectReferencesToSecondaryFunctions 27852.3143 12.55% indirectCallsToSecondaryFunctions 25091.4263 11.31% moveSecondaryFunctions 14159.9166 6.38% writeModule_secondary 9331.1667 4.20% setupTablePatching 3099.9597 1.40% initExportedPrimaryFuncs 1657.0465 0.75% writeModule_primary 901.6800 0.41% exportImportCalledPrimaryFunctions 892.0132 0.40% thunkExportedSecondaryFunctions 826.8599 0.37% initSecondary 0.2241 0.00% --------------------------------------------------------------------------- Overall Total 221924.1985 100.00% ``` - After this PR: Time : 88.40207334437098 Task breakdown: ``` Task Total Time (ms) Percentage --------------------------------------------------------------------------- shareImportableItems 40176.7000 50.38% removeUnusedSecondaryElements 28635.2000 35.91% moveSecondaryFunctions 5998.9600 7.52% writeModule_secondary 2611.0099 3.27% writeModule_primary 935.7750 1.17% exportImportCalledPrimaryFunctions 646.9860 0.81% indirectReferencesToSecondaryFunctions 318.2980 0.40% classifyFunctions 238.5780 0.30% indirectCallsToSecondaryFunctions 139.1730 0.17% setupTablePatching 44.1466 0.06% thunkExportedSecondaryFunctions 3.9405 0.00% initExportedPrimaryFuncs 0.6870 0.00% --------------------------------------------------------------------------- Overall Total 79749.4539 100.00% ``` We can see time taken in `classifyFunctions`, `indirectReferencesToSecondaryFunctions`, and `indirectCallsToSecondaryFunctions` has reduced basically to nothing. This is because now we can all functions only once in those functions, where we used to scan the functions n times or similar. Now `shareImportableItems` and `moveSecondaryFunctions` take up around 85% of the execution time. The reason `shareImportableItems` takes so long is the reproducer has 90k globals. ``` Analysis of shareImportableItems: Sub-Task Total Time (ms) Percentage --------------------------------------------------------------------------- globals 41166.4904 98.35% tables 535.5134 1.28% tags 10.3355 0.02% memories 7.5937 0.02% exports 1.5482 0.00% --------------------------------------------------------------------------- Total 41857.1000 100.00% ``` ('exports' meaning processing existing exports) We can probably improve this by selectively importing module items, as already noted by the existing TODO. `moveSecondaryFunctions` basically just runs RemoveUnusedModuleElements on each module. We can also consider parallelizing `moveSecondaryFunctions` by modules but not sure how much improvements it can bring given that the pass is already parallized in function granularity. But if we export only used items in `shareImportableItems`, running this pass may become unnecessary after all. --- src/ir/module-splitting.cpp | 464 ++++++++++-------- src/ir/module-splitting.h | 9 +- src/tools/wasm-split/wasm-split.cpp | 52 +- test/example/module-splitting.cpp | 10 +- .../wasm-split/multi-split-escape-names.wast | 74 ++- test/lit/wasm-split/multi-split.wast | 162 +++--- .../placeholdermap-multi-split.wast | 8 +- .../lit/wasm-split/symbolmap-multi-split.wast | 18 +- 8 files changed, 419 insertions(+), 378 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 12aaa9512f8..deefc60c86f 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 secondary functions in the primary module's or +// different 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 the 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,15 @@ TableSlotManager::Slot TableSlotManager::getSlot(Name func, HeapType type) { struct ModuleSplitter { const Config& config; - std::unique_ptr secondaryPtr; + // TODO unordered_map possible? + std::map> secondaryPtrMap; 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 allSecondryFuncs; + std::map> moduleToFuncs; + std::unordered_map funcToModule; TableSlotManager tableManager; @@ -305,7 +308,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. @@ -318,34 +321,32 @@ struct ModuleSplitter { std::unordered_map trampolineMap; // 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::unique_ptr initSecondary(const Module& primary, + Name name); + 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(); } @@ -390,16 +391,17 @@ void ModuleSplitter::setupJSPI() { LOAD_SECONDARY_STATUS, LOAD_SECONDARY_STATUS, ExternalKind::Global)); } -std::unique_ptr ModuleSplitter::initSecondary(const Module& primary) { +std::unique_ptr ModuleSplitter::initSecondary(const Module& primary, + Name name) { // Create the secondary module and copy trivial properties. auto secondary = std::make_unique(); secondary->features = primary.features; secondary->hasFeaturesSection = primary.hasFeaturesSection; + secondary->name = name; 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 +459,44 @@ ModuleSplitter::classifyFunctions(Module& primary, const Config& config) { segmentReferrers.insert(referrers.begin(), referrers.end()); } - std::set primaryFuncs, secondaryFuncs; + std::unordered_set configSecondaryFuncs; + for (auto& [mod, funcs] : config.moduleToFuncs) { + 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); + allSecondryFuncs.insert(func->name); + } + } + + // Move functions to each secondary module and create the reverse map + for (auto& [mod, funcs] : config.moduleToFuncs) { + moduleToFuncs[mod]; + for (auto func : funcs) { + if (allSecondryFuncs.count(func)) { + moduleToFuncs[mod].insert(func); + } + } + } + for (auto& [mod, funcs] : moduleToFuncs) { + for (auto func : funcs) { + funcToModule[func] = mod; } } - 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 +505,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 +528,28 @@ 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& [mod, funcNames] : moduleToFuncs) { + auto [it, _] = secondaryPtrMap.emplace(mod, initSecondary(primary, mod)); + Module& secondary = *(it->second); + for (auto funcName : funcNames) { + auto* func = primary.getFunction(funcName); + ModuleUtils::copyFunction(func, secondary); + primary.removeFunction(funcName); + } } } @@ -534,6 +560,7 @@ Name ModuleSplitter::getTrampoline(Name funcName) { } Builder builder(primary); + Module& secondary = *secondaryPtrMap[funcToModule[funcName]]; auto* oldFunc = secondary.getFunction(funcName); auto trampoline = Names::getValidFunctionName( primary, std::string("trampoline_") + funcName.toString()); @@ -549,6 +576,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 +589,7 @@ void ModuleSplitter::thunkExportedSecondaryFunctions() { Builder builder(primary); for (auto& ex : primary.exports) { if (ex->kind != ExternalKind::Function || - !secondaryFuncs.count(*ex->getInternalName())) { + !allSecondryFuncs.count(*ex->getInternalName())) { continue; } Name trampoline = getTrampoline(*ex->getInternalName()); @@ -598,12 +626,22 @@ void ModuleSplitter::indirectReferencesToSecondaryFunctions() { InsertOrderedMap> map; void visitRefFunc(RefFunc* curr) { - if (parent.secondaryFuncs.count(curr->func)) { + Module* curModule = 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 second module) + if (parent.allSecondryFuncs.count(curr->func) && + (curModule == &parent.primary || + curModule->name != parent.funcToModule.at(curr->func))) { map[curr->func].push_back(curr); } } } gatherer(*this); gatherer.walkModule(&primary); + for (auto& [mod, secondaryPtr] : secondaryPtrMap) { + 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 +687,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.allSecondryFuncs.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* curModule = getModule(); + if (curModule != &parent.primary && + curModule->name == parent.funcToModule.at(curr->target)) { + return; + } + + Builder builder(*getModule()); + Name moduleName = parent.funcToModule.at(curr->target); + auto* func = + parent.secondaryPtrMap.at(moduleName)->getFunction(curr->target); auto tableSlot = parent.tableManager.getSlot(curr->target, func->type); replaceCurrent(parent.maybeLoadSecondary( @@ -670,42 +716,54 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { curr->isReturn))); } }; - CallIndirector(*this).walkModule(&primary); + CallIndirector callIndirector(*this); + callIndirector.walkModule(&primary); + for (auto& [mod, secondaryPtr] : secondaryPtrMap) { + 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); + // using RefFuncMap = InsertOrderedMap>; + using CalledPrimaryToModules = std::map>; + for (auto& [mod, secondaryPtr] : secondaryPtrMap) { + Module* secondary = secondaryPtr.get(); + ModuleUtils::ParallelFunctionAnalysis callCollector( + *secondary, + [&](Function* func, CalledPrimaryToModules& calledPrimaryFuncs) { + struct CallCollector : PostWalker { + const std::unordered_set& primaryFuncs; + CalledPrimaryToModules& calledPrimaryFuncs; + CallCollector(const std::unordered_set& primaryFuncs, + CalledPrimaryToModules& calledPrimaryFuncs) + : primaryFuncs(primaryFuncs), + calledPrimaryFuncs(calledPrimaryFuncs) {} + void visitCall(Call* curr) { + if (primaryFuncs.count(curr->target)) { + calledPrimaryFuncs[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)) { + calledPrimaryFuncs[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 collector(primaryFuncs, calledPrimaryFuncs); + collector.setModule(secondary); + collector.walkFunction(func); + }); + + 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 +772,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 +782,15 @@ void ModuleSplitter::setupTablePatching() { if (!ref) { return; } - if (!secondaryFuncs.count(ref->func)) { + if (!allSecondryFuncs.count(ref->func)) { return; } assert(table == tableManager.activeTable->name); placeholderMap[table][index] = ref->func; + Module& secondary = *secondaryPtrMap[funcToModule[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 +808,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 +926,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] : secondaryPtrMap) { + 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 +980,22 @@ 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] : secondaryPtrMap) { + 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)}; + // Remove the temporary names we used during splitting + for (auto& [_, secondaryPtr] : split.secondaryPtrMap) { + secondaryPtr->name = Name(); + } + return {std::move(split.secondaryPtrMap), std::move(split.placeholderMap)}; } } // namespace wasm::ModuleSplitting diff --git a/src/ir/module-splitting.h b/src/ir/module-splitting.h index b21a4f2bc8f..8dc587a3bc2 100644 --- a/src/ir/module-splitting.h +++ b/src/ir/module-splitting.h @@ -47,11 +47,8 @@ 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; + // Module names to the functions that should be in the modules. + std::map> moduleToFuncs; // 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 +73,7 @@ struct Config { }; struct Results { - std::unique_ptr secondary; + std::map> secondaryPtrMap; std::unordered_map> placeholderMap; }; diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index 669101fc4b5..4d23844e2c7 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.moduleToFuncs["secondary"] = 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.secondaryPtrMap["secondary"]; adjustTableSize(wasm, options.initialTableSize); adjustTableSize(*secondary, options.initialTableSize, /*secondary=*/true); @@ -389,18 +389,18 @@ 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::unordered_set moduleNames; while (std::getline(manifest, line)) { if (line.empty()) { newSection = true; @@ -408,8 +408,10 @@ void multiSplitModule(const WasmSplitOptions& options) { } Name name = WasmBinaryReader::escape(line); if (newSection) { - currModule = name; - currFuncs = &moduleFuncs[name]; + currModule = Names::getValidName( + name, [&](Name n) { return moduleNames.find(n) == moduleNames.end(); }); + moduleNames.insert(currModule); + currFuncs = &config.moduleToFuncs[currModule]; newSection = false; continue; } @@ -426,7 +428,6 @@ void multiSplitModule(const WasmSplitOptions& options) { } } - ModuleSplitting::Config config; config.usePlaceholders = options.usePlaceholders; config.importNamespace = options.importNamespace; config.minimizeNewExportNames = !options.passOptions.debugInfo; @@ -434,34 +435,31 @@ 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'; - } + for (auto& [mod, funcs] : config.moduleToFuncs) { 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 splitResults = ModuleSplitting::splitFunctions(wasm, config); + for (auto& [mod, funcs] : config.moduleToFuncs) { + Module& secondary = *splitResults.secondaryPtrMap[mod]; auto moduleName = options.outPrefix + mod.toString() + (options.emitBinary ? ".wasm" : ".wast"); if (options.symbolMap) { - writeSymbolMap(*splitResults.secondary, moduleName + ".symbols"); - } - if (options.placeholderMap) { - placeholderMap.merge(splitResults.placeholderMap); + writeSymbolMap(*splitResults.secondaryPtrMap[mod], + moduleName + ".symbols"); } if (options.emitModuleNames) { - splitResults.secondary->name = Path::getBaseName(moduleName); + secondary.name = Path::getBaseName(moduleName); + } + writeModule(secondary, moduleName, options); + if (options.symbolMap) { + writeSymbolMap(wasm, options.output + ".symbols"); + } + if (options.placeholderMap) { + writePlaceholderMap( + wasm, splitResults.placeholderMap, options.output + ".placeholders"); } - writeModule(*splitResults.secondary, moduleName, options); - } - if (options.symbolMap) { - writeSymbolMap(wasm, options.output + ".symbols"); - } - if (options.placeholderMap) { - writePlaceholderMap(wasm, 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..0a6d99c98b7 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.moduleToFuncs["secondary"] = std::move(splitFuncs); config.newExportPrefix = "%"; - auto secondary = splitFunctions(*primary, config).secondary; + auto results = splitFunctions(*primary, config); + auto& secondary = results.secondaryPtrMap["secondary"]; 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.moduleToFuncs["secondary"] = {"call"}; config.newExportPrefix = "%"; config.minimizeNewExportNames = true; - auto secondary = splitFunctions(primary, config).secondary; + auto results = splitFunctions(primary, config); + auto& secondary = results.secondaryPtrMap["secondary"]; 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 From 7de862e84d0b0f846c194da56c4aa0b553f2f334 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 8 Oct 2025 08:44:20 +0000 Subject: [PATCH 02/11] Misc fixes - Remove old comments + fix comments - Rename a variable - Take primary module's symbolmap and placeholdermap writing out of the for loop --- src/ir/module-splitting.cpp | 18 ++++++++---------- src/ir/module-splitting.h | 7 ++++++- src/tools/wasm-split/wasm-split.cpp | 14 +++++++------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index deefc60c86f..cc45291fa33 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -292,7 +292,6 @@ TableSlotManager::Slot TableSlotManager::getSlot(Name func, HeapType type) { struct ModuleSplitter { const Config& config; - // TODO unordered_map possible? std::map> secondaryPtrMap; Module& primary; @@ -630,7 +629,7 @@ void ModuleSplitter::indirectReferencesToSecondaryFunctions() { // 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 second module) + // or a different secondary module) if (parent.allSecondryFuncs.count(curr->func) && (curModule == &parent.primary || curModule->name != parent.funcToModule.at(curr->func))) { @@ -725,32 +724,31 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { void ModuleSplitter::exportImportCalledPrimaryFunctions() { // Find primary functions called/referred in the secondary module. - // using RefFuncMap = InsertOrderedMap>; using CalledPrimaryToModules = std::map>; for (auto& [mod, secondaryPtr] : secondaryPtrMap) { Module* secondary = secondaryPtr.get(); ModuleUtils::ParallelFunctionAnalysis callCollector( *secondary, - [&](Function* func, CalledPrimaryToModules& calledPrimaryFuncs) { + [&](Function* func, CalledPrimaryToModules& calledPrimaryToModules) { struct CallCollector : PostWalker { const std::unordered_set& primaryFuncs; - CalledPrimaryToModules& calledPrimaryFuncs; + CalledPrimaryToModules& calledPrimaryToModules; CallCollector(const std::unordered_set& primaryFuncs, - CalledPrimaryToModules& calledPrimaryFuncs) + CalledPrimaryToModules& calledPrimaryToModules) : primaryFuncs(primaryFuncs), - calledPrimaryFuncs(calledPrimaryFuncs) {} + calledPrimaryToModules(calledPrimaryToModules) {} void visitCall(Call* curr) { if (primaryFuncs.count(curr->target)) { - calledPrimaryFuncs[curr->target].insert(getModule()); + calledPrimaryToModules[curr->target].insert(getModule()); } } void visitRefFunc(RefFunc* curr) { if (primaryFuncs.count(curr->func)) { - calledPrimaryFuncs[curr->func].insert(getModule()); + calledPrimaryToModules[curr->func].insert(getModule()); } } }; - CallCollector collector(primaryFuncs, calledPrimaryFuncs); + CallCollector collector(primaryFuncs, calledPrimaryToModules); collector.setModule(secondary); collector.walkFunction(func); }); diff --git a/src/ir/module-splitting.h b/src/ir/module-splitting.h index 8dc587a3bc2..5e03aa7902d 100644 --- a/src/ir/module-splitting.h +++ b/src/ir/module-splitting.h @@ -47,7 +47,12 @@ namespace wasm::ModuleSplitting { static const Name LOAD_SECONDARY_MODULE("__load_secondary_module"); struct Config { - // Module names to the functions that should be in the modules. + // Module names to the set of functions to split into that secondary module. + + // 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::map> moduleToFuncs; // Whether to import placeholder functions into the primary module that will // be called when a secondary function is called before the secondary module diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index 4d23844e2c7..921c62a5870 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -453,13 +453,13 @@ void multiSplitModule(const WasmSplitOptions& options) { secondary.name = Path::getBaseName(moduleName); } writeModule(secondary, moduleName, options); - if (options.symbolMap) { - writeSymbolMap(wasm, options.output + ".symbols"); - } - if (options.placeholderMap) { - writePlaceholderMap( - wasm, splitResults.placeholderMap, options.output + ".placeholders"); - } + } + if (options.symbolMap) { + writeSymbolMap(wasm, options.output + ".symbols"); + } + if (options.placeholderMap) { + writePlaceholderMap( + wasm, splitResults.placeholderMap, options.output + ".placeholders"); } writeModule(wasm, options.output, options); } From 4d2475e1d7941046bf21f0ce14599f10ed06e2cc Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Wed, 8 Oct 2025 08:46:59 +0000 Subject: [PATCH 03/11] Comment fix --- src/ir/module-splitting.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ir/module-splitting.h b/src/ir/module-splitting.h index 5e03aa7902d..ac4e7b73f8c 100644 --- a/src/ir/module-splitting.h +++ b/src/ir/module-splitting.h @@ -47,12 +47,10 @@ namespace wasm::ModuleSplitting { static const Name LOAD_SECONDARY_MODULE("__load_secondary_module"); struct Config { - // Module names to the set of functions to split into that secondary module. - - // 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. + // Module name to the set of functions to split into that 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::map> moduleToFuncs; // Whether to import placeholder functions into the primary module that will // be called when a secondary function is called before the secondary module From 566249c407d3e020ab77b6d58b8f3552cc318624 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 9 Oct 2025 15:14:00 -0700 Subject: [PATCH 04/11] Apply suggestions from code review Co-authored-by: Thomas Lively --- src/ir/module-splitting.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index cc45291fa33..da9d4d085a5 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -26,8 +26,8 @@ // 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 or -// different secondary module's table segments with references to imported +// 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 @@ -37,7 +37,7 @@ // // 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 using it. +// into each secondary module using it. // // 7. For each secondary module, create new active table segments in the // module that will replace all the placeholder function references in the @@ -297,7 +297,7 @@ struct ModuleSplitter { Module& primary; std::unordered_set primaryFuncs; - std::unordered_set allSecondryFuncs; + std::unordered_set allSecondaryFuncs; std::map> moduleToFuncs; std::unordered_map funcToModule; @@ -479,10 +479,10 @@ void ModuleSplitter::classifyFunctions() { // Move functions to each secondary module and create the reverse map for (auto& [mod, funcs] : config.moduleToFuncs) { - moduleToFuncs[mod]; + auto& funcs = moduleToFuncs[mod]; for (auto func : funcs) { - if (allSecondryFuncs.count(func)) { - moduleToFuncs[mod].insert(func); + if (allSecondaryFuncs.count(func)) { + funcs.insert(func); } } } @@ -625,7 +625,7 @@ void ModuleSplitter::indirectReferencesToSecondaryFunctions() { InsertOrderedMap> map; void visitRefFunc(RefFunc* curr) { - Module* curModule = getModule(); + 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 @@ -694,7 +694,7 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { } // 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* curModule = getModule(); + Module* currModule = getModule(); if (curModule != &parent.primary && curModule->name == parent.funcToModule.at(curr->target)) { return; @@ -723,7 +723,7 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { } void ModuleSplitter::exportImportCalledPrimaryFunctions() { - // Find primary functions called/referred in the secondary module. + // Find primary functions called/referred to from the secondary modules. using CalledPrimaryToModules = std::map>; for (auto& [mod, secondaryPtr] : secondaryPtrMap) { Module* secondary = secondaryPtr.get(); From d071d5c9192d4fdb00c2309f73b54a5f0c7b731b Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 9 Oct 2025 22:18:33 +0000 Subject: [PATCH 05/11] Fixing errors after apply suggestions --- src/ir/module-splitting.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index da9d4d085a5..dcec6cf6e93 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -26,9 +26,9 @@ // placeholder function (and eventually to the original secondary // function), allocating a new table slot for the placeholder if necessary. // -// 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. +// 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 @@ -473,14 +473,14 @@ void ModuleSplitter::classifyFunctions() { primaryFuncs.insert(func->name); } else { assert(func->name != primary.start && "The start function must be kept"); - allSecondryFuncs.insert(func->name); + allSecondaryFuncs.insert(func->name); } } // Move functions to each secondary module and create the reverse map - for (auto& [mod, funcs] : config.moduleToFuncs) { + for (auto& [mod, configFuncs] : config.moduleToFuncs) { auto& funcs = moduleToFuncs[mod]; - for (auto func : funcs) { + for (auto func : configFuncs) { if (allSecondaryFuncs.count(func)) { funcs.insert(func); } @@ -588,7 +588,7 @@ void ModuleSplitter::thunkExportedSecondaryFunctions() { Builder builder(primary); for (auto& ex : primary.exports) { if (ex->kind != ExternalKind::Function || - !allSecondryFuncs.count(*ex->getInternalName())) { + !allSecondaryFuncs.count(*ex->getInternalName())) { continue; } Name trampoline = getTrampoline(*ex->getInternalName()); @@ -630,9 +630,9 @@ void ModuleSplitter::indirectReferencesToSecondaryFunctions() { // 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.allSecondryFuncs.count(curr->func) && - (curModule == &parent.primary || - curModule->name != parent.funcToModule.at(curr->func))) { + if (parent.allSecondaryFuncs.count(curr->func) && + (currModule == &parent.primary || + currModule->name != parent.funcToModule.at(curr->func))) { map[curr->func].push_back(curr); } } @@ -689,14 +689,14 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { CallIndirector(ModuleSplitter& parent) : parent(parent) {} void visitCall(Call* curr) { // Return if the call's target is not in one of the secondary module. - if (!parent.allSecondryFuncs.count(curr->target)) { + if (!parent.allSecondaryFuncs.count(curr->target)) { return; } // 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 (curModule != &parent.primary && - curModule->name == parent.funcToModule.at(curr->target)) { + if (currModule != &parent.primary && + currModule->name == parent.funcToModule.at(curr->target)) { return; } @@ -780,7 +780,7 @@ void ModuleSplitter::setupTablePatching() { if (!ref) { return; } - if (!allSecondryFuncs.count(ref->func)) { + if (!allSecondaryFuncs.count(ref->func)) { return; } assert(table == tableManager.activeTable->name); From aece184d443ca62e2b72bea3fcfaf2590b02c417 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 9 Oct 2025 22:24:34 +0000 Subject: [PATCH 06/11] Make CallCollector calls one-liner --- src/ir/module-splitting.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index dcec6cf6e93..245e724d446 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -748,9 +748,8 @@ void ModuleSplitter::exportImportCalledPrimaryFunctions() { } } }; - CallCollector collector(primaryFuncs, calledPrimaryToModules); - collector.setModule(secondary); - collector.walkFunction(func); + CallCollector(primaryFuncs, calledPrimaryToModules) + .walkFunctionInModule(func, secondary); }); CalledPrimaryToModules calledPrimaryToModules; From 0c853c5ee3b2cce3211157ff2dff7b5b309241da Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 9 Oct 2025 22:04:18 +0000 Subject: [PATCH 07/11] Use vector instead of map to store secondary modules --- src/ir/module-splitting.cpp | 75 +++++++++++------------------ src/ir/module-splitting.h | 13 ++--- src/tools/wasm-split/wasm-split.cpp | 36 +++++++------- 3 files changed, 54 insertions(+), 70 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 245e724d446..43ad6ccde58 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -292,14 +292,13 @@ TableSlotManager::Slot TableSlotManager::getSlot(Name func, HeapType type) { struct ModuleSplitter { const Config& config; - std::map> secondaryPtrMap; + std::vector> secondaries; Module& primary; std::unordered_set primaryFuncs; std::unordered_set allSecondaryFuncs; - std::map> moduleToFuncs; - std::unordered_map funcToModule; + std::unordered_map funcToSecondaryIndex; TableSlotManager tableManager; @@ -320,8 +319,7 @@ struct ModuleSplitter { std::unordered_map trampolineMap; // Initialization helpers - static std::unique_ptr initSecondary(const Module& primary, - Name name); + static std::unique_ptr initSecondary(const Module& primary); static std::unordered_map initExportedPrimaryFuncs(const Module& primary); @@ -390,13 +388,11 @@ void ModuleSplitter::setupJSPI() { LOAD_SECONDARY_STATUS, LOAD_SECONDARY_STATUS, ExternalKind::Global)); } -std::unique_ptr ModuleSplitter::initSecondary(const Module& primary, - Name name) { +std::unique_ptr ModuleSplitter::initSecondary(const Module& primary) { // Create the secondary module and copy trivial properties. auto secondary = std::make_unique(); secondary->features = primary.features; secondary->hasFeaturesSection = primary.hasFeaturesSection; - secondary->name = name; return secondary; } @@ -459,7 +455,7 @@ void ModuleSplitter::classifyFunctions() { } std::unordered_set configSecondaryFuncs; - for (auto& [mod, funcs] : config.moduleToFuncs) { + for (auto& funcs : config.secondaryFuncs) { configSecondaryFuncs.insert(funcs.begin(), funcs.end()); } for (auto& func : primary.functions) { @@ -476,21 +472,6 @@ void ModuleSplitter::classifyFunctions() { allSecondaryFuncs.insert(func->name); } } - - // Move functions to each secondary module and create the reverse map - for (auto& [mod, configFuncs] : config.moduleToFuncs) { - auto& funcs = moduleToFuncs[mod]; - for (auto func : configFuncs) { - if (allSecondaryFuncs.count(func)) { - funcs.insert(func); - } - } - } - for (auto& [mod, funcs] : moduleToFuncs) { - for (auto func : funcs) { - funcToModule[func] = mod; - } - } } std::unordered_map @@ -541,14 +522,17 @@ void ModuleSplitter::exportImportFunction(Name funcName, void ModuleSplitter::moveSecondaryFunctions() { // Move the specified functions from the primary to the secondary modules. - for (auto& [mod, funcNames] : moduleToFuncs) { - auto [it, _] = secondaryPtrMap.emplace(mod, initSecondary(primary, mod)); - Module& secondary = *(it->second); + for (auto& funcNames : config.secondaryFuncs) { + auto secondary = initSecondary(primary); for (auto funcName : funcNames) { - auto* func = primary.getFunction(funcName); - ModuleUtils::copyFunction(func, secondary); - primary.removeFunction(funcName); + 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)); } } @@ -559,7 +543,7 @@ Name ModuleSplitter::getTrampoline(Name funcName) { } Builder builder(primary); - Module& secondary = *secondaryPtrMap[funcToModule[funcName]]; + Module& secondary = *secondaries.at(funcToSecondaryIndex.at(funcName)); auto* oldFunc = secondary.getFunction(funcName); auto trampoline = Names::getValidFunctionName( primary, std::string("trampoline_") + funcName.toString()); @@ -632,13 +616,14 @@ void ModuleSplitter::indirectReferencesToSecondaryFunctions() { // or a different secondary module) if (parent.allSecondaryFuncs.count(curr->func) && (currModule == &parent.primary || - currModule->name != parent.funcToModule.at(curr->func))) { + parent.secondaries.at(parent.funcToSecondaryIndex.at(curr->func)) + .get() != currModule)) { map[curr->func].push_back(curr); } } } gatherer(*this); gatherer.walkModule(&primary); - for (auto& [mod, secondaryPtr] : secondaryPtrMap) { + for (auto& secondaryPtr : secondaries) { gatherer.walkModule(secondaryPtr.get()); } @@ -696,14 +681,14 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { // because we don't need a call_indirect within the same module. Module* currModule = getModule(); if (currModule != &parent.primary && - currModule->name == parent.funcToModule.at(curr->target)) { + parent.secondaries.at(parent.funcToSecondaryIndex.at(curr->target)) + .get() == currModule) { return; } Builder builder(*getModule()); - Name moduleName = parent.funcToModule.at(curr->target); - auto* func = - parent.secondaryPtrMap.at(moduleName)->getFunction(curr->target); + 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( @@ -717,7 +702,7 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { }; CallIndirector callIndirector(*this); callIndirector.walkModule(&primary); - for (auto& [mod, secondaryPtr] : secondaryPtrMap) { + for (auto& secondaryPtr : secondaries) { callIndirector.walkModule(secondaryPtr.get()); } } @@ -725,7 +710,7 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { void ModuleSplitter::exportImportCalledPrimaryFunctions() { // Find primary functions called/referred to from the secondary modules. using CalledPrimaryToModules = std::map>; - for (auto& [mod, secondaryPtr] : secondaryPtrMap) { + for (auto& secondaryPtr : secondaries) { Module* secondary = secondaryPtr.get(); ModuleUtils::ParallelFunctionAnalysis callCollector( *secondary, @@ -785,7 +770,7 @@ void ModuleSplitter::setupTablePatching() { assert(table == tableManager.activeTable->name); placeholderMap[table][index] = ref->func; - Module& secondary = *secondaryPtrMap[funcToModule[ref->func]]; + Module& secondary = *secondaries.at(funcToSecondaryIndex.at(ref->func)); auto* secondaryFunc = secondary.getFunction(ref->func); moduleToReplacedElems[&secondary][index] = secondaryFunc; if (!config.usePlaceholders) { @@ -930,7 +915,7 @@ void ModuleSplitter::shareImportableItems() { // 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& [_, secondaryPtr] : secondaryPtrMap) { + for (auto& secondaryPtr : secondaries) { Module& secondary = *secondaryPtr; for (auto& memory : primary.memories) { auto secondaryMemory = ModuleUtils::copyMemory(memory.get(), secondary); @@ -977,7 +962,7 @@ 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. - for (auto& [_, secondaryPtr] : secondaryPtrMap) { + for (auto& secondaryPtr : secondaries) { PassRunner runner(secondaryPtr.get()); runner.add("remove-unused-module-elements"); runner.run(); @@ -988,11 +973,7 @@ void ModuleSplitter::removeUnusedSecondaryElements() { Results splitFunctions(Module& primary, const Config& config) { ModuleSplitter split(primary, config); - // Remove the temporary names we used during splitting - for (auto& [_, secondaryPtr] : split.secondaryPtrMap) { - secondaryPtr->name = Name(); - } - return {std::move(split.secondaryPtrMap), 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 ac4e7b73f8c..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 { - // Module name to the set of functions to split into that 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::map> moduleToFuncs; + // 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::map> secondaryPtrMap; + 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 921c62a5870..93759bad243 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.moduleToFuncs["secondary"] = 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.secondaryPtrMap["secondary"]; + auto& secondary = *splitResults.secondaries.begin(); adjustTableSize(wasm, options.initialTableSize); adjustTableSize(*secondary, options.initialTableSize, /*secondary=*/true); @@ -400,18 +400,25 @@ void multiSplitModule(const WasmSplitOptions& options) { ModuleSplitting::Config config; std::string line; bool newSection = true; - std::unordered_set moduleNames; + std::vector moduleNames; + std::unordered_set moduleNameSet; while (std::getline(manifest, line)) { if (line.empty()) { newSection = true; + if (currFuncs->empty()) { + std::cerr << "warning: Module " << currModule << " will be empty\n"; + } continue; } Name name = WasmBinaryReader::escape(line); if (newSection) { - currModule = Names::getValidName( - name, [&](Name n) { return moduleNames.find(n) == moduleNames.end(); }); - moduleNames.insert(currModule); - currFuncs = &config.moduleToFuncs[currModule]; + currModule = Names::getValidName(name, [&](Name n) { + return moduleNameSet.find(n) == moduleNameSet.end(); + }); + moduleNameSet.insert(currModule); + moduleNames.push_back(currModule); + config.secondaryFuncs.emplace_back(std::set()); + currFuncs = &config.secondaryFuncs.back(); newSection = false; continue; } @@ -435,19 +442,14 @@ void multiSplitModule(const WasmSplitOptions& options) { wasm.name = Path::getBaseName(options.output); } - for (auto& [mod, funcs] : config.moduleToFuncs) { - if (!options.quiet && funcs.empty()) { - std::cerr << "warning: Module " << mod << " will be empty\n"; - } - } auto splitResults = ModuleSplitting::splitFunctions(wasm, config); - for (auto& [mod, funcs] : config.moduleToFuncs) { - Module& secondary = *splitResults.secondaryPtrMap[mod]; - auto moduleName = options.outPrefix + mod.toString() + + 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.secondaryPtrMap[mod], - moduleName + ".symbols"); + writeSymbolMap(secondary, moduleName + ".symbols"); } if (options.emitModuleNames) { secondary.name = Path::getBaseName(moduleName); From 7e7fa664cea6124a3f295575fac84d7176f96c01 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 9 Oct 2025 22:47:37 +0000 Subject: [PATCH 08/11] Error out when there are duplicate module names --- src/tools/wasm-split/wasm-split.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index 93759bad243..48ba2f93043 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -412,9 +412,10 @@ void multiSplitModule(const WasmSplitOptions& options) { } Name name = WasmBinaryReader::escape(line); if (newSection) { - currModule = Names::getValidName(name, [&](Name n) { - return moduleNameSet.find(n) == moduleNameSet.end(); - }); + if (moduleNameSet.count(name)) { + Fatal() << "Module name " << name << " is listed more than once\n"; + } + currModule = name; moduleNameSet.insert(currModule); moduleNames.push_back(currModule); config.secondaryFuncs.emplace_back(std::set()); From d275cc4bb4c98bc23e943fabb2c5a1d37eccaf17 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 9 Oct 2025 23:08:39 +0000 Subject: [PATCH 09/11] Example test fix after "Use vector instead of map to store secondary modules" --- src/ir/module-splitting.cpp | 2 +- test/example/module-splitting.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 43ad6ccde58..57d99aeb8f2 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -559,7 +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); + // primaryFuncs.insert(trampoline); return trampoline; } diff --git a/test/example/module-splitting.cpp b/test/example/module-splitting.cpp index 0a6d99c98b7..96b4101fc97 100644 --- a/test/example/module-splitting.cpp +++ b/test/example/module-splitting.cpp @@ -51,10 +51,10 @@ void do_test(const std::set& keptFuncs, std::string&& module) { std::cout << "\n"; ModuleSplitting::Config config; - config.moduleToFuncs["secondary"] = std::move(splitFuncs); + config.secondaryFuncs.push_back(std::move(splitFuncs)); config.newExportPrefix = "%"; auto results = splitFunctions(*primary, config); - auto& secondary = results.secondaryPtrMap["secondary"]; + auto& secondary = *results.secondaries.begin(); std::cout << "After:\n"; std::cout << *primary.get(); @@ -476,12 +476,12 @@ void test_minimized_exports() { primary.addFunction(Builder::makeFunction("call", funcType, {}, callBody)); ModuleSplitting::Config config; - config.moduleToFuncs["secondary"] = {"call"}; + config.secondaryFuncs.push_back({"call"}); config.newExportPrefix = "%"; config.minimizeNewExportNames = true; auto results = splitFunctions(primary, config); - auto& secondary = results.secondaryPtrMap["secondary"]; + auto& secondary = *results.secondaries.begin(); std::cout << "Minimized names primary:\n"; std::cout << primary << "\n"; std::cout << "Minimized names secondary:\n"; From 46c907f6291fce7ef9c61cec6c818ac52995007e Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 9 Oct 2025 23:27:21 +0000 Subject: [PATCH 10/11] Revert an accidental change --- src/ir/module-splitting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 57d99aeb8f2..43ad6ccde58 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -559,7 +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); + primaryFuncs.insert(trampoline); return trampoline; } From cef1155713b4c55e591857b0a8a15aeaf2390f77 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 10 Oct 2025 14:46:46 -0700 Subject: [PATCH 11/11] Update src/tools/wasm-split/wasm-split.cpp Co-authored-by: Thomas Lively --- src/tools/wasm-split/wasm-split.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index 48ba2f93043..1e17b1d5b3a 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -405,7 +405,7 @@ void multiSplitModule(const WasmSplitOptions& options) { while (std::getline(manifest, line)) { if (line.empty()) { newSection = true; - if (currFuncs->empty()) { + if (currFuncs->empty() && !options.quiet) { std::cerr << "warning: Module " << currModule << " will be empty\n"; } continue;