From 2579712f6805fefcf0a1732047708f4121408ef4 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sat, 21 Mar 2026 00:10:49 +0000 Subject: [PATCH 1/3] [wasm-split] Remove dead globals Currently dead module items are not split and just end up remaining in the primary module. Usually a user runs DCE before or after the splitting, and the goal of wasm-split is not DCE, so from the optimization perspective it shouldn't be a problem. But after #8441, this can be a problem because a dead global's initializer can refer to another global that is moved to a secondary module: ```wast ;; Primary (global.get $dead i32 (global.get $a)) ;; Secondary (global $a i32 (...)) ``` This PR just removes those dead globals. We leave it and do some post processing to make it work but that's more complicated, or we can move it to the same secondary module but this requires scanning of the reverse mapping. Removing it seems the simplest. We can remove those dead items for other module items (memories, tables, and tags) but they are not necessary for wasm-split to run, and they can be handled later in DCE. Fixes https://github.com/WebAssembly/binaryen/pull/8442#issuecomment-4098636025. --- src/ir/module-splitting.cpp | 15 +++++++++++++-- test/lit/wasm-split/ref.func.wast | 12 ++++++++++++ test/lit/wasm-split/transitive-globals.wast | 5 +++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 6105e81c664..085ad9b9e30 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -1182,8 +1182,18 @@ void ModuleSplitter::shareImportableItems() { auto usingSecondaries = getUsingSecondaries(global->name, &UsedNames::globals); - bool usedInPrimary = primaryUsed.globals.count(global->name); - if (!usedInPrimary && usingSecondaries.size() == 1) { + bool inPrimary = primaryUsed.globals.count(global->name); + + if (!inPrimary && usingSecondaries.empty()) { + // It's not used anywhere, so delete it. Unlike other unused module items + // (memories, tables, and tags) that can just sit in the primary module + // and later be DCE'ed by another pass, we should remove it here, because + // an unused global can contain an initialier that refers to another + // global that has scheduled to move to a secondary module, like + // (global $unused i32 (global.get $a)) // $a is moved to a secondary + globalsToRemove.push_back(global->name); + + } else if (!inPrimary && usingSecondaries.size() == 1) { // We are moving this global to this secondary module auto* secondary = usingSecondaries[0]; auto* secondaryGlobal = ModuleUtils::copyGlobal(global.get(), *secondary); @@ -1213,6 +1223,7 @@ void ModuleSplitter::shareImportableItems() { // function. } } + } else { // We are NOT moving this global to the secondary module if (global->init) { for (auto* ref : FindAll(global->init).list) { diff --git a/test/lit/wasm-split/ref.func.wast b/test/lit/wasm-split/ref.func.wast index 11007235ffe..38eea498d72 100644 --- a/test/lit/wasm-split/ref.func.wast +++ b/test/lit/wasm-split/ref.func.wast @@ -45,6 +45,12 @@ ;; PRIMARY-NEXT: (drop ;; PRIMARY-NEXT: (ref.func $trampoline_second) ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: (drop + ;; PRIMARY-NEXT: (global.get $glob1) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: (drop + ;; PRIMARY-NEXT: (global.get $glob2) + ;; PRIMARY-NEXT: ) ;; PRIMARY-NEXT: ) (func $prime (drop @@ -53,6 +59,12 @@ (drop (ref.func $second) ) + (drop + (global.get $glob1) + ) + (drop + (global.get $glob2) + ) ) ;; SECONDARY: (type $0 (func)) diff --git a/test/lit/wasm-split/transitive-globals.wast b/test/lit/wasm-split/transitive-globals.wast index 6bf5ce6636f..a0bac6c3364 100644 --- a/test/lit/wasm-split/transitive-globals.wast +++ b/test/lit/wasm-split/transitive-globals.wast @@ -34,6 +34,11 @@ ;; SECONDARY: (global $d i32 (global.get $e)) + ;; This dead global is referring to a global ($a) that's moved to the + ;; secondary module. This should be deleted. + ;; PRIMARY-NOT: (global (global $dead i32 (global.get $a)) + (global $dead i32 (global.get $a)) + ;; PRIMARY: (func $keep ;; PRIMARY-NEXT: (drop ;; PRIMARY-NEXT: (global.get $e) From f66e325054b9cd14de8058ab7537b42f9c6a8bab Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 20 Mar 2026 17:38:34 -0700 Subject: [PATCH 2/3] Update src/ir/module-splitting.cpp Co-authored-by: Thomas Lively --- src/ir/module-splitting.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 085ad9b9e30..35408d63c45 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -1189,7 +1189,7 @@ void ModuleSplitter::shareImportableItems() { // (memories, tables, and tags) that can just sit in the primary module // and later be DCE'ed by another pass, we should remove it here, because // an unused global can contain an initialier that refers to another - // global that has scheduled to move to a secondary module, like + // global that will be moved to a secondary module, like // (global $unused i32 (global.get $a)) // $a is moved to a secondary globalsToRemove.push_back(global->name); From a0ee6dddfcfe726e61ade469c836ab3034acc44e Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sat, 21 Mar 2026 01:00:38 +0000 Subject: [PATCH 3/3] Update example test expectations --- test/example/module-splitting.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/example/module-splitting.txt b/test/example/module-splitting.txt index 88de5da0a4e..5f03322b7fe 100644 --- a/test/example/module-splitting.txt +++ b/test/example/module-splitting.txt @@ -22,7 +22,6 @@ Keeping: After: (module (type $0 (func (param i32))) - (global $glob (mut i32) (i32.const 7)) (memory $mem 3 42 shared) (table $tab 3 42 funcref) (tag $e (type $0) (param i32)) @@ -46,7 +45,6 @@ After: (type $0 (func (param i32))) (import "env" "mem" (memory $mem 3 42 shared)) (import "env" "tab" (table $tab 3 42 funcref)) - (import "env" "glob" (global $glob (mut i32))) (import "env" "e" (tag $e (type $0) (param i32))) ) Secondary: