From 663529139c25ea535b2058d8428ccb160be46db8 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sat, 6 Jun 2026 21:04:17 +0000 Subject: [PATCH 1/3] [wasm-split] Remove unused module elements We currently just leave unused module items in the primary module, except for globals (#8505). This removes other unused module items too, because it is simpler for upcoming PRs. We currently pin all segments in the primary module, but future PRs can change that, in which case segments' usage can be dependent on tables, in which case unused tables can become tricky as in the case of globals. This PR just removes all unused module items for simplicity. This doesn't mean this runs a comprehensive fixed-point analysis like RemoveUnusedModuleElements pass; it just drops elements that are never referenced anywhere in any modules when exporting them. Update some tests so that tables are "used" in not droped. --- src/ir/module-splitting.cpp | 24 +++++++++---------- test/lit/wasm-split/passive.wast | 17 ++++++++++++- test/lit/wasm-split/ref.func.wast | 5 ++-- test/lit/wasm-split/table64-const-offset.wast | 6 +++-- .../lit/wasm-split/table64-global-offset.wast | 6 +++-- test/lit/wasm-split/trampoline.wast | 4 +++- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index adc69d4cc6d..088348f1c03 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -850,9 +850,11 @@ void ModuleSplitter::shareImportableItems() { for (auto& memory : primary.memories) { auto usingSecondaries = getUsingSecondaries(memory->name, &UsedNames::memories); - bool usedInPrimary = primaryUsed.memories.contains(memory->name); + bool inPrimary = primaryUsed.memories.contains(memory->name); - if (!usedInPrimary && usingSecondaries.size() == 1) { + if (!inPrimary && usingSecondaries.empty()) { + memoriesToRemove.push_back(memory->name); + } else if (!inPrimary && usingSecondaries.size() == 1) { auto* secondary = usingSecondaries[0]; ModuleUtils::copyMemory(memory.get(), *secondary); memoriesToRemove.push_back(memory->name); @@ -873,9 +875,11 @@ void ModuleSplitter::shareImportableItems() { for (auto& table : primary.tables) { auto usingSecondaries = getUsingSecondaries(table->name, &UsedNames::tables); - bool usedInPrimary = primaryUsed.tables.contains(table->name); + bool inPrimary = primaryUsed.tables.contains(table->name); - if (!usedInPrimary && usingSecondaries.size() == 1) { + if (!inPrimary && usingSecondaries.empty()) { + tablesToRemove.push_back(table->name); + } else if (!inPrimary && usingSecondaries.size() == 1) { auto* secondary = usingSecondaries[0]; assert(!secondary->getTableOrNull(table->name)); ModuleUtils::copyTable(table.get(), *secondary); @@ -903,12 +907,6 @@ void ModuleSplitter::shareImportableItems() { bool inPrimary = primaryUsed.globals.contains(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 initializer that refers to another - // 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); } else if (!inPrimary && usingSecondaries.size() == 1) { auto* secondary = usingSecondaries[0]; @@ -930,9 +928,11 @@ void ModuleSplitter::shareImportableItems() { std::vector tagsToRemove; for (auto& tag : primary.tags) { auto usingSecondaries = getUsingSecondaries(tag->name, &UsedNames::tags); - bool usedInPrimary = primaryUsed.tags.contains(tag->name); + bool inPrimary = primaryUsed.tags.contains(tag->name); - if (!usedInPrimary && usingSecondaries.size() == 1) { + if (!inPrimary && usingSecondaries.empty()) { + tagsToRemove.push_back(tag->name); + } else if (!inPrimary && usingSecondaries.size() == 1) { auto* secondary = usingSecondaries[0]; ModuleUtils::copyTag(tag.get(), *secondary); tagsToRemove.push_back(tag->name); diff --git a/test/lit/wasm-split/passive.wast b/test/lit/wasm-split/passive.wast index 0b0fbfa7790..21d167ebce0 100644 --- a/test/lit/wasm-split/passive.wast +++ b/test/lit/wasm-split/passive.wast @@ -23,6 +23,20 @@ ;; PRIMARY: (export "table" (table $1)) + ;; PRIMARY: (func $use_table (type $0) + ;; PRIMARY-NEXT: (call_indirect $table (type $0) + ;; PRIMARY-NEXT: (i32.const 0) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: (elem.drop $passive) + ;; PRIMARY-NEXT: ) + (func $use_table + ;; Use $table so that it will not be deleted + (call_indirect $table + (i32.const 0) + ) + (elem.drop $passive) + ) + ;; PRIMARY: (func $in-table (type $0) ;; PRIMARY-NEXT: ) (func $in-table @@ -40,7 +54,8 @@ ;; SECONDARY-NEXT: ) (func $second-in-table ;; This is in a passive segment, and it is in the secondary module, so we will - ;; handle it by adding a trampoline from the segment as a new function "$1". + ;; handle it by adding a trampoline from the segment as a new function + ;; "$trampoline_second-in-table". ) ) ;; PRIMARY: (func $trampoline_second-in-table (type $0) diff --git a/test/lit/wasm-split/ref.func.wast b/test/lit/wasm-split/ref.func.wast index 5cc261dc4cd..0d3932c827b 100644 --- a/test/lit/wasm-split/ref.func.wast +++ b/test/lit/wasm-split/ref.func.wast @@ -19,8 +19,7 @@ ;; PRIMARY: (global $glob2 (ref func) (ref.func $trampoline_second)) ;; PRIMARY: (table $table 1 1 funcref) - (table $table 1 1 funcref) - + (table $table (export "table_export") 1 1 funcref) (global $glob1 (ref func) (ref.func $prime)) (global $glob2 (ref func) (ref.func $second)) @@ -34,6 +33,8 @@ ;; PRIMARY: (elem declare func $prime $trampoline_second) + ;; PRIMARY: (export "table_export" (table $table)) + ;; PRIMARY: (export "prime" (func $prime)) ;; PRIMARY: (export "table" (table $1)) diff --git a/test/lit/wasm-split/table64-const-offset.wast b/test/lit/wasm-split/table64-const-offset.wast index cd396f6361f..2e1f4d5eaa1 100644 --- a/test/lit/wasm-split/table64-const-offset.wast +++ b/test/lit/wasm-split/table64-const-offset.wast @@ -22,8 +22,8 @@ ;; PRIMARY-NOREF-NEXT: (import "placeholder.deferred" "1" (func $placeholder_1 (param i32) (result i32))) ;; PRIMARY-NOREF-NEXT: (table $table i64 2 2 funcref) ;; PRIMARY-NOREF-NEXT: (elem $0 (i64.const 0) $foo $placeholder_1) +;; PRIMARY-NOREF-NEXT: (export "%table_export" (table $table)) ;; PRIMARY-NOREF-NEXT: (export "%foo" (func $foo)) -;; PRIMARY-NOREF-NEXT: (export "%table" (table $table)) ;; PRIMARY-NOREF-NEXT: (func $foo (param $0 i32) (result i32) ;; PRIMARY-NOREF-NEXT: (call_indirect (type $0) ;; PRIMARY-NOREF-NEXT: (i32.const 0) @@ -34,7 +34,7 @@ ;; SECONDARY-NOREF: (module ;; SECONDARY-NOREF-NEXT: (type $0 (func (param i32) (result i32))) -;; SECONDARY-NOREF-NEXT: (import "primary" "%table" (table $table i64 2 2 funcref)) +;; SECONDARY-NOREF-NEXT: (import "primary" "%table_export" (table $table i64 2 2 funcref)) ;; SECONDARY-NOREF-NEXT: (import "primary" "%foo" (func $foo (param i32) (result i32))) ;; SECONDARY-NOREF-NEXT: (elem $0 (i64.const 1) $bar) ;; SECONDARY-NOREF-NEXT: (func $bar (param $0 i32) (result i32) @@ -51,6 +51,7 @@ ;; PRIMARY-REF-NEXT: (table $1 1 funcref) ;; PRIMARY-REF-NEXT: (elem $0 (table $table) (i64.const 0) func $foo) ;; PRIMARY-REF-NEXT: (elem $1 (table $1) (i32.const 0) func $placeholder_0) +;; PRIMARY-REF-NEXT: (export "%table_export" (table $table)) ;; PRIMARY-REF-NEXT: (export "%foo" (func $foo)) ;; PRIMARY-REF-NEXT: (export "%table" (table $1)) ;; PRIMARY-REF-NEXT: (func $foo (param $0 i32) (result i32) @@ -75,6 +76,7 @@ (module (table $table i64 1 1 funcref) + (export "%table_export" (table $table)) (elem (i64.const 0) $foo) (func $foo (param i32) (result i32) (call $bar (i32.const 0)) diff --git a/test/lit/wasm-split/table64-global-offset.wast b/test/lit/wasm-split/table64-global-offset.wast index ebcc930903b..809062c3d24 100644 --- a/test/lit/wasm-split/table64-global-offset.wast +++ b/test/lit/wasm-split/table64-global-offset.wast @@ -23,8 +23,8 @@ ;; PRIMARY-NOREF-NEXT: (import "placeholder.deferred" "1" (func $placeholder_1 (param i32) (result i32))) ;; PRIMARY-NOREF-NEXT: (table $table i64 2 2 funcref) ;; PRIMARY-NOREF-NEXT: (elem $0 (global.get $g) $foo $placeholder_1) +;; PRIMARY-NOREF-NEXT: (export "%table_export" (table $table)) ;; PRIMARY-NOREF-NEXT: (export "%foo" (func $foo)) -;; PRIMARY-NOREF-NEXT: (export "%table" (table $table)) ;; PRIMARY-NOREF-NEXT: (export "%global" (global $g)) ;; PRIMARY-NOREF-NEXT: (func $foo (param $0 i32) (result i32) ;; PRIMARY-NOREF-NEXT: (call_indirect (type $0) @@ -39,7 +39,7 @@ ;; SECONDARY-NOREF: (module ;; SECONDARY-NOREF-NEXT: (type $0 (func (param i32) (result i32))) -;; SECONDARY-NOREF-NEXT: (import "primary" "%table" (table $table i64 2 2 funcref)) +;; SECONDARY-NOREF-NEXT: (import "primary" "%table_export" (table $table i64 2 2 funcref)) ;; SECONDARY-NOREF-NEXT: (import "primary" "%global" (global $g i64)) ;; SECONDARY-NOREF-NEXT: (import "primary" "%foo" (func $foo (param i32) (result i32))) ;; SECONDARY-NOREF-NEXT: (elem $0 (global.get $g) $foo $bar) @@ -58,6 +58,7 @@ ;; PRIMARY-REF-NEXT: (table $1 1 funcref) ;; PRIMARY-REF-NEXT: (elem $0 (table $table) (global.get $g) func $foo) ;; PRIMARY-REF-NEXT: (elem $1 (table $1) (i32.const 0) func $placeholder_0) +;; PRIMARY-REF-NEXT: (export "%table_export" (table $table)) ;; PRIMARY-REF-NEXT: (export "%foo" (func $foo)) ;; PRIMARY-REF-NEXT: (export "%table" (table $1)) ;; PRIMARY-REF-NEXT: (func $foo (param $0 i32) (result i32) @@ -83,6 +84,7 @@ (module (global $g (import "env" "g") i64) (table $table i64 1 1 funcref) + (export "%table_export" (table $table)) (elem (global.get $g) $foo) (func $foo (param i32) (result i32) (call $bar (i32.const 0)) diff --git a/test/lit/wasm-split/trampoline.wast b/test/lit/wasm-split/trampoline.wast index 8c1fb5d5167..2fad323dacb 100644 --- a/test/lit/wasm-split/trampoline.wast +++ b/test/lit/wasm-split/trampoline.wast @@ -9,7 +9,7 @@ ;; PRIMARY: (import "placeholder.deferred" "0" (func $placeholder_0 (param i32) (result i32))) ;; PRIMARY: (table $table 1 1 funcref) - (table $table 1 1 funcref) + (table $table (export "table_export") 1 1 funcref) (elem (i32.const 0) $bar) (export "bar" (func $bar)) ;; PRIMARY: (table $1 1 funcref) @@ -18,6 +18,8 @@ ;; PRIMARY: (elem $1 (table $1) (i32.const 0) func $placeholder_0) + ;; PRIMARY: (export "table_export" (table $table)) + ;; PRIMARY: (export "bar" (func $trampoline_bar)) ;; PRIMARY: (export "foo" (func $foo)) From 4882fb846a62bb11a897fefa5eb5d7c761aed161 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 8 Jun 2026 20:25:39 +0000 Subject: [PATCH 2/3] Export elements in exmample tests to make them used --- test/example/module-splitting.cpp | 16 ++++++++-------- test/example/module-splitting.txt | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/test/example/module-splitting.cpp b/test/example/module-splitting.cpp index e09316acbef..a44aefa12fc 100644 --- a/test/example/module-splitting.cpp +++ b/test/example/module-splitting.cpp @@ -78,19 +78,19 @@ int main() { // Global stuff do_test({}, R"( (module - (memory $mem 3 42 shared) - (table $tab 3 42 funcref) - (global $glob (mut i32) (i32.const 7)) - (tag $e (param i32)) + (memory $mem (export "mem") 3 42 shared) + (table $tab (export "tab") 3 42 funcref) + (global $glob (export "glob") (mut i32) (i32.const 7)) + (tag $e (export "e") (param i32)) ))"); // Imported global stuff do_test({}, R"( (module - (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 (param i32))) + (memory $mem (export "mem") (import "env" "mem") 3 42 shared) + (table $tab (export "tab") (import "env" "tab") 3 42 funcref) + (global $glob (export "glob") (import "env" "glob") (mut i32)) + (tag $e (export "e") (import "env" "e") (param i32)) ))"); // Exported global stuff diff --git a/test/example/module-splitting.txt b/test/example/module-splitting.txt index 5f03322b7fe..f65f607f397 100644 --- a/test/example/module-splitting.txt +++ b/test/example/module-splitting.txt @@ -17,14 +17,23 @@ Before: (memory $mem 3 42 shared) (table $tab 3 42 funcref) (tag $e (type $0) (param i32)) + (export "mem" (memory $mem)) + (export "tab" (table $tab)) + (export "glob" (global $glob)) + (export "e" (tag $e)) ) 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)) + (export "mem" (memory $mem)) + (export "tab" (table $tab)) + (export "glob" (global $glob)) + (export "e" (tag $e)) ) Secondary: (module @@ -38,6 +47,10 @@ Before: (import "env" "tab" (table $tab 3 42 funcref)) (import "env" "glob" (global $glob (mut i32))) (import "env" "e" (tag $e (type $0) (param i32))) + (export "mem" (memory $mem)) + (export "tab" (table $tab)) + (export "glob" (global $glob)) + (export "e" (tag $e)) ) Keeping: After: @@ -45,7 +58,12 @@ 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))) + (export "mem" (memory $mem)) + (export "tab" (table $tab)) + (export "glob" (global $glob)) + (export "e" (tag $e)) ) Secondary: (module From d42a9c18a278e6c97c07c9e15536f1273efb355e Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 8 Jun 2026 20:50:49 +0000 Subject: [PATCH 3/3] Add test showing unused elements removed --- .../wasm-split/remove-unused-elements.wast | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/lit/wasm-split/remove-unused-elements.wast diff --git a/test/lit/wasm-split/remove-unused-elements.wast b/test/lit/wasm-split/remove-unused-elements.wast new file mode 100644 index 00000000000..8295da5bd33 --- /dev/null +++ b/test/lit/wasm-split/remove-unused-elements.wast @@ -0,0 +1,25 @@ +;; RUN: wasm-split %s -all -g -o1 %t.1.wasm -o2 %t.2.wasm --keep-funcs=keep +;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY +;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY + +(module + (memory $unused-memory 1 1) + (global $unused-global i32 (i32.const 20)) + (table $unused-table 1 1 funcref) + (tag $unused-tag (param i32)) + + (func $keep) + (func $split) +) + +;; PRIMARY: (module +;; PRIMARY-NEXT: (type $0 (func)) +;; PRIMARY-NEXT: (func $keep +;; PRIMARY-NEXT: ) +;; PRIMARY-NEXT: ) + +;; SECONDARY: (module +;; SECONDARY-NEXT: (type $0 (func)) +;; SECONDARY-NEXT: (func $split +;; SECONDARY-NEXT: ) +;; SECONDARY-NEXT: )