From 0e2c9ff625a0d3f06efc4bb6ef2b9eef841f3931 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 6 Aug 2026 07:15:39 +0000 Subject: [PATCH 1/9] [wasm-split] Precompute ownership info (NFC) Given a module element name, many parts of the code queries for its owning modules (where the module element has to be placed) or secondary modules using that module element. This adds `OwnershipTracker`, which precomputes and manages that information. All calls to `getOwner` or `getUsingSecondaries` that required computations iterating on all secondary modules which can be as many as thousands, has been replaced with a call that simply returns prcomputed information. For the Jul 2026 version of the applications received from the Dart team, this reduces the running time of wasm-split by 17% for acx_gallery (30s -> 25s) and by 33% for essentials (230s -> 153s). Suggested in https://github.com/WebAssembly/binaryen/pull/8832#issuecomment-4714734931. --- src/ir/module-splitting.cpp | 324 +++++++++++++++++++++++------------- 1 file changed, 206 insertions(+), 118 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 21695f0bf07..c09f5d068ec 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -77,6 +77,7 @@ #include "ir/find_all.h" #include "ir/module-utils.h" #include "ir/names.h" +#include "support/small_vector.h" #include "support/stdckdint.h" #include "wasm-builder.h" #include "wasm.h" @@ -313,6 +314,130 @@ TableSlotManager::Slot TableSlotManager::getSlot(Name func, HeapType type) { return newSlot; } +// Module items ownership tracking + +// Struct containing sets of used module elements of a single module +struct UsedNames { + std::unordered_set globals; + std::unordered_set memories; + std::unordered_set tables; + std::unordered_set tags; + std::unordered_set dataSegments; + std::unordered_set elementSegments; +}; + +// A tracker that, given a module element, tracks which module is its owner, +// i.e., where the element should be placed, and the list of secondary modules +// using this element. +struct OwnershipTracker { + UsedNames primaryUsed; + std::vector secondaryUsed; + + struct ItemInfo { + UsedNames* owner = nullptr; + SmallVector usingSecondaries; + }; + + std::unordered_map tables; + std::unordered_map memories; + std::unordered_map globals; + std::unordered_map tags; + std::unordered_map dataSegments; + std::unordered_map elementSegments; + + std::unordered_map usedToSecondary; + + using FieldType = std::unordered_set UsedNames::*; + using MapType = std::unordered_map OwnershipTracker::*; + + void insert(Name name, UsedNames* owner, MapType mapField, FieldType field) { + (owner->*field).insert(name); + // Figure out which module the 'owner' is of this item. If it is used by a + // single secondary module, that secondary module is the owner. If it is + // used by the primary module or multiple secondary modules, the primary + // module is the owner. + auto [it, inserted] = (this->*mapField).insert({name, ItemInfo{owner, {}}}); + Module* mod = usedToSecondary[owner]; + if (inserted) { + if (mod) { + it->second.usingSecondaries.push_back(mod); + } + } else { + if (it->second.owner != owner) { + it->second.owner = &primaryUsed; + (primaryUsed.*field).insert(name); + } + if (mod) { + auto& vec = it->second.usingSecondaries; + if (std::find(vec.begin(), vec.end(), mod) == vec.end()) { + vec.push_back(mod); + } + } + } + } + + void build(const std::vector>& secondaries) { + usedToSecondary[&primaryUsed] = nullptr; + for (size_t i = 0; i < secondaryUsed.size(); ++i) { + usedToSecondary[&secondaryUsed[i]] = secondaries[i].get(); + } + + auto buildMap = [&](FieldType field, + std::unordered_map& map) { + for (auto& name : (primaryUsed.*field)) { + map[name].owner = &primaryUsed; + } + for (size_t i = 0; i < secondaryUsed.size(); ++i) { + auto& sec = secondaryUsed[i]; + auto* mod = secondaries[i].get(); + for (auto& name : (sec.*field)) { + auto [it, inserted] = map.insert({name, ItemInfo{&sec, {}}}); + it->second.usingSecondaries.push_back(mod); + if (!inserted) { + it->second.owner = &primaryUsed; + } + } + } + }; + buildMap(&UsedNames::tables, tables); + buildMap(&UsedNames::memories, memories); + buildMap(&UsedNames::globals, globals); + buildMap(&UsedNames::tags, tags); + buildMap(&UsedNames::dataSegments, dataSegments); + buildMap(&UsedNames::elementSegments, elementSegments); + } + + UsedNames* getOwner(Name name, + const std::unordered_map& map) { + auto it = map.find(name); + if (it != map.end()) { + return it->second.owner; + } + return nullptr; + } + + const SmallVector& + getUsingSecondaries(Name name, + const std::unordered_map& map) { + auto it = map.find(name); + if (it != map.end()) { + return it->second.usingSecondaries; + } + static SmallVector empty; + return empty; + } + + bool useEmpty(Name name, const std::unordered_map& map) { + return getOwner(name, map) == nullptr; + } + + bool usedBySingleSecondary(Name name, + const std::unordered_map& map) { + auto* owner = getOwner(name, map); + return owner != nullptr && owner != &primaryUsed; + } +}; + struct ModuleSplitter { const Config& config; std::vector> secondaries; @@ -359,17 +484,9 @@ struct ModuleSplitter { ExternalKind kind); Name getTrampoline(Name funcName); - struct UsedNames { - std::unordered_set globals; - std::unordered_set memories; - std::unordered_set tables; - std::unordered_set tags; - std::unordered_set dataSegments; - std::unordered_set elementSegments; - }; - using PrimarySecondaryUsedNames = - std::pair>; - PrimarySecondaryUsedNames computeUsedNames(); + OwnershipTracker tracker; + + void computeUsedNames(); // Main splitting steps void classifyFunctions(); @@ -649,12 +766,18 @@ void ModuleSplitter::thunkExportedSecondaryFunctions() { } } -ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { +void ModuleSplitter::computeUsedNames() { + UsedNames& primaryUsed = tracker.primaryUsed; + std::vector& secondaryUsed = tracker.secondaryUsed; + struct NameCollector : public PostWalker> { UsedNames& used; - NameCollector(UsedNames& used) : used(used) {} + OwnershipTracker* tracker = nullptr; + + NameCollector(UsedNames& used, OwnershipTracker* tracker = nullptr) + : used(used), tracker(tracker) {} void visitExpression(Expression* curr) { #define DELEGATE_ID curr->_id @@ -670,26 +793,36 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { #define DELEGATE_FIELD_SCOPE_NAME_USE(id, field) #define DELEGATE_FIELD_ADDRESS(id, field) +// In the initial building phase, we just directly add to a UsedName struct. +// After OwnershipTracker is constructed, we all its insert() method to update +// owner modules and using secondary modules correctly. +#define ADD_ITEM(FIELD, VAL) \ + if (tracker) { \ + tracker->insert(VAL, &used, &OwnershipTracker::FIELD, &UsedNames::FIELD); \ + } else { \ + used.FIELD.insert(VAL); \ + } + #define DELEGATE_FIELD_NAME_KIND(id, field, kind) \ if (cast->field.is()) { \ switch (kind) { \ case ModuleItemKind::Table: \ - used.tables.insert(cast->field); \ + ADD_ITEM(tables, cast->field); \ break; \ case ModuleItemKind::Memory: \ - used.memories.insert(cast->field); \ + ADD_ITEM(memories, cast->field); \ break; \ case ModuleItemKind::Global: \ - used.globals.insert(cast->field); \ + ADD_ITEM(globals, cast->field); \ break; \ case ModuleItemKind::Tag: \ - used.tags.insert(cast->field); \ + ADD_ITEM(tags, cast->field); \ break; \ case ModuleItemKind::DataSegment: \ - used.dataSegments.insert(cast->field); \ + ADD_ITEM(dataSegments, cast->field); \ break; \ case ModuleItemKind::ElementSegment: \ - used.elementSegments.insert(cast->field); \ + ADD_ITEM(elementSegments, cast->field); \ break; \ case ModuleItemKind::Function: \ case ModuleItemKind::Invalid: \ @@ -698,6 +831,7 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { } #include "wasm-delegations-fields.def" +#undef ADD_ITEM } }; @@ -713,8 +847,7 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { return used; }; - UsedNames primaryUsed = scanModule(primary); - std::vector secondaryUsed; + primaryUsed = scanModule(primary); for (auto& secondaryPtr : secondaries) { secondaryUsed.push_back(scanModule(*secondaryPtr)); } @@ -769,27 +902,7 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { } } - // Given a name and a module item kind (field pointer), find which module - // "owns" it. If it is used by exactly one secondary module, that secondary - // module is the owner. If it is used by the primary module or multiple - // secondary modules, the primary module is the owner. If it is not used, - // returns nullptr. - auto getOwner = [&](Name name, auto UsedNames::* field) -> UsedNames* { - UsedNames* owner = nullptr; - if ((primaryUsed.*field).contains(name)) { - owner = &primaryUsed; - } - for (auto& sec : secondaryUsed) { - if ((sec.*field).contains(name)) { - if (owner) { - owner = &primaryUsed; - break; - } - owner = &sec; - } - } - return owner; - }; + tracker.build(secondaries); // Scan table initializers into their owning modules. If a table is used by a // single secondary module, its initializer dependencies are marked as "used" @@ -800,8 +913,8 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { if (!table->init) { continue; } - if (UsedNames* owner = getOwner(table->name, &UsedNames::tables)) { - NameCollector(*owner).walk(table->init); + if (UsedNames* owner = tracker.getOwner(table->name, tracker.tables)) { + NameCollector(*owner, &tracker).walk(table->init); } } } @@ -850,13 +963,16 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { return false; }; +#define ADD_ITEM_TO_TRACKER(FIELD, VAL) \ + tracker.insert(VAL, owner, &OwnershipTracker::FIELD, &UsedNames::FIELD) + // Iterate on active data and element segments. If its table or memory is // used by a single secondary module, mark it "used" there. Only scan its // 'offset' or 'data'(in case of ElementSegment) and add it to that module's // used only when it is a sole secondary owner. If not assign it to the // primary module and scan it there. ModuleUtils::iterActiveDataSegments(primary, [&](DataSegment* segment) { - UsedNames* owner = getOwner(segment->memory, &UsedNames::memories); + UsedNames* owner = tracker.getOwner(segment->memory, tracker.memories); // Trapping segments should be kept in the primary module because they are // evaluated at the instantiation time. if (mayTrap(segment)) { @@ -865,15 +981,15 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { if (!owner) { return; } - owner->dataSegments.insert(segment->name); - owner->memories.insert(segment->memory); + ADD_ITEM_TO_TRACKER(dataSegments, segment->name); + ADD_ITEM_TO_TRACKER(memories, segment->memory); if (segment->offset) { - NameCollector(*owner).walk(segment->offset); + NameCollector(*owner, &tracker).walk(segment->offset); } }); ModuleUtils::iterActiveElementSegments(primary, [&](ElementSegment* segment) { - UsedNames* owner = getOwner(segment->table, &UsedNames::tables); + UsedNames* owner = tracker.getOwner(segment->table, tracker.tables); // If placeholders are NOT used, and if all functions in an element segment // belong to a single secondary module, we can move the segment to that @@ -920,13 +1036,13 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { if (!owner) { return; } - owner->elementSegments.insert(segment->name); - owner->tables.insert(segment->table); + ADD_ITEM_TO_TRACKER(elementSegments, segment->name); + ADD_ITEM_TO_TRACKER(tables, segment->table); if (segment->offset) { - NameCollector(*owner).walk(segment->offset); + NameCollector(*owner, &tracker).walk(segment->offset); } for (auto* item : segment->data) { - NameCollector(*owner).walk(item); + NameCollector(*owner, &tracker).walk(item); } }); @@ -938,7 +1054,7 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { if (segment->isPassive() && primaryUsed.elementSegments.contains(segment->name)) { for (auto* item : segment->data) { - NameCollector(primaryUsed).walk(item); + NameCollector(primaryUsed, &tracker).walk(item); } } } @@ -955,32 +1071,16 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { if (!global->init) { continue; } - if (UsedNames* owner = getOwner(global->name, &UsedNames::globals)) { + if (UsedNames* owner = tracker.getOwner(global->name, tracker.globals)) { for (auto* get : FindAll(global->init).list) { - owner->globals.insert(get->name); + ADD_ITEM_TO_TRACKER(globals, get->name); } } } - - return std::make_pair(primaryUsed, secondaryUsed); } void ModuleSplitter::shareImportableItems() { - auto usedNames = computeUsedNames(); - auto& primaryUsed = usedNames.first; - auto& secondaryUsed = usedNames.second; - - // Given a name and module item kind, returns the list of secondary modules - // using that name - auto getUsingSecondaries = [&](const Name& name, auto UsedNames::* field) { - std::vector usingModules; - for (size_t i = 0; i < secondaries.size(); ++i) { - if ((secondaryUsed[i].*field).contains(name)) { - usingModules.push_back(secondaries[i].get()); - } - } - return usingModules; - }; + computeUsedNames(); // Share module items with secondary modules. // 1. Only share an item with the modules that use it @@ -991,18 +1091,16 @@ void ModuleSplitter::shareImportableItems() { std::vector memoriesToRemove; for (auto& memory : primary.memories) { - auto usingSecondaries = - getUsingSecondaries(memory->name, &UsedNames::memories); - bool inPrimary = primaryUsed.memories.contains(memory->name); - - if (!inPrimary && usingSecondaries.empty()) { + if (tracker.useEmpty(memory->name, tracker.memories)) { memoriesToRemove.push_back(memory->name); - } else if (!inPrimary && usingSecondaries.size() == 1) { - auto* secondary = usingSecondaries[0]; + } else if (tracker.usedBySingleSecondary(memory->name, tracker.memories)) { + auto* secondary = + tracker.getUsingSecondaries(memory->name, tracker.memories)[0]; ModuleUtils::copyMemory(memory.get(), *secondary); memoriesToRemove.push_back(memory->name); } else { - for (auto* secondary : usingSecondaries) { + for (auto* secondary : + tracker.getUsingSecondaries(memory->name, tracker.memories)) { auto* secondaryMemory = ModuleUtils::copyMemory(memory.get(), *secondary); makeImportExport( @@ -1016,19 +1114,17 @@ void ModuleSplitter::shareImportableItems() { std::vector tablesToRemove; for (auto& table : primary.tables) { - auto usingSecondaries = - getUsingSecondaries(table->name, &UsedNames::tables); - bool inPrimary = primaryUsed.tables.contains(table->name); - - if (!inPrimary && usingSecondaries.empty()) { + if (tracker.useEmpty(table->name, tracker.tables)) { tablesToRemove.push_back(table->name); - } else if (!inPrimary && usingSecondaries.size() == 1) { - auto* secondary = usingSecondaries[0]; + } else if (tracker.usedBySingleSecondary(table->name, tracker.tables)) { + auto* secondary = + tracker.getUsingSecondaries(table->name, tracker.tables)[0]; assert(!secondary->getTableOrNull(table->name)); ModuleUtils::copyTable(table.get(), *secondary); tablesToRemove.push_back(table->name); } else { - for (auto* secondary : usingSecondaries) { + for (auto* secondary : + tracker.getUsingSecondaries(table->name, tracker.tables)) { auto* secondaryTable = ModuleUtils::copyTable(table.get(), *secondary); makeImportExport(*table, *secondaryTable, "table", ExternalKind::Table); } @@ -1045,18 +1141,16 @@ void ModuleSplitter::shareImportableItems() { "TODO: add wrapper functions for disallowed mutable globals"); } - auto usingSecondaries = - getUsingSecondaries(global->name, &UsedNames::globals); - bool inPrimary = primaryUsed.globals.contains(global->name); - - if (!inPrimary && usingSecondaries.empty()) { + if (tracker.useEmpty(global->name, tracker.globals)) { globalsToRemove.push_back(global->name); - } else if (!inPrimary && usingSecondaries.size() == 1) { - auto* secondary = usingSecondaries[0]; + } else if (tracker.usedBySingleSecondary(global->name, tracker.globals)) { + auto* secondary = + tracker.getUsingSecondaries(global->name, tracker.globals)[0]; ModuleUtils::copyGlobal(global.get(), *secondary); globalsToRemove.push_back(global->name); } else { - for (auto* secondary : usingSecondaries) { + for (auto* secondary : + tracker.getUsingSecondaries(global->name, tracker.globals)) { auto* secondaryGlobal = ModuleUtils::copyGlobal(global.get(), *secondary); makeImportExport( @@ -1070,17 +1164,15 @@ void ModuleSplitter::shareImportableItems() { std::vector tagsToRemove; for (auto& tag : primary.tags) { - auto usingSecondaries = getUsingSecondaries(tag->name, &UsedNames::tags); - bool inPrimary = primaryUsed.tags.contains(tag->name); - - if (!inPrimary && usingSecondaries.empty()) { + if (tracker.useEmpty(tag->name, tracker.tags)) { tagsToRemove.push_back(tag->name); - } else if (!inPrimary && usingSecondaries.size() == 1) { - auto* secondary = usingSecondaries[0]; + } else if (tracker.usedBySingleSecondary(tag->name, tracker.tags)) { + auto* secondary = tracker.getUsingSecondaries(tag->name, tracker.tags)[0]; ModuleUtils::copyTag(tag.get(), *secondary); tagsToRemove.push_back(tag->name); } else { - for (auto* secondary : usingSecondaries) { + for (auto* secondary : + tracker.getUsingSecondaries(tag->name, tracker.tags)) { auto* secondaryTag = ModuleUtils::copyTag(tag.get(), *secondary); makeImportExport(*tag, *secondaryTag, "tag", ExternalKind::Tag); } @@ -1096,14 +1188,12 @@ void ModuleSplitter::shareImportableItems() { std::vector dataSegmentsToRemove; for (auto& dataSegment : primary.dataSegments) { - auto usingSecondaries = - getUsingSecondaries(dataSegment->name, &UsedNames::dataSegments); - bool inPrimary = primaryUsed.dataSegments.contains(dataSegment->name); - - if (!inPrimary && usingSecondaries.empty()) { + if (tracker.useEmpty(dataSegment->name, tracker.dataSegments)) { dataSegmentsToRemove.push_back(dataSegment->name); - } else if (!inPrimary && usingSecondaries.size() == 1) { - auto* secondary = usingSecondaries[0]; + } else if (tracker.usedBySingleSecondary(dataSegment->name, + tracker.dataSegments)) { + auto* secondary = + tracker.getUsingSecondaries(dataSegment->name, tracker.dataSegments)[0]; ModuleUtils::copyDataSegment(dataSegment.get(), *secondary); dataSegmentsToRemove.push_back(dataSegment->name); } @@ -1114,14 +1204,12 @@ void ModuleSplitter::shareImportableItems() { std::vector elementSegmentsToRemove; for (auto& elementSegment : primary.elementSegments) { - auto usingSecondaries = - getUsingSecondaries(elementSegment->name, &UsedNames::elementSegments); - bool inPrimary = primaryUsed.elementSegments.contains(elementSegment->name); - - if (!inPrimary && usingSecondaries.empty()) { + if (tracker.useEmpty(elementSegment->name, tracker.elementSegments)) { elementSegmentsToRemove.push_back(elementSegment->name); - } else if (!inPrimary && usingSecondaries.size() == 1) { - auto* secondary = usingSecondaries[0]; + } else if (tracker.usedBySingleSecondary(elementSegment->name, + tracker.elementSegments)) { + auto* secondary = tracker.getUsingSecondaries(elementSegment->name, + tracker.elementSegments)[0]; ModuleUtils::copyElementSegment(elementSegment.get(), *secondary); elementSegmentsToRemove.push_back(elementSegment->name); } From 7d0d87d5207873f420860c7792da3b9c82b19cc2 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 14 Aug 2026 14:25:41 -0700 Subject: [PATCH 2/9] Update src/ir/module-splitting.cpp Co-authored-by: Thomas Lively --- 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 c09f5d068ec..58c98b5ce03 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -796,7 +796,7 @@ void ModuleSplitter::computeUsedNames() { // In the initial building phase, we just directly add to a UsedName struct. // After OwnershipTracker is constructed, we all its insert() method to update // owner modules and using secondary modules correctly. -#define ADD_ITEM(FIELD, VAL) \ +#define ADD_ITEM(field, val) \ if (tracker) { \ tracker->insert(VAL, &used, &OwnershipTracker::FIELD, &UsedNames::FIELD); \ } else { \ From 8d9455b254b79294d7b5aa5f5780710c309111cd Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 14 Aug 2026 21:29:14 +0000 Subject: [PATCH 3/9] Remove usedToSecondary map --- src/ir/module-splitting.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 58c98b5ce03..f69c3dc8772 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -345,7 +345,7 @@ struct OwnershipTracker { std::unordered_map dataSegments; std::unordered_map elementSegments; - std::unordered_map usedToSecondary; + const std::vector>* secondaries = nullptr; using FieldType = std::unordered_set UsedNames::*; using MapType = std::unordered_map OwnershipTracker::*; @@ -357,30 +357,31 @@ struct OwnershipTracker { // used by the primary module or multiple secondary modules, the primary // module is the owner. auto [it, inserted] = (this->*mapField).insert({name, ItemInfo{owner, {}}}); - Module* mod = usedToSecondary[owner]; + Module* secondary = nullptr; + if (owner != &primaryUsed) { + size_t index = owner - secondaryUsed.data(); + secondary = (*secondaries)[index].get(); + } if (inserted) { - if (mod) { - it->second.usingSecondaries.push_back(mod); + if (secondary) { + it->second.usingSecondaries.push_back(secondary); } } else { if (it->second.owner != owner) { it->second.owner = &primaryUsed; (primaryUsed.*field).insert(name); } - if (mod) { + if (secondary) { auto& vec = it->second.usingSecondaries; - if (std::find(vec.begin(), vec.end(), mod) == vec.end()) { - vec.push_back(mod); + if (std::find(vec.begin(), vec.end(), secondary) == vec.end()) { + vec.push_back(secondary); } } } } void build(const std::vector>& secondaries) { - usedToSecondary[&primaryUsed] = nullptr; - for (size_t i = 0; i < secondaryUsed.size(); ++i) { - usedToSecondary[&secondaryUsed[i]] = secondaries[i].get(); - } + this->secondaries = &secondaries; auto buildMap = [&](FieldType field, std::unordered_map& map) { From 86f57f48c00f4de125a4c7c83140f17027df5cd8 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 14 Aug 2026 21:33:10 +0000 Subject: [PATCH 4/9] FIELD->field, VAL->val --- src/ir/module-splitting.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index f69c3dc8772..4844232f1b5 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -799,9 +799,9 @@ void ModuleSplitter::computeUsedNames() { // owner modules and using secondary modules correctly. #define ADD_ITEM(field, val) \ if (tracker) { \ - tracker->insert(VAL, &used, &OwnershipTracker::FIELD, &UsedNames::FIELD); \ + tracker->insert(val, &used, &OwnershipTracker::field, &UsedNames::field); \ } else { \ - used.FIELD.insert(VAL); \ + used.field.insert(val); \ } #define DELEGATE_FIELD_NAME_KIND(id, field, kind) \ From 89b8e08d624265788693b5085e5c621a789e29be Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 14 Aug 2026 21:58:54 +0000 Subject: [PATCH 5/9] Rename a few variables --- src/ir/module-splitting.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 4844232f1b5..d41037011dd 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -389,11 +389,11 @@ struct OwnershipTracker { map[name].owner = &primaryUsed; } for (size_t i = 0; i < secondaryUsed.size(); ++i) { - auto& sec = secondaryUsed[i]; - auto* mod = secondaries[i].get(); - for (auto& name : (sec.*field)) { - auto [it, inserted] = map.insert({name, ItemInfo{&sec, {}}}); - it->second.usingSecondaries.push_back(mod); + auto& secUsed = secondaryUsed[i]; + auto* secondary = secondaries[i].get(); + for (auto& name : (secUsed.*field)) { + auto [it, inserted] = map.insert({name, ItemInfo{&secUsed, {}}}); + it->second.usingSecondaries.push_back(secondary); if (!inserted) { it->second.owner = &primaryUsed; } From 620886fd381371ff7f02c411172067f6f5149dd5 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 14 Aug 2026 22:02:36 +0000 Subject: [PATCH 6/9] More comments --- src/ir/module-splitting.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index d41037011dd..32cfdcafb34 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -350,6 +350,10 @@ struct OwnershipTracker { using FieldType = std::unordered_set UsedNames::*; using MapType = std::unordered_map OwnershipTracker::*; + // 'mapField' points to one of OwnershipTracker's maps, such as + // std::unordered_map globals; + // 'field' points to one of UsedName's sets, such as + // std::unordered_set globals; void insert(Name name, UsedNames* owner, MapType mapField, FieldType field) { (owner->*field).insert(name); // Figure out which module the 'owner' is of this item. If it is used by a @@ -383,6 +387,10 @@ struct OwnershipTracker { void build(const std::vector>& secondaries) { this->secondaries = &secondaries; + // Build initial maps of a module element Name to an ItemInfo for each + // module element type. + // 'field' points to one of UsedName's sets, such as + // std::unordered_set globals; auto buildMap = [&](FieldType field, std::unordered_map& map) { for (auto& name : (primaryUsed.*field)) { From 47b0a61888d9fb38740a9219db9636323e1ffdf1 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 14 Aug 2026 16:48:45 -0700 Subject: [PATCH 7/9] Apply suggestions from code review Co-authored-by: Thomas Lively --- src/ir/module-splitting.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 32cfdcafb34..b535acb9c44 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -356,7 +356,7 @@ struct OwnershipTracker { // std::unordered_set globals; void insert(Name name, UsedNames* owner, MapType mapField, FieldType field) { (owner->*field).insert(name); - // Figure out which module the 'owner' is of this item. If it is used by a + // Figure out which module is the 'owner' of this item. If it is used by a // single secondary module, that secondary module is the owner. If it is // used by the primary module or multiple secondary modules, the primary // module is the owner. @@ -803,7 +803,7 @@ void ModuleSplitter::computeUsedNames() { #define DELEGATE_FIELD_ADDRESS(id, field) // In the initial building phase, we just directly add to a UsedName struct. -// After OwnershipTracker is constructed, we all its insert() method to update +// After OwnershipTracker is constructed, we call its insert() method to update // owner modules and using secondary modules correctly. #define ADD_ITEM(field, val) \ if (tracker) { \ From f3b69005b3a2645f574557cfd92fca8d9ec89a1f Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sat, 15 Aug 2026 03:32:47 +0000 Subject: [PATCH 8/9] Eliminate two-phase approach in computeUsedNames --- src/ir/module-splitting.cpp | 85 +++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index b535acb9c44..6ef3a682d7d 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -776,16 +776,21 @@ void ModuleSplitter::thunkExportedSecondaryFunctions() { } void ModuleSplitter::computeUsedNames() { + tracker.secondaries = &secondaries; + tracker.secondaryUsed.resize(secondaries.size()); UsedNames& primaryUsed = tracker.primaryUsed; std::vector& secondaryUsed = tracker.secondaryUsed; +#define ADD_ITEM_TO_TRACKER(field, val, owner) \ + tracker.insert(val, owner, &OwnershipTracker::field, &UsedNames::field) + struct NameCollector : public PostWalker> { UsedNames& used; - OwnershipTracker* tracker = nullptr; + OwnershipTracker& tracker; - NameCollector(UsedNames& used, OwnershipTracker* tracker = nullptr) + NameCollector(UsedNames& used, OwnershipTracker& tracker) : used(used), tracker(tracker) {} void visitExpression(Expression* curr) { @@ -802,36 +807,26 @@ void ModuleSplitter::computeUsedNames() { #define DELEGATE_FIELD_SCOPE_NAME_USE(id, field) #define DELEGATE_FIELD_ADDRESS(id, field) -// In the initial building phase, we just directly add to a UsedName struct. -// After OwnershipTracker is constructed, we call its insert() method to update -// owner modules and using secondary modules correctly. -#define ADD_ITEM(field, val) \ - if (tracker) { \ - tracker->insert(val, &used, &OwnershipTracker::field, &UsedNames::field); \ - } else { \ - used.field.insert(val); \ - } - #define DELEGATE_FIELD_NAME_KIND(id, field, kind) \ if (cast->field.is()) { \ switch (kind) { \ case ModuleItemKind::Table: \ - ADD_ITEM(tables, cast->field); \ + ADD_ITEM_TO_TRACKER(tables, cast->field, &used); \ break; \ case ModuleItemKind::Memory: \ - ADD_ITEM(memories, cast->field); \ + ADD_ITEM_TO_TRACKER(memories, cast->field, &used); \ break; \ case ModuleItemKind::Global: \ - ADD_ITEM(globals, cast->field); \ + ADD_ITEM_TO_TRACKER(globals, cast->field, &used); \ break; \ case ModuleItemKind::Tag: \ - ADD_ITEM(tags, cast->field); \ + ADD_ITEM_TO_TRACKER(tags, cast->field, &used); \ break; \ case ModuleItemKind::DataSegment: \ - ADD_ITEM(dataSegments, cast->field); \ + ADD_ITEM_TO_TRACKER(dataSegments, cast->field, &used); \ break; \ case ModuleItemKind::ElementSegment: \ - ADD_ITEM(elementSegments, cast->field); \ + ADD_ITEM_TO_TRACKER(elementSegments, cast->field, &used); \ break; \ case ModuleItemKind::Function: \ case ModuleItemKind::Invalid: \ @@ -840,25 +835,22 @@ void ModuleSplitter::computeUsedNames() { } #include "wasm-delegations-fields.def" -#undef ADD_ITEM } }; // Given a module, collect names used in the module - auto scanModule = [&](Module& module) { - UsedNames used; - NameCollector collector(used); + auto scanModule = [&](Module& module, UsedNames& used) { + NameCollector collector(used, tracker); for (auto& func : module.functions) { if (!func->imported()) { collector.walk(func->body); } } - return used; }; - primaryUsed = scanModule(primary); - for (auto& secondaryPtr : secondaries) { - secondaryUsed.push_back(scanModule(*secondaryPtr)); + scanModule(primary, primaryUsed); + for (size_t i = 0; i < secondaries.size(); ++i) { + scanModule(*secondaries[i], secondaryUsed[i]); } // If primary module has exports, they are "used" in it. Secondary modules @@ -866,16 +858,16 @@ void ModuleSplitter::computeUsedNames() { for (auto& ex : primary.exports) { switch (ex->kind) { case ExternalKind::Global: - primaryUsed.globals.insert(*ex->getInternalName()); + ADD_ITEM_TO_TRACKER(globals, *ex->getInternalName(), &primaryUsed); break; case ExternalKind::Memory: - primaryUsed.memories.insert(*ex->getInternalName()); + ADD_ITEM_TO_TRACKER(memories, *ex->getInternalName(), &primaryUsed); break; case ExternalKind::Table: - primaryUsed.tables.insert(*ex->getInternalName()); + ADD_ITEM_TO_TRACKER(tables, *ex->getInternalName(), &primaryUsed); break; case ExternalKind::Tag: - primaryUsed.tags.insert(*ex->getInternalName()); + ADD_ITEM_TO_TRACKER(tags, *ex->getInternalName(), &primaryUsed); break; default: break; @@ -885,10 +877,10 @@ void ModuleSplitter::computeUsedNames() { // We need to assume the dispatch table and its base global are used in the // primary module, because we will create segments there later. if (tableManager.dispatchTable) { - primaryUsed.tables.insert(tableManager.dispatchTable->name); + ADD_ITEM_TO_TRACKER(tables, tableManager.dispatchTable->name, &primaryUsed); } if (tableManager.dispatchBase.global) { - primaryUsed.globals.insert(tableManager.dispatchBase.global); + ADD_ITEM_TO_TRACKER(globals, tableManager.dispatchBase.global, &primaryUsed); } // If custom-descirptors is enabled, global and table initializers can trap. @@ -899,20 +891,18 @@ void ModuleSplitter::computeUsedNames() { if (global->init && EffectAnalyzer(config.passOptions, primary, global->init) .hasUnremovableSideEffects()) { - primaryUsed.globals.insert(global->name); + ADD_ITEM_TO_TRACKER(globals, global->name, &primaryUsed); } } for (auto& table : primary.tables) { if (table->init && EffectAnalyzer(config.passOptions, primary, table->init) .hasUnremovableSideEffects()) { - primaryUsed.tables.insert(table->name); + ADD_ITEM_TO_TRACKER(tables, table->name, &primaryUsed); } } } - tracker.build(secondaries); - // Scan table initializers into their owning modules. If a table is used by a // single secondary module, its initializer dependencies are marked as "used" // in that secondary module. Otherwise, they are marked as used in the primary @@ -923,7 +913,7 @@ void ModuleSplitter::computeUsedNames() { continue; } if (UsedNames* owner = tracker.getOwner(table->name, tracker.tables)) { - NameCollector(*owner, &tracker).walk(table->init); + NameCollector(*owner, tracker).walk(table->init); } } } @@ -972,7 +962,7 @@ void ModuleSplitter::computeUsedNames() { return false; }; -#define ADD_ITEM_TO_TRACKER(FIELD, VAL) \ +#define ADD_ITEM_TO_TRACKER_TO_TRACKER(FIELD, VAL) \ tracker.insert(VAL, owner, &OwnershipTracker::FIELD, &UsedNames::FIELD) // Iterate on active data and element segments. If its table or memory is @@ -990,10 +980,10 @@ void ModuleSplitter::computeUsedNames() { if (!owner) { return; } - ADD_ITEM_TO_TRACKER(dataSegments, segment->name); - ADD_ITEM_TO_TRACKER(memories, segment->memory); + ADD_ITEM_TO_TRACKER(dataSegments, segment->name, owner); + ADD_ITEM_TO_TRACKER(memories, segment->memory, owner); if (segment->offset) { - NameCollector(*owner, &tracker).walk(segment->offset); + NameCollector(*owner, tracker).walk(segment->offset); } }); @@ -1045,13 +1035,13 @@ void ModuleSplitter::computeUsedNames() { if (!owner) { return; } - ADD_ITEM_TO_TRACKER(elementSegments, segment->name); - ADD_ITEM_TO_TRACKER(tables, segment->table); + ADD_ITEM_TO_TRACKER(elementSegments, segment->name, owner); + ADD_ITEM_TO_TRACKER(tables, segment->table, owner); if (segment->offset) { - NameCollector(*owner, &tracker).walk(segment->offset); + NameCollector(*owner, tracker).walk(segment->offset); } for (auto* item : segment->data) { - NameCollector(*owner, &tracker).walk(item); + NameCollector(*owner, tracker).walk(item); } }); @@ -1063,7 +1053,7 @@ void ModuleSplitter::computeUsedNames() { if (segment->isPassive() && primaryUsed.elementSegments.contains(segment->name)) { for (auto* item : segment->data) { - NameCollector(primaryUsed, &tracker).walk(item); + NameCollector(primaryUsed, tracker).walk(item); } } } @@ -1082,10 +1072,11 @@ void ModuleSplitter::computeUsedNames() { } if (UsedNames* owner = tracker.getOwner(global->name, tracker.globals)) { for (auto* get : FindAll(global->init).list) { - ADD_ITEM_TO_TRACKER(globals, get->name); + ADD_ITEM_TO_TRACKER(globals, get->name, owner); } } } +#undef ADD_ITEM_TO_TRACKER } void ModuleSplitter::shareImportableItems() { From d117127f2d573053f60e6a1b4bb9db0d38ccb383 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sat, 15 Aug 2026 04:15:48 +0000 Subject: [PATCH 9/9] Use template instead of ADD_ITEM macro --- src/ir/module-splitting.cpp | 66 +++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 6ef3a682d7d..0d11829e034 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -350,11 +350,33 @@ struct OwnershipTracker { using FieldType = std::unordered_set UsedNames::*; using MapType = std::unordered_map OwnershipTracker::*; + template void insert(Name name, UsedNames* owner) { + if constexpr (std::is_same_v) { + insertImpl(name, owner, &OwnershipTracker::tables, &UsedNames::tables); + } else if constexpr (std::is_same_v) { + insertImpl( + name, owner, &OwnershipTracker::memories, &UsedNames::memories); + } else if constexpr (std::is_same_v) { + insertImpl(name, owner, &OwnershipTracker::globals, &UsedNames::globals); + } else if constexpr (std::is_same_v) { + insertImpl(name, owner, &OwnershipTracker::tags, &UsedNames::tags); + } else if constexpr (std::is_same_v) { + insertImpl( + name, owner, &OwnershipTracker::dataSegments, &UsedNames::dataSegments); + } else if constexpr (std::is_same_v) { + insertImpl(name, + owner, + &OwnershipTracker::elementSegments, + &UsedNames::elementSegments); + } + } + // 'mapField' points to one of OwnershipTracker's maps, such as // std::unordered_map globals; // 'field' points to one of UsedName's sets, such as // std::unordered_set globals; - void insert(Name name, UsedNames* owner, MapType mapField, FieldType field) { + void + insertImpl(Name name, UsedNames* owner, MapType mapField, FieldType field) { (owner->*field).insert(name); // Figure out which module is the 'owner' of this item. If it is used by a // single secondary module, that secondary module is the owner. If it is @@ -781,9 +803,6 @@ void ModuleSplitter::computeUsedNames() { UsedNames& primaryUsed = tracker.primaryUsed; std::vector& secondaryUsed = tracker.secondaryUsed; -#define ADD_ITEM_TO_TRACKER(field, val, owner) \ - tracker.insert(val, owner, &OwnershipTracker::field, &UsedNames::field) - struct NameCollector : public PostWalker> { @@ -811,22 +830,22 @@ void ModuleSplitter::computeUsedNames() { if (cast->field.is()) { \ switch (kind) { \ case ModuleItemKind::Table: \ - ADD_ITEM_TO_TRACKER(tables, cast->field, &used); \ + tracker.insert(cast->field, &used); \ break; \ case ModuleItemKind::Memory: \ - ADD_ITEM_TO_TRACKER(memories, cast->field, &used); \ + tracker.insert(cast->field, &used); \ break; \ case ModuleItemKind::Global: \ - ADD_ITEM_TO_TRACKER(globals, cast->field, &used); \ + tracker.insert(cast->field, &used); \ break; \ case ModuleItemKind::Tag: \ - ADD_ITEM_TO_TRACKER(tags, cast->field, &used); \ + tracker.insert(cast->field, &used); \ break; \ case ModuleItemKind::DataSegment: \ - ADD_ITEM_TO_TRACKER(dataSegments, cast->field, &used); \ + tracker.insert(cast->field, &used); \ break; \ case ModuleItemKind::ElementSegment: \ - ADD_ITEM_TO_TRACKER(elementSegments, cast->field, &used); \ + tracker.insert(cast->field, &used); \ break; \ case ModuleItemKind::Function: \ case ModuleItemKind::Invalid: \ @@ -858,16 +877,16 @@ void ModuleSplitter::computeUsedNames() { for (auto& ex : primary.exports) { switch (ex->kind) { case ExternalKind::Global: - ADD_ITEM_TO_TRACKER(globals, *ex->getInternalName(), &primaryUsed); + tracker.insert(*ex->getInternalName(), &primaryUsed); break; case ExternalKind::Memory: - ADD_ITEM_TO_TRACKER(memories, *ex->getInternalName(), &primaryUsed); + tracker.insert(*ex->getInternalName(), &primaryUsed); break; case ExternalKind::Table: - ADD_ITEM_TO_TRACKER(tables, *ex->getInternalName(), &primaryUsed); + tracker.insert
(*ex->getInternalName(), &primaryUsed); break; case ExternalKind::Tag: - ADD_ITEM_TO_TRACKER(tags, *ex->getInternalName(), &primaryUsed); + tracker.insert(*ex->getInternalName(), &primaryUsed); break; default: break; @@ -877,10 +896,10 @@ void ModuleSplitter::computeUsedNames() { // We need to assume the dispatch table and its base global are used in the // primary module, because we will create segments there later. if (tableManager.dispatchTable) { - ADD_ITEM_TO_TRACKER(tables, tableManager.dispatchTable->name, &primaryUsed); + tracker.insert
(tableManager.dispatchTable->name, &primaryUsed); } if (tableManager.dispatchBase.global) { - ADD_ITEM_TO_TRACKER(globals, tableManager.dispatchBase.global, &primaryUsed); + tracker.insert(tableManager.dispatchBase.global, &primaryUsed); } // If custom-descirptors is enabled, global and table initializers can trap. @@ -891,14 +910,14 @@ void ModuleSplitter::computeUsedNames() { if (global->init && EffectAnalyzer(config.passOptions, primary, global->init) .hasUnremovableSideEffects()) { - ADD_ITEM_TO_TRACKER(globals, global->name, &primaryUsed); + tracker.insert(global->name, &primaryUsed); } } for (auto& table : primary.tables) { if (table->init && EffectAnalyzer(config.passOptions, primary, table->init) .hasUnremovableSideEffects()) { - ADD_ITEM_TO_TRACKER(tables, table->name, &primaryUsed); + tracker.insert
(table->name, &primaryUsed); } } } @@ -980,8 +999,8 @@ void ModuleSplitter::computeUsedNames() { if (!owner) { return; } - ADD_ITEM_TO_TRACKER(dataSegments, segment->name, owner); - ADD_ITEM_TO_TRACKER(memories, segment->memory, owner); + tracker.insert(segment->name, owner); + tracker.insert(segment->memory, owner); if (segment->offset) { NameCollector(*owner, tracker).walk(segment->offset); } @@ -1035,8 +1054,8 @@ void ModuleSplitter::computeUsedNames() { if (!owner) { return; } - ADD_ITEM_TO_TRACKER(elementSegments, segment->name, owner); - ADD_ITEM_TO_TRACKER(tables, segment->table, owner); + tracker.insert(segment->name, owner); + tracker.insert
(segment->table, owner); if (segment->offset) { NameCollector(*owner, tracker).walk(segment->offset); } @@ -1072,11 +1091,10 @@ void ModuleSplitter::computeUsedNames() { } if (UsedNames* owner = tracker.getOwner(global->name, tracker.globals)) { for (auto* get : FindAll(global->init).list) { - ADD_ITEM_TO_TRACKER(globals, get->name, owner); + tracker.insert(get->name, owner); } } } -#undef ADD_ITEM_TO_TRACKER } void ModuleSplitter::shareImportableItems() {