Skip to content

Commit 23f061b

Browse files
authored
[wasm-split] Move exclusive items to secondary modules (#8441)
When splitting a module, if non-function items (memories, tables, globals, tags) are exclusively used by a single secondary module, this moves them directly to that secondary module rather than exporting them from the primary module. When a global is moved, its initializer can contain `global.get` or `ref.func`s, creating dependences on other globals and functions. For now, this PR just exports all the dependences from the primary module to the secondary module. This will be improved by follow-up PRs. This PR reduces the size of the primary module for acx_gallery by 12.5%. Follow-up PRs will reduce it further. This also sadly increases wasm-split's running time on acx_gallery from 16.5s -> 24.7s, by 49%, due to more computations in `shareImportableItems`. --- The below is `wasm-objdump -h` result of the primary modules: - Before ``` Type start=0x0000000c end=0x00035e09 (size=0x00035dfd) count: 11192 Import start=0x00035e0e end=0x004bd669 (size=0x0048785b) count: 65720 Function start=0x004bd66d end=0x004d0519 (size=0x00012eac) count: 62890 Table start=0x004d051c end=0x004d4059 (size=0x00003b3d) count: 2921 Tag start=0x004d405b end=0x004d405f (size=0x00000004) count: 1 Global start=0x004d4063 end=0x00689ff8 (size=0x001b5f95) count: 80766 Export start=0x00689ffc end=0x0071b16c (size=0x00091170) count: 60877 Start start=0x0071b16e end=0x0071b170 (size=0x00000002) start: 828 Elem start=0x0071b174 end=0x00784fb9 (size=0x00069e45) count: 12303 DataCount start=0x00784fbb end=0x00784fbc (size=0x00000001) count: 1 Code start=0x00784fc1 end=0x009b4958 (size=0x0022f997) count: 62890 Data start=0x009b495c end=0x009d44e9 (size=0x0001fb8d) count: 1 ``` - After (This PR) ``` Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185 Import start=0x00035d49 end=0x003faf6f (size=0x003c5226) count: 56805 Function start=0x003faf73 end=0x0040de1f (size=0x00012eac) count: 62890 Table start=0x0040de22 end=0x0041195d (size=0x00003b3b) count: 2921 Tag start=0x0041195f end=0x00411963 (size=0x00000004) count: 1 Global start=0x00411967 end=0x005541c5 (size=0x0014285e) count: 47771 Export start=0x005541c9 end=0x005dfc2c (size=0x0008ba63) count: 59077 Start start=0x005dfc2e end=0x005dfc30 (size=0x00000002) start: 828 Elem start=0x005dfc34 end=0x00649a77 (size=0x00069e43) count: 12303 DataCount start=0x00649a79 end=0x00649a7a (size=0x00000001) count: 1 Code start=0x00649a7f end=0x00879385 (size=0x0022f906) count: 62890 Data start=0x00879389 end=0x00898f16 (size=0x0001fb8d) count: 1 ``` Follow-ups: #8442 and #8443
1 parent 11798b1 commit 23f061b

5 files changed

Lines changed: 406 additions & 103 deletions

File tree

src/ir/module-splitting.cpp

Lines changed: 176 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@
4747
// 8. Export globals, tags, tables, and memories from the primary module and
4848
// import them in the secondary modules.
4949
//
50-
// 9. Run RemoveUnusedModuleElements pass on the secondary modules in order to
51-
// remove unused imports.
52-
//
5350
// Functions can be used or referenced three ways in a WebAssembly module: they
5451
// can be exported, called, or referenced with ref.func. The above procedure
5552
// introduces a layer of indirection to each of those mechanisms that removes
@@ -77,10 +74,9 @@
7774
#include "ir/module-splitting.h"
7875
#include "asmjs/shared-constants.h"
7976
#include "ir/export-utils.h"
77+
#include "ir/find_all.h"
8078
#include "ir/module-utils.h"
8179
#include "ir/names.h"
82-
#include "ir/utils.h"
83-
#include "pass.h"
8480
#include "support/insert_ordered.h"
8581
#include "wasm-builder.h"
8682
#include "wasm.h"
@@ -963,13 +959,11 @@ void ModuleSplitter::shareImportableItems() {
963959
}
964960
};
965961

966-
for (auto& secondaryPtr : secondaries) {
967-
Module& secondary = *secondaryPtr;
968-
969-
// Collect names used in the secondary module
962+
// Given a module, collect names used in the module
963+
auto getUsedNames = [&](Module& module) {
970964
UsedNames used;
971965
ModuleUtils::ParallelFunctionAnalysis<UsedNames> nameCollector(
972-
secondary, [&](Function* func, UsedNames& used) {
966+
module, [&](Function* func, UsedNames& used) {
973967
if (!func->imported()) {
974968
NameCollector(used).walk(func->body);
975969
}
@@ -983,65 +977,201 @@ void ModuleSplitter::shareImportableItems() {
983977
}
984978

985979
NameCollector collector(used);
986-
collector.walkModuleCode(&secondary);
987-
for (auto& segment : secondary.dataSegments) {
980+
collector.walkModuleCode(&module);
981+
for (auto& segment : module.dataSegments) {
988982
if (segment->memory.is()) {
989983
used.memories.insert(segment->memory);
990984
}
991985
}
992-
for (auto& segment : secondary.elementSegments) {
986+
for (auto& segment : module.elementSegments) {
993987
if (segment->table.is()) {
994988
used.tables.insert(segment->table);
995989
}
996990
}
997991

998-
// Export module items that are used in the secondary module
999-
for (auto& memory : primary.memories) {
1000-
if (!used.memories.count(memory->name)) {
1001-
continue;
992+
// If primary module has exports, they are "used" in it. Secondary modules
993+
// don't have exports, so this only applies to the primary module.
994+
for (auto& ex : module.exports) {
995+
switch (ex->kind) {
996+
case ExternalKind::Global:
997+
used.globals.insert(*ex->getInternalName());
998+
break;
999+
case ExternalKind::Memory:
1000+
used.memories.insert(*ex->getInternalName());
1001+
break;
1002+
case ExternalKind::Table:
1003+
used.tables.insert(*ex->getInternalName());
1004+
break;
1005+
case ExternalKind::Tag:
1006+
used.tags.insert(*ex->getInternalName());
1007+
break;
1008+
default:
1009+
break;
1010+
}
1011+
}
1012+
1013+
return used;
1014+
};
1015+
1016+
UsedNames primaryUsed = getUsedNames(primary);
1017+
std::vector<UsedNames> secondaryUsed;
1018+
for (auto& secondaryPtr : secondaries) {
1019+
secondaryUsed.push_back(getUsedNames(*secondaryPtr));
1020+
}
1021+
1022+
// Compute globals referenced in other globals' initializers. Since globals
1023+
// can reference other globals, we must ensure that if a global is used in a
1024+
// module, all its dependencies are also marked as used.
1025+
auto computeDependentItems = [&](UsedNames& used) {
1026+
std::vector<Name> worklist(used.globals.begin(), used.globals.end());
1027+
for (auto name : worklist) {
1028+
// At this point all globals are still in the primary module, so this
1029+
// exists
1030+
auto* global = primary.getGlobal(name);
1031+
if (!global->imported() && global->init) {
1032+
for (auto* get : FindAll<GlobalGet>(global->init).list) {
1033+
used.globals.insert(get->name);
1034+
}
1035+
}
1036+
}
1037+
};
1038+
1039+
for (auto& used : secondaryUsed) {
1040+
computeDependentItems(used);
1041+
}
1042+
1043+
// Given a name and module item kind, returns the list of secondary modules
1044+
// using that name
1045+
auto getUsingSecondaries = [&](const Name& name, auto UsedNames::* field) {
1046+
std::vector<Module*> usingModules;
1047+
for (size_t i = 0; i < secondaries.size(); ++i) {
1048+
if ((secondaryUsed[i].*field).count(name)) {
1049+
usingModules.push_back(secondaries[i].get());
10021050
}
1003-
auto secondaryMemory = ModuleUtils::copyMemory(memory.get(), secondary);
1004-
makeImportExport(
1005-
*memory, *secondaryMemory, "memory", ExternalKind::Memory);
10061051
}
1052+
return usingModules;
1053+
};
10071054

1008-
for (auto& table : primary.tables) {
1009-
// 1. In case we copied this table to this secondary module in
1010-
// setupTablePatching(), secondary.getTableOrNull(table->name) is not
1011-
// null, and we need to export it.
1012-
// 2. As in the case with other module elements, if the table is used in
1013-
// the secondary module's instructions, we need to export it.
1014-
auto secondaryTable = secondary.getTableOrNull(table->name);
1015-
if (!secondaryTable && !used.tables.count(table->name)) {
1016-
continue;
1055+
// Share module items with secondary modules.
1056+
// 1. Only share an item with the modules that use it
1057+
// 2. If an item is used by only a single secondary module, move the item to
1058+
// that secondary module. If an item is used by multiple modules (including
1059+
// the primary and secondary modules), export the item from the primary and
1060+
// import it from the using secondary modules.
1061+
1062+
std::vector<Name> memoriesToRemove;
1063+
for (auto& memory : primary.memories) {
1064+
auto usingSecondaries =
1065+
getUsingSecondaries(memory->name, &UsedNames::memories);
1066+
bool usedInPrimary = primaryUsed.memories.count(memory->name);
1067+
1068+
if (!usedInPrimary && usingSecondaries.size() == 1) {
1069+
auto* secondary = usingSecondaries[0];
1070+
ModuleUtils::copyMemory(memory.get(), *secondary);
1071+
memoriesToRemove.push_back(memory->name);
1072+
} else {
1073+
for (auto* secondary : usingSecondaries) {
1074+
auto* secondaryMemory =
1075+
ModuleUtils::copyMemory(memory.get(), *secondary);
1076+
makeImportExport(
1077+
*memory, *secondaryMemory, "memory", ExternalKind::Memory);
10171078
}
1018-
if (!secondaryTable) {
1019-
secondaryTable = ModuleUtils::copyTable(table.get(), secondary);
1079+
}
1080+
}
1081+
for (auto& name : memoriesToRemove) {
1082+
primary.removeMemory(name);
1083+
}
1084+
1085+
std::vector<Name> tablesToRemove;
1086+
for (auto& table : primary.tables) {
1087+
auto usingSecondaries =
1088+
getUsingSecondaries(table->name, &UsedNames::tables);
1089+
bool usedInPrimary = primaryUsed.tables.count(table->name);
1090+
1091+
if (!usedInPrimary && usingSecondaries.size() == 1) {
1092+
auto* secondary = usingSecondaries[0];
1093+
// In case we copied this table to this secondary module in
1094+
// setupTablePatching(), !usedInPrimary can't be satisfied, because the
1095+
// primary module should have an element segment that refers to this
1096+
// table.
1097+
assert(!secondary->getTableOrNull(table->name));
1098+
ModuleUtils::copyTable(table.get(), *secondary);
1099+
tablesToRemove.push_back(table->name);
1100+
} else {
1101+
for (auto* secondary : usingSecondaries) {
1102+
// 1. In case we copied this table to this secondary module in
1103+
// setupTablePatching(), secondary.getTableOrNull(table->name) is not
1104+
// null, and we need to import it.
1105+
// 2. As in the case with other module elements, if the table is used in
1106+
// the secondary module's instructions, we need to export it.
1107+
auto secondaryTable = secondary->getTableOrNull(table->name);
1108+
if (!secondaryTable) {
1109+
secondaryTable = ModuleUtils::copyTable(table.get(), *secondary);
1110+
}
1111+
makeImportExport(*table, *secondaryTable, "table", ExternalKind::Table);
10201112
}
1021-
makeImportExport(*table, *secondaryTable, "table", ExternalKind::Table);
10221113
}
1114+
}
1115+
for (auto& name : tablesToRemove) {
1116+
primary.removeTable(name);
1117+
}
10231118

1024-
for (auto& global : primary.globals) {
1025-
if (!used.globals.count(global->name)) {
1026-
continue;
1119+
std::vector<Name> globalsToRemove;
1120+
for (auto& global : primary.globals) {
1121+
if (global->mutable_) {
1122+
assert(primary.features.hasMutableGlobals() &&
1123+
"TODO: add wrapper functions for disallowed mutable globals");
1124+
}
1125+
1126+
auto usingSecondaries =
1127+
getUsingSecondaries(global->name, &UsedNames::globals);
1128+
bool usedInPrimary = primaryUsed.globals.count(global->name);
1129+
if (!usedInPrimary && usingSecondaries.size() == 1) {
1130+
auto* secondary = usingSecondaries[0];
1131+
ModuleUtils::copyGlobal(global.get(), *secondary);
1132+
globalsToRemove.push_back(global->name);
1133+
// Import global initializer's ref.func dependences
1134+
if (global->init) {
1135+
for (auto* ref : FindAll<RefFunc>(global->init).list) {
1136+
// Here, ref->func is either a function the primary module, or a
1137+
// trampoline created in indirectReferencesToSecondaryFunctions in
1138+
// case the original function is in one of the secondaries.
1139+
assert(primary.getFunctionOrNull(ref->func));
1140+
exportImportFunction(ref->func, {secondary});
1141+
}
10271142
}
1028-
if (global->mutable_) {
1029-
assert(primary.features.hasMutableGlobals() &&
1030-
"TODO: add wrapper functions for disallowed mutable globals");
1143+
} else {
1144+
for (auto* secondary : usingSecondaries) {
1145+
auto* secondaryGlobal =
1146+
ModuleUtils::copyGlobal(global.get(), *secondary);
1147+
makeImportExport(
1148+
*global, *secondaryGlobal, "global", ExternalKind::Global);
10311149
}
1032-
auto* secondaryGlobal = ModuleUtils::copyGlobal(global.get(), secondary);
1033-
makeImportExport(
1034-
*global, *secondaryGlobal, "global", ExternalKind::Global);
10351150
}
1151+
}
1152+
for (auto& name : globalsToRemove) {
1153+
primary.removeGlobal(name);
1154+
}
1155+
1156+
std::vector<Name> tagsToRemove;
1157+
for (auto& tag : primary.tags) {
1158+
auto usingSecondaries = getUsingSecondaries(tag->name, &UsedNames::tags);
1159+
bool usedInPrimary = primaryUsed.tags.count(tag->name);
10361160

1037-
for (auto& tag : primary.tags) {
1038-
if (!used.tags.count(tag->name)) {
1039-
continue;
1161+
if (!usedInPrimary && usingSecondaries.size() == 1) {
1162+
auto* secondary = usingSecondaries[0];
1163+
ModuleUtils::copyTag(tag.get(), *secondary);
1164+
tagsToRemove.push_back(tag->name);
1165+
} else {
1166+
for (auto* secondary : usingSecondaries) {
1167+
auto* secondaryTag = ModuleUtils::copyTag(tag.get(), *secondary);
1168+
makeImportExport(*tag, *secondaryTag, "tag", ExternalKind::Tag);
10401169
}
1041-
auto* secondaryTag = ModuleUtils::copyTag(tag.get(), secondary);
1042-
makeImportExport(*tag, *secondaryTag, "tag", ExternalKind::Tag);
10431170
}
10441171
}
1172+
for (auto& name : tagsToRemove) {
1173+
primary.removeTag(name);
1174+
}
10451175
}
10461176

10471177
} // anonymous namespace
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
;; RUN: wasm-split %s -all -g -o1 %t.1.wasm -o2 %t.2.wasm --keep-funcs=keep
2+
;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY
3+
;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY
4+
5+
;; When a split global ($a here)'s initializer contains a ref.func of a split
6+
;; function, currently we create its trampoline in the primary module and export
7+
;; it.
8+
;; TODO Use $split in the secondary module directly in the split global
9+
10+
(module
11+
;; PRIMARY: (export "trampoline_split" (func $trampoline_split))
12+
13+
;; PRIMARY: (func $keep
14+
;; PRIMARY-NEXT: )
15+
(func $keep)
16+
17+
;; PRIMARY: (func $trampoline_split
18+
;; PRIMARY-NEXT: (call_indirect (type $0)
19+
;; PRIMARY-NEXT: (i32.const 0)
20+
;; PRIMARY-NEXT: )
21+
;; PRIMARY-NEXT: )
22+
23+
24+
;; SECONDARY: (import "primary" "trampoline_split" (func $trampoline_split (exact)))
25+
;; SECONDARY: (global $a funcref (ref.func $trampoline_split))
26+
(global $a funcref (ref.func $split))
27+
28+
;; SECONDARY: (func $split
29+
;; SECONDARY-NEXT: (drop
30+
;; SECONDARY-NEXT: (global.get $a)
31+
;; SECONDARY-NEXT: )
32+
;; SECONDARY-NEXT: )
33+
(func $split
34+
(drop
35+
(global.get $a)
36+
)
37+
)
38+
)

test/lit/wasm-split/selective-exports.wast

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)