Skip to content

Commit 9687f72

Browse files
authored
[wasm-split] Split globals' transitive global dependencies (#8442)
When a global is exclusively used by a secondary module, we can move it to the secondary module. If its initializer contains a `global.get` of another global, we exported it from the primary module to the secondary module, even if it may not be used anywhere else. When we split a global out to a secondary module, this PR computes the transitive dependency of the split global, and if those globals in the dependency are not used anywhere else in other modules, we move them to the secondary module as well. #8441 and this PR combined reduce the size of the primary module of acx_gallery by 42.6%. The running time of `wasm-split` hasn't really changed with this PR, compared to #8441. --- `wasm-objdump -h` result: - Before (#8441) ``` 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 ``` - After (This PR) ``` Type start=0x0000000c end=0x00035d44 (size=0x00035d38) count: 11185 Import start=0x00035d48 end=0x00132efc (size=0x000fd1b4) count: 32642 Function start=0x00132f00 end=0x00145dac (size=0x00012eac) count: 62890 Table start=0x00145daf end=0x001498ea (size=0x00003b3b) count: 2921 Tag start=0x001498ec end=0x001498f0 (size=0x00000004) count: 1 Global start=0x001498f4 end=0x00289e60 (size=0x0014056c) count: 47728 Export start=0x00289e64 end=0x002e99c1 (size=0x0005fb5d) count: 35861 Start start=0x002e99c3 end=0x002e99c5 (size=0x00000002) start: 828 Elem start=0x002e99c9 end=0x0035380c (size=0x00069e43) count: 12303 DataCount start=0x0035380e end=0x0035380f (size=0x00000001) count: 1 Code start=0x00353814 end=0x005830e5 (size=0x0022f8d1) count: 62890 Data start=0x005830e9 end=0x005a2c76 (size=0x0001fb8d) count: 1 ``` Note that while the decrease in the global section is small, we have a significant size decrease in the import and the export sections, because we used to import globals and export them just to relay those globals to the secondary modules. Follow-up: #8443 Fixes #7724.
1 parent c7a6976 commit 9687f72

2 files changed

Lines changed: 97 additions & 35 deletions

File tree

src/ir/module-splitting.cpp

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@
4545
// instantiation.
4646
//
4747
// 8. Export globals, tags, tables, and memories from the primary module and
48-
// import them in the secondary modules.
48+
// import them in the secondary modules. If possible, move those module
49+
// items instead to the secondary modules.
4950
//
5051
// Functions can be used or referenced three ways in a WebAssembly module: they
5152
// can be exported, called, or referenced with ref.func. The above procedure
@@ -583,6 +584,25 @@ Expression* ModuleSplitter::maybeLoadSecondary(Builder& builder,
583584
return builder.makeSequence(loadSecondary, callIndirect);
584585
}
585586

587+
// Helper to walk expressions in segments but NOT in globals.
588+
template<typename Walker>
589+
static void walkSegments(Walker& walker, Module* module) {
590+
walker.setModule(module);
591+
for (auto& curr : module->elementSegments) {
592+
if (curr->offset) {
593+
walker.walk(curr->offset);
594+
}
595+
for (auto* item : curr->data) {
596+
walker.walk(item);
597+
}
598+
}
599+
for (auto& curr : module->dataSegments) {
600+
if (curr->offset) {
601+
walker.walk(curr->offset);
602+
}
603+
}
604+
}
605+
586606
void ModuleSplitter::indirectReferencesToSecondaryFunctions() {
587607
// Turn references to secondary functions into references to thunks that
588608
// perform a direct call to the original referent. The direct calls in the
@@ -977,7 +997,19 @@ void ModuleSplitter::shareImportableItems() {
977997
}
978998

979999
NameCollector collector(used);
980-
collector.walkModuleCode(&module);
1000+
// We shouldn't use collector.walkModuleCode here, because we don't want to
1001+
// walk global initializers. At this point, all globals are still in the
1002+
// primary module, so if we walk global initializers here, other globals
1003+
// appearing in their initializers will all be marked as used in the primary
1004+
// module, which is not what we want.
1005+
//
1006+
// For example, we have (global $a i32 (global.get $b)). Because $a is at
1007+
// this point still in the primary module, $b will be marked as "used" in
1008+
// the primary module. But $a can be moved to a secondary module later if it
1009+
// is used exclusively by that module. Then $b can be also moved, in case it
1010+
// doesn't have other uses. But if it is marked as "used" in the primary
1011+
// module, it can't.
1012+
walkSegments(collector, &module);
9811013
for (auto& segment : module.dataSegments) {
9821014
if (segment->memory.is()) {
9831015
used.memories.insert(segment->memory);
@@ -1009,7 +1041,6 @@ void ModuleSplitter::shareImportableItems() {
10091041
break;
10101042
}
10111043
}
1012-
10131044
return used;
10141045
};
10151046

@@ -1019,25 +1050,33 @@ void ModuleSplitter::shareImportableItems() {
10191050
secondaryUsed.push_back(getUsedNames(*secondaryPtr));
10201051
}
10211052

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) {
1053+
// Compute the transitive closure of globals referenced in other globals'
1054+
// initializers. Since globals can reference other globals, we must ensure
1055+
// that if a global is used in a module, all its dependencies are also marked
1056+
// as used.
1057+
auto computeTransitiveGlobals = [&](UsedNames& used) {
10261058
std::vector<Name> worklist(used.globals.begin(), used.globals.end());
1027-
for (auto name : worklist) {
1059+
std::unordered_set<Name> visited(used.globals.begin(), used.globals.end());
1060+
while (!worklist.empty()) {
1061+
Name name = worklist.back();
1062+
worklist.pop_back();
10281063
// At this point all globals are still in the primary module, so this
10291064
// exists
10301065
auto* global = primary.getGlobal(name);
10311066
if (!global->imported() && global->init) {
10321067
for (auto* get : FindAll<GlobalGet>(global->init).list) {
1033-
used.globals.insert(get->name);
1068+
if (visited.insert(get->name).second) {
1069+
worklist.push_back(get->name);
1070+
used.globals.insert(get->name);
1071+
}
10341072
}
10351073
}
10361074
}
10371075
};
10381076

1077+
computeTransitiveGlobals(primaryUsed);
10391078
for (auto& used : secondaryUsed) {
1040-
computeDependentItems(used);
1079+
computeTransitiveGlobals(used);
10411080
}
10421081

10431082
// Given a name and module item kind, returns the list of secondary modules
@@ -1127,20 +1166,21 @@ void ModuleSplitter::shareImportableItems() {
11271166
getUsingSecondaries(global->name, &UsedNames::globals);
11281167
bool usedInPrimary = primaryUsed.globals.count(global->name);
11291168
if (!usedInPrimary && usingSecondaries.size() == 1) {
1169+
// We are moving this global to this secondary module
11301170
auto* secondary = usingSecondaries[0];
11311171
ModuleUtils::copyGlobal(global.get(), *secondary);
11321172
globalsToRemove.push_back(global->name);
11331173
// Import global initializer's ref.func dependences
11341174
if (global->init) {
11351175
for (auto* ref : FindAll<RefFunc>(global->init).list) {
1136-
// Here, ref->func is either a function the primary module, or a
1176+
// Here, ref->func is either a function in the primary module, or a
11371177
// trampoline created in indirectReferencesToSecondaryFunctions in
11381178
// case the original function is in one of the secondaries.
11391179
assert(primary.getFunctionOrNull(ref->func));
11401180
exportImportFunction(ref->func, {secondary});
11411181
}
11421182
}
1143-
} else {
1183+
} else { // We are NOT moving this global to the secondary module
11441184
for (auto* secondary : usingSecondaries) {
11451185
auto* secondaryGlobal =
11461186
ModuleUtils::copyGlobal(global.get(), *secondary);

test/lit/wasm-split/transitive-globals.wast

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,63 @@
33
;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY
44

55
;; Check that transitive dependencies in global initializers are correctly
6-
;; analyzed and exported from the primary module to the secondary module.
7-
;; TODO Move $b and $c to the secondary module
6+
;; analyzed and moved to the secondary module.
87

98
(module
10-
;; PRIMARY: (global $c i32 (i32.const 42))
11-
(global $c i32 (i32.const 42))
9+
;; There are two dependency chains: $a->$b->$c and $d->$e->$f. While all of
10+
;; $a, $b, and $c can be moved to the secondary module because all f them are
11+
;; used only there, $e is used in the primary module, preventing $e and $f
12+
;; from being moved to the secondary module.
1213

13-
;; $b depends on $c.
14-
;; PRIMARY: (global $b i32 (global.get $c))
14+
(global $c i32 (i32.const 42))
1515
(global $b i32 (global.get $c))
16+
(global $a i32 (global.get $b))
17+
18+
(global $f i32 (i32.const 42))
19+
(global $e i32 (global.get $f))
20+
(global $d i32 (global.get $e))
1621

17-
;; Globals $b is exported to the secondary module
18-
;; PRIMARY: (export "global" (global $b))
22+
;; PRIMARY: (global $f i32 (i32.const 42))
23+
;; PRIMARY: (global $e i32 (global.get $f))
1924

20-
;; Globals $b is imported from the primary module
21-
;; SECONDARY: (import "primary" "global" (global $b i32))
25+
;; PRIMARY: (export "global" (global $f))
26+
;; PRIMARY: (export "global_1" (global $e))
2227

23-
;; $a depends on $b. Since $a is exclusively used by the secondary module,
24-
;; it will be moved there. Its dependency $b should be exported from the
25-
;; primary module and imported into the secondary module.
28+
;; SECONDARY: (import "primary" "global" (global $f i32))
29+
;; SECONDARY: (import "primary" "global_1" (global $e i32))
30+
31+
;; SECONDARY: (global $c i32 (i32.const 42))
32+
;; SECONDARY: (global $b i32 (global.get $c))
2633
;; SECONDARY: (global $a i32 (global.get $b))
27-
(global $a i32 (global.get $b))
2834

29-
;; PRIMARY: (func $keep (result i32)
30-
;; PRIMARY-NEXT: (i32.const 0)
35+
;; SECONDARY: (global $d i32 (global.get $e))
36+
37+
;; PRIMARY: (func $keep
38+
;; PRIMARY-NEXT: (drop
39+
;; PRIMARY-NEXT: (global.get $e)
40+
;; PRIMARY-NEXT: )
3141
;; PRIMARY-NEXT: )
32-
(func $keep (result i32)
33-
(i32.const 0)
42+
(func $keep
43+
(drop
44+
(global.get $e)
45+
)
3446
)
3547

36-
;; Exclusively uses $a, causing $a to move to the secondary module
37-
;; SECONDARY: (func $split (result i32)
38-
;; SECONDARY-NEXT: (global.get $a)
48+
;; Exclusively uses $a and $d, causing them to move to the secondary module
49+
;; SECONDARY: (func $split
50+
;; SECONDARY-NEXT: (drop
51+
;; SECONDARY-NEXT: (global.get $a)
52+
;; SECONDARY-NEXT: )
53+
;; SECONDARY-NEXT: (drop
54+
;; SECONDARY-NEXT: (global.get $d)
55+
;; SECONDARY-NEXT: )
3956
;; SECONDARY-NEXT: )
40-
(func $split (result i32)
41-
(global.get $a)
57+
(func $split
58+
(drop
59+
(global.get $a)
60+
)
61+
(drop
62+
(global.get $d)
63+
)
4264
)
4365
)

0 commit comments

Comments
 (0)