From 4e9ea51bfab475beb390631f6d11fa5c3c5dc4f0 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sat, 8 Aug 2026 05:28:11 +0000 Subject: [PATCH 1/2] [wasm-split] Remove module elements in bulk (NFC) Previously we removed module elements one by one within a loop. But because `Module` stores a module element in both a map and a vector, removing a single module element using `removeModuleElement` is O(N), because it needs to shift all vector elements after it: https://github.com/WebAssembly/binaryen/blob/302396a676433152a32375a81d71e74687c97a1b/src/wasm/wasm.cpp#L1970-L1979 This removes module elements in bulk using `removeModuleElements`, which does the shifting only once. https://github.com/WebAssembly/binaryen/blob/302396a676433152a32375a81d71e74687c97a1b/src/wasm/wasm.cpp#L2004-L2018 Combining with #8986, acx_gallery's running time improved by 50.3% (30s -> 15s), and essentials by 60.8% (230s -> 90s). (for Jul 2026 version) I guess the main reason for the running time increase in #8441 was this O(N) `removeModuleElement` called within a loop after all. --- src/ir/module-splitting.cpp | 67 +++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 7a006a403f0..b08a6ea4006 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -1103,15 +1103,15 @@ void ModuleSplitter::shareImportableItems() { // the primary and secondary modules), export the item from the primary and // import it from the using secondary modules. - std::vector memoriesToRemove; + std::unordered_set memoriesToRemove; for (auto& memory : primary.memories) { if (tracker.useEmpty(memory->name, tracker.memories)) { - memoriesToRemove.push_back(memory->name); + memoriesToRemove.insert(memory->name); } 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); + memoriesToRemove.insert(memory->name); } else { for (auto* secondary : tracker.getUsingSecondaries(memory->name, tracker.memories)) { @@ -1122,20 +1122,19 @@ void ModuleSplitter::shareImportableItems() { } } } - for (auto& name : memoriesToRemove) { - primary.removeMemory(name); - } + primary.removeMemories( + [&](Memory* memory) { return memoriesToRemove.count(memory->name); }); - std::vector tablesToRemove; + std::unordered_set tablesToRemove; for (auto& table : primary.tables) { if (tracker.useEmpty(table->name, tracker.tables)) { - tablesToRemove.push_back(table->name); + tablesToRemove.insert(table->name); } 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); + tablesToRemove.insert(table->name); } else { for (auto* secondary : tracker.getUsingSecondaries(table->name, tracker.tables)) { @@ -1144,11 +1143,10 @@ void ModuleSplitter::shareImportableItems() { } } } - for (auto& name : tablesToRemove) { - primary.removeTable(name); - } + primary.removeTables( + [&](Table* table) { return tablesToRemove.count(table->name); }); - std::vector globalsToRemove; + std::unordered_set globalsToRemove; for (auto& global : primary.globals) { if (global->mutable_) { assert(primary.features.hasMutableGlobals() && @@ -1156,12 +1154,12 @@ void ModuleSplitter::shareImportableItems() { } if (tracker.useEmpty(global->name, tracker.globals)) { - globalsToRemove.push_back(global->name); + globalsToRemove.insert(global->name); } 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); + globalsToRemove.insert(global->name); } else { for (auto* secondary : tracker.getUsingSecondaries(global->name, tracker.globals)) { @@ -1172,18 +1170,17 @@ void ModuleSplitter::shareImportableItems() { } } } - for (auto& name : globalsToRemove) { - primary.removeGlobal(name); - } + primary.removeGlobals( + [&](Global* global) { return globalsToRemove.count(global->name); }); - std::vector tagsToRemove; + std::unordered_set tagsToRemove; for (auto& tag : primary.tags) { if (tracker.useEmpty(tag->name, tracker.tags)) { - tagsToRemove.push_back(tag->name); + tagsToRemove.insert(tag->name); } 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); + tagsToRemove.insert(tag->name); } else { for (auto* secondary : tracker.getUsingSecondaries(tag->name, tracker.tags)) { @@ -1192,45 +1189,43 @@ void ModuleSplitter::shareImportableItems() { } } } - for (auto& name : tagsToRemove) { - primary.removeTag(name); - } + primary.removeTags([&](Tag* tag) { return tagsToRemove.count(tag->name); }); // Move segments that are exclusively used in a secondary module. If not, do // nothing. (Segments cannot be imported / exported. They will be handled in // indirectReferencesToSecondaryFunctions.) - std::vector dataSegmentsToRemove; + std::unordered_set dataSegmentsToRemove; for (auto& dataSegment : primary.dataSegments) { if (tracker.useEmpty(dataSegment->name, tracker.dataSegments)) { - dataSegmentsToRemove.push_back(dataSegment->name); + dataSegmentsToRemove.insert(dataSegment->name); } 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); + dataSegmentsToRemove.insert(dataSegment->name); } } - for (auto& name : dataSegmentsToRemove) { - primary.removeDataSegment(name); - } + primary.removeDataSegments([&](DataSegment* dataSegment) { + return dataSegmentsToRemove.count(dataSegment->name); + }); - std::vector elementSegmentsToRemove; + std::unordered_set elementSegmentsToRemove; for (auto& elementSegment : primary.elementSegments) { if (tracker.useEmpty(elementSegment->name, tracker.elementSegments)) { - elementSegmentsToRemove.push_back(elementSegment->name); + elementSegmentsToRemove.insert(elementSegment->name); } 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); + elementSegmentsToRemove.insert(elementSegment->name); } } - for (auto& name : elementSegmentsToRemove) { - primary.removeElementSegment(name); - } + primary.removeElementSegments([&](ElementSegment* elementSegment) { + return elementSegmentsToRemove.count(elementSegment->name); + }); } void ModuleSplitter::indirectReferencesToSecondaryFunctions() { From e802edefefb2c92a3d648bddb1412e1330ac7586 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 14 Aug 2026 21:24:28 -0700 Subject: [PATCH 2/2] 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 6315788e7cb..23b10bbc348 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -1127,7 +1127,7 @@ void ModuleSplitter::shareImportableItems() { } } primary.removeMemories( - [&](Memory* memory) { return memoriesToRemove.count(memory->name); }); + [&](Memory* memory) { return memoriesToRemove.contains(memory->name); }); std::unordered_set tablesToRemove; for (auto& table : primary.tables) {