Skip to content
62 changes: 53 additions & 9 deletions src/ir/module-splitting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,25 @@ void ModuleSplitter::indirectReferencesToSecondaryFunctions() {
}
}
} gatherer(*this);
gatherer.walkModule(&primary);
// We shouldn't use collector.walkModuleCode here, because we don't want to
// walk global initializers. At this point, all globals are still in the
// primary module, so if we walk global initializers here, it will create
// unnecessary trampolines.
//
// For example, we have (global $a funcref (ref.func $foo)), and $foo was
// split into a secondary module. Because $a is at this point still in the
// primary module, $foo will be considered to exist in a different module, so
// this will create a trampoline for $foo. But it is possible that later we
// find out $a is exclusively used by that secondary module and move $a there.
// In that case, $a can just reference $foo locally, but if we scan global
// initializers here, we would have created an unnecessary trampoline for
// $foo.
walkSegments(gatherer, &primary);
for (auto& curr : primary.functions) {
if (!curr->imported()) {
gatherer.walkFunction(curr.get());
}
}
for (auto& secondaryPtr : secondaries) {
gatherer.walkModule(secondaryPtr.get());
}
Expand Down Expand Up @@ -1168,19 +1186,45 @@ void ModuleSplitter::shareImportableItems() {
if (!usedInPrimary && usingSecondaries.size() == 1) {
// We are moving this global to this secondary module
auto* secondary = usingSecondaries[0];
ModuleUtils::copyGlobal(global.get(), *secondary);
auto* secondaryGlobal = ModuleUtils::copyGlobal(global.get(), *secondary);
globalsToRemove.push_back(global->name);
// Import global initializer's ref.func dependences

if (secondaryGlobal->init) {
// When a global's initializer contains ref.func
for (auto* ref : FindAll<RefFunc>(secondaryGlobal->init).list) {
// If ref.func's function is in a different secondary module, we
// create a trampoline here.
if (allSecondaryFuncs.count(ref->func)) {
Index targetIndex = funcToSecondaryIndex.at(ref->func);
if (secondaries[targetIndex].get() != secondary) {
ref->func = getTrampoline(ref->func);
}
}
// 1. If ref.func's function is in the primary module, we export it
// here.
// 2. If ref.func's function is in a different secondary module and we
// just created a trampoline for it in the primary module above, we
// export the trampoline here.
if (primary.getFunctionOrNull(ref->func)) {
exportImportFunction(ref->func, {secondary});
}
// If ref.func's function is in the same secondary module, we don't
// need to do anything. The ref.func can directly reference the
// function.
}
}
} else { // We are NOT moving this global to the secondary module
if (global->init) {
for (auto* ref : FindAll<RefFunc>(global->init).list) {
// Here, ref->func is either a function in the primary module, or a
// trampoline created in indirectReferencesToSecondaryFunctions in
// case the original function is in one of the secondaries.
assert(primary.getFunctionOrNull(ref->func));
exportImportFunction(ref->func, {secondary});
// If we are exporting this global from the primary module, we should
// create a trampoline here, because we skipped doing it for global
// initializers in indirectReferencesToSecondaryFunctions.
if (allSecondaryFuncs.count(ref->func)) {
ref->func = getTrampoline(ref->func);
}
}
}
} else { // We are NOT moving this global to the secondary module

for (auto* secondary : usingSecondaries) {
auto* secondaryGlobal =
ModuleUtils::copyGlobal(global.get(), *secondary);
Expand Down
35 changes: 20 additions & 15 deletions test/lit/wasm-split/global-funcref.wast
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to add a test where another reference to the function in the primary module prevents it from being split out, even though the global is moved to the secondary module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 5754f67

Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,41 @@
;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY

;; When a split global ($a here)'s initializer contains a ref.func of a split
;; function, currently we create its trampoline in the primary module and export
;; it.
;; TODO Use $split in the secondary module directly in the split global
;; function, we should NOT create any trampolines, and the split global should
;; direclty refer to the function.

(module
;; PRIMARY: (export "trampoline_split" (func $trampoline_split))
(global $a funcref (ref.func $split))
(global $b funcref (ref.func $keep))

;; PRIMARY: (func $keep
;; PRIMARY-NEXT: )
(func $keep)
;; PRIMARY: (export "keep" (func $keep))

;; PRIMARY: (func $trampoline_split
;; PRIMARY-NEXT: (call_indirect (type $0)
;; PRIMARY-NEXT: (i32.const 0)
;; PRIMARY-NEXT: )
;; PRIMARY-NEXT: )
;; PRIMARY-NOT: (export "trampoline_split"
;; PRIMARY-NOT: (func $trampoline_split

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

;; SECONDARY: (import "primary" "trampoline_split" (func $trampoline_split (exact)))
;; SECONDARY: (global $a funcref (ref.func $trampoline_split))
(global $a funcref (ref.func $split))
;; SECONDARY: (global $a funcref (ref.func $split))
;; SECONDARY: (global $b funcref (ref.func $keep))

;; PRIMARY: (func $keep
;; PRIMARY-NEXT: )
(func $keep)

;; SECONDARY: (func $split
;; SECONDARY-NEXT: (drop
;; SECONDARY-NEXT: (global.get $a)
;; SECONDARY-NEXT: )
;; SECONDARY-NEXT: (drop
;; SECONDARY-NEXT: (global.get $b)
;; SECONDARY-NEXT: )
;; SECONDARY-NEXT: )
(func $split
(drop
(global.get $a)
)
(drop
(global.get $b)
)
)
)
6 changes: 3 additions & 3 deletions test/lit/wasm-split/ref.func.wast
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@

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

;; SECONDARY: (elem $0 (i32.const 0) $second $second-in-table)
;; SECONDARY: (elem $0 (i32.const 0) $second-in-table $second)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test changes in this file was just caused by the order we create trampolines and are not really meaningful


;; SECONDARY: (elem declare func $prime)

Expand Down Expand Up @@ -97,13 +97,13 @@
;; (but we will get a placeholder, as all split-out functions do).
)
)
;; PRIMARY: (func $trampoline_second (type $0)
;; PRIMARY: (func $trampoline_second-in-table (type $0)
;; PRIMARY-NEXT: (call_indirect $1 (type $0)
;; PRIMARY-NEXT: (i32.const 0)
;; PRIMARY-NEXT: )
;; PRIMARY-NEXT: )

;; PRIMARY: (func $trampoline_second-in-table (type $0)
;; PRIMARY: (func $trampoline_second (type $0)
;; PRIMARY-NEXT: (call_indirect $1 (type $0)
;; PRIMARY-NEXT: (i32.const 1)
;; PRIMARY-NEXT: )
Expand Down
Loading