diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 3544610b654..0f35656fae6 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -45,7 +45,8 @@ // instantiation. // // 8. Export globals, tags, tables, and memories from the primary module and -// import them in the secondary modules. +// import them in the secondary modules. If possible, move those module +// items instead to the secondary modules. // // Functions can be used or referenced three ways in a WebAssembly module: they // can be exported, called, or referenced with ref.func. The above procedure @@ -630,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 on global initializers. At this point, all globals are still in the + // primary module, so if we walk on 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()); } @@ -1156,20 +1175,47 @@ void ModuleSplitter::shareImportableItems() { getUsingSecondaries(global->name, &UsedNames::globals); bool inPrimary = primaryUsed.globals.count(global->name); if (!inPrimary && 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(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, 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(global->init).list) { - // Here, ref->func is either a function 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 { + for (auto* secondary : usingSecondaries) { auto* secondaryGlobal = ModuleUtils::copyGlobal(global.get(), *secondary); diff --git a/test/lit/wasm-split/global-funcref.wast b/test/lit/wasm-split/global-funcref.wast index 11c4a332df7..4237c3c7ecc 100644 --- a/test/lit/wasm-split/global-funcref.wast +++ b/test/lit/wasm-split/global-funcref.wast @@ -8,21 +8,11 @@ ;; TODO Use $split in the secondary module directly in the split global (module - ;; PRIMARY: (export "trampoline_split" (func $trampoline_split)) - ;; PRIMARY: (func $keep ;; PRIMARY-NEXT: ) (func $keep) - ;; PRIMARY: (func $trampoline_split - ;; PRIMARY-NEXT: (call_indirect (type $0) - ;; PRIMARY-NEXT: (i32.const 0) - ;; PRIMARY-NEXT: ) - ;; PRIMARY-NEXT: ) - - - ;; SECONDARY: (import "primary" "trampoline_split" (func $trampoline_split (exact))) - ;; SECONDARY: (global $a funcref (ref.func $trampoline_split)) + ;; SECONDARY: (global $a funcref (ref.func $split)) (global $a funcref (ref.func $split)) ;; SECONDARY: (func $split diff --git a/test/lit/wasm-split/ref.func.wast b/test/lit/wasm-split/ref.func.wast index 63dc3080b16..11007235ffe 100644 --- a/test/lit/wasm-split/ref.func.wast +++ b/test/lit/wasm-split/ref.func.wast @@ -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) ;; SECONDARY: (elem declare func $prime) @@ -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: )