Skip to content

Commit 4ee183b

Browse files
authored
[wasm-split] Remove unnecessary trampolines for ref.func initializers (WebAssembly#8443)
When a global is exclusively used by a secondary module and thus moved to that module, and its initializer has a `(ref.func $func)`, we used to create a trampoline and export it from the primary module in all cases, even in the case that the function is in the same secondary module. This now only avoids creating a trampoline when the function is already in the same secondary module. To do this, we now skip scanning global initializers in `indirectReferencesToSecondaryFunctions`, and selectively create trampolines only when needed in `shareImportableItems`. The running time of `wasm-split` hasn't really changed with this PR, compared to the previous PR WebAssembly#8442 (~25s range in acx_gallery). WebAssembly#8441, WebAssembly#8442, and this PR combined reduce the size of the primary module of acx_gallery by 45.4%. --- `wasm-objdump -h` result: - Before (WebAssembly#8442) ``` 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 ``` - After (This PR) ``` Type start=0x0000000c end=0x00035d38 (size=0x00035d2c) count: 11185 Import start=0x00035d3c end=0x00132ef0 (size=0x000fd1b4) count: 32642 Function start=0x00132ef4 end=0x001436cc (size=0x000107d8) count: 53001 Table start=0x001436cf end=0x0014720a (size=0x00003b3b) count: 2921 Tag start=0x0014720c end=0x00147210 (size=0x00000004) count: 1 Global start=0x00147214 end=0x00287b75 (size=0x00140961) count: 47728 Export start=0x00287b79 end=0x002d41ce (size=0x0004c655) count: 25972 Start start=0x002d41d0 end=0x002d41d2 (size=0x00000002) start: 828 Elem start=0x002d41d6 end=0x00336c36 (size=0x00062a60) count: 12303 DataCount start=0x00336c38 end=0x00336c39 (size=0x00000001) count: 1 Code start=0x00336c3e end=0x0053dbdd (size=0x00206f9f) count: 53001 Data start=0x0053dbe1 end=0x0055d76e (size=0x0001fb8d) count: 1 ```
1 parent 0e2ee37 commit 4ee183b

File tree

3 files changed

+84
-35
lines changed

3 files changed

+84
-35
lines changed

src/ir/module-splitting.cpp

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
#include "ir/module-utils.h"
8080
#include "ir/names.h"
8181
#include "support/insert_ordered.h"
82+
#include "support/unique_deferring_queue.h"
8283
#include "wasm-builder.h"
8384
#include "wasm.h"
8485

@@ -631,7 +632,25 @@ void ModuleSplitter::indirectReferencesToSecondaryFunctions() {
631632
}
632633
}
633634
} gatherer(*this);
634-
gatherer.walkModule(&primary);
635+
// We shouldn't use collector.walkModuleCode here, because we don't want to
636+
// walk global initializers. At this point, all globals are still in the
637+
// primary module, so if we walk global initializers here, it will create
638+
// unnecessary trampolines.
639+
//
640+
// For example, we have (global $a funcref (ref.func $foo)), and $foo was
641+
// split into a secondary module. Because $a is at this point still in the
642+
// primary module, $foo will be considered to exist in a different module, so
643+
// this will create a trampoline for $foo. But it is possible that later we
644+
// find out $a is exclusively used by that secondary module and move $a there.
645+
// In that case, $a can just reference $foo locally, but if we scan global
646+
// initializers here, we would have created an unnecessary trampoline for
647+
// $foo.
648+
walkSegments(gatherer, &primary);
649+
for (auto& curr : primary.functions) {
650+
if (!curr->imported()) {
651+
gatherer.walkFunction(curr.get());
652+
}
653+
}
635654
for (auto& secondaryPtr : secondaries) {
636655
gatherer.walkModule(secondaryPtr.get());
637656
}
@@ -1055,20 +1074,19 @@ void ModuleSplitter::shareImportableItems() {
10551074
// that if a global is used in a module, all its dependencies are also marked
10561075
// as used.
10571076
auto computeTransitiveGlobals = [&](UsedNames& used) {
1058-
std::vector<Name> worklist(used.globals.begin(), used.globals.end());
1059-
std::unordered_set<Name> visited(used.globals.begin(), used.globals.end());
1077+
UniqueNonrepeatingDeferredQueue<Name> worklist;
1078+
for (auto global : used.globals) {
1079+
worklist.push(global);
1080+
}
10601081
while (!worklist.empty()) {
1061-
Name name = worklist.back();
1062-
worklist.pop_back();
1082+
Name name = worklist.pop();
10631083
// At this point all globals are still in the primary module, so this
10641084
// exists
10651085
auto* global = primary.getGlobal(name);
10661086
if (!global->imported() && global->init) {
10671087
for (auto* get : FindAll<GlobalGet>(global->init).list) {
1068-
if (visited.insert(get->name).second) {
1069-
worklist.push_back(get->name);
1070-
used.globals.insert(get->name);
1071-
}
1088+
worklist.push(get->name);
1089+
used.globals.insert(get->name);
10721090
}
10731091
}
10741092
}
@@ -1168,19 +1186,45 @@ void ModuleSplitter::shareImportableItems() {
11681186
if (!usedInPrimary && usingSecondaries.size() == 1) {
11691187
// We are moving this global to this secondary module
11701188
auto* secondary = usingSecondaries[0];
1171-
ModuleUtils::copyGlobal(global.get(), *secondary);
1189+
auto* secondaryGlobal = ModuleUtils::copyGlobal(global.get(), *secondary);
11721190
globalsToRemove.push_back(global->name);
1173-
// Import global initializer's ref.func dependences
1191+
1192+
if (secondaryGlobal->init) {
1193+
// When a global's initializer contains ref.func
1194+
for (auto* ref : FindAll<RefFunc>(secondaryGlobal->init).list) {
1195+
// If ref.func's function is in a different secondary module, we
1196+
// create a trampoline here.
1197+
if (auto targetIndexIt = funcToSecondaryIndex.find(ref->func);
1198+
targetIndexIt != funcToSecondaryIndex.end()) {
1199+
if (secondaries[targetIndexIt->second].get() != secondary) {
1200+
ref->func = getTrampoline(ref->func);
1201+
}
1202+
}
1203+
// 1. If ref.func's function is in the primary module, we export it
1204+
// here.
1205+
// 2. If ref.func's function is in a different secondary module and we
1206+
// just created a trampoline for it in the primary module above, we
1207+
// export the trampoline here.
1208+
if (primary.getFunctionOrNull(ref->func)) {
1209+
exportImportFunction(ref->func, {secondary});
1210+
}
1211+
// If ref.func's function is in the same secondary module, we don't
1212+
// need to do anything. The ref.func can directly reference the
1213+
// function.
1214+
}
1215+
}
1216+
} else { // We are NOT moving this global to the secondary module
11741217
if (global->init) {
11751218
for (auto* ref : FindAll<RefFunc>(global->init).list) {
1176-
// Here, ref->func is either a function in the primary module, or a
1177-
// trampoline created in indirectReferencesToSecondaryFunctions in
1178-
// case the original function is in one of the secondaries.
1179-
assert(primary.getFunctionOrNull(ref->func));
1180-
exportImportFunction(ref->func, {secondary});
1219+
// If we are exporting this global from the primary module, we should
1220+
// create a trampoline here, because we skipped doing it for global
1221+
// initializers in indirectReferencesToSecondaryFunctions.
1222+
if (allSecondaryFuncs.count(ref->func)) {
1223+
ref->func = getTrampoline(ref->func);
1224+
}
11811225
}
11821226
}
1183-
} else { // We are NOT moving this global to the secondary module
1227+
11841228
for (auto* secondary : usingSecondaries) {
11851229
auto* secondaryGlobal =
11861230
ModuleUtils::copyGlobal(global.get(), *secondary);

test/lit/wasm-split/global-funcref.wast

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

55
;; 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
6+
;; function, we should NOT create any trampolines, and the split global should
7+
;; direclty refer to the function.
98

109
(module
11-
;; PRIMARY: (export "trampoline_split" (func $trampoline_split))
10+
(global $a funcref (ref.func $split))
11+
(global $b funcref (ref.func $keep))
1212

13-
;; PRIMARY: (func $keep
14-
;; PRIMARY-NEXT: )
15-
(func $keep)
13+
;; PRIMARY: (export "keep" (func $keep))
1614

17-
;; PRIMARY: (func $trampoline_split
18-
;; PRIMARY-NEXT: (call_indirect (type $0)
19-
;; PRIMARY-NEXT: (i32.const 0)
20-
;; PRIMARY-NEXT: )
21-
;; PRIMARY-NEXT: )
15+
;; PRIMARY-NOT: (export "trampoline_split"
16+
;; PRIMARY-NOT: (func $trampoline_split
2217

18+
;; SECONDARY: (import "primary" "keep" (func $keep (exact)))
2319

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))
20+
;; SECONDARY: (global $a funcref (ref.func $split))
21+
;; SECONDARY: (global $b funcref (ref.func $keep))
22+
23+
;; PRIMARY: (func $keep
24+
;; PRIMARY-NEXT: )
25+
(func $keep)
2726

2827
;; SECONDARY: (func $split
2928
;; SECONDARY-NEXT: (drop
3029
;; SECONDARY-NEXT: (global.get $a)
3130
;; SECONDARY-NEXT: )
31+
;; SECONDARY-NEXT: (drop
32+
;; SECONDARY-NEXT: (global.get $b)
33+
;; SECONDARY-NEXT: )
3234
;; SECONDARY-NEXT: )
3335
(func $split
3436
(drop
3537
(global.get $a)
3638
)
39+
(drop
40+
(global.get $b)
41+
)
3742
)
3843
)

test/lit/wasm-split/ref.func.wast

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161

6262
;; SECONDARY: (import "primary" "prime" (func $prime (exact (type $0))))
6363

64-
;; SECONDARY: (elem $0 (i32.const 0) $second $second-in-table)
64+
;; SECONDARY: (elem $0 (i32.const 0) $second-in-table $second)
6565

6666
;; SECONDARY: (elem declare func $prime)
6767

@@ -97,13 +97,13 @@
9797
;; (but we will get a placeholder, as all split-out functions do).
9898
)
9999
)
100-
;; PRIMARY: (func $trampoline_second (type $0)
100+
;; PRIMARY: (func $trampoline_second-in-table (type $0)
101101
;; PRIMARY-NEXT: (call_indirect $1 (type $0)
102102
;; PRIMARY-NEXT: (i32.const 0)
103103
;; PRIMARY-NEXT: )
104104
;; PRIMARY-NEXT: )
105105

106-
;; PRIMARY: (func $trampoline_second-in-table (type $0)
106+
;; PRIMARY: (func $trampoline_second (type $0)
107107
;; PRIMARY-NEXT: (call_indirect $1 (type $0)
108108
;; PRIMARY-NEXT: (i32.const 1)
109109
;; PRIMARY-NEXT: )

0 commit comments

Comments
 (0)