diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index b340ad5f874..f6ff1ad08d5 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -336,6 +336,7 @@ struct OwnershipTracker { struct ItemInfo { UsedNames* owner = nullptr; SmallVector usingSecondaries; + bool usedByPrimary = false; }; std::unordered_map tables; @@ -376,6 +377,9 @@ struct OwnershipTracker { // used by the primary module or multiple secondary modules, the primary // module is the owner. auto [it, inserted] = (this->*mapField).insert({name, ItemInfo{owner, {}}}); + if (owner == &primaryUsed) { + it->second.usedByPrimary = true; + } Module* secondary = nullptr; if (owner != &primaryUsed) { size_t index = owner - secondaryUsed.data(); @@ -420,13 +424,30 @@ struct OwnershipTracker { } bool isUnused(Name name, const std::unordered_map& map) { - return getOwner(name, map) == nullptr; + auto it = map.find(name); + if (it != map.end()) { + return !it->second.usedByPrimary && it->second.usingSecondaries.empty(); + } + return true; + } + + bool isUsedByPrimary(Name name, + const std::unordered_map& map) { + auto it = map.find(name); + if (it != map.end()) { + return it->second.usedByPrimary; + } + return false; } bool usedBySingleSecondary(Name name, const std::unordered_map& map) { - auto* owner = getOwner(name, map); - return owner != nullptr && owner != &primaryUsed; + auto it = map.find(name); + if (it != map.end()) { + return !it->second.usedByPrimary && + it->second.usingSecondaries.size() == 1; + } + return false; } }; @@ -1047,9 +1068,39 @@ void ModuleSplitter::computeUsedNames() { if (!global->init) { continue; } - if (UsedNames* owner = tracker.getOwner(global->name, tracker.globals)) { - for (auto* get : FindAll(global->init).list) { - tracker.insert(get->name, owner); + auto gets = FindAll(global->init).list; + if (gets.empty()) { + continue; + } + + // In case of mutable globals, we cannot have multiple copies. Compute the + // 'owner' of the global and insert its dependent globals there. + if (global->mutable_) { + if (UsedNames* owner = tracker.getOwner(global->name, tracker.globals)) { + for (auto* get : gets) { + tracker.insert(get->name, owner); + } + } + continue; + } + + // In case of immutable globals, we can have multiple copies of it. To + // reduce the primary module size, we just copy the globals to all secondary + // modules using them. So here we insert the global in all using modules. + if (tracker.isUsedByPrimary(global->name, tracker.globals)) { + for (auto* get : gets) { + tracker.insert(get->name, &primaryUsed); + } + } + auto& usingSecs = + tracker.getUsingSecondaries(global->name, tracker.globals); + for (size_t i = 0; i < secondaries.size(); ++i) { + Module* sec = secondaries[i].get(); + if (std::find(usingSecs.begin(), usingSecs.end(), sec) != + usingSecs.end()) { + for (auto* get : gets) { + tracker.insert(get->name, &secondaryUsed[i]); + } } } } @@ -1063,8 +1114,8 @@ void ModuleSplitter::shareImportableItems() { // 2. If an item is used by only a single secondary module, move the item to // that secondary module. If an item is used by multiple modules (including // the primary and secondary modules), export the item from the primary and - // import it from the using secondary modules. - + // import it from the using secondary modules. (except for immutable + // globals, which we just copy to using secondary modules) auto shareElements = [&](auto& elements, auto& trackerElements, auto copyElement, @@ -1073,18 +1124,43 @@ void ModuleSplitter::shareImportableItems() { ExternalKind kind = ExternalKind::Invalid) { std::unordered_set elementsToRemove; for (auto& element : elements) { + using T = std::remove_pointer_t; + bool isImmutableGlobal = false; + if constexpr (std::is_same_v) { + if (!element->mutable_) { + isImmutableGlobal = true; + } + } + if (tracker.isUnused(element->name, trackerElements)) { + // If this element is not used anywhere, just remove it elementsToRemove.insert(element->name); } else if (tracker.usedBySingleSecondary(element->name, trackerElements)) { + // If this element is used in a single secondary module, move it to the + // secondary module auto* secondary = tracker.getUsingSecondaries(element->name, trackerElements)[0]; copyElement(element.get(), *secondary); elementsToRemove.insert(element->name); + } else if (isImmutableGlobal) { + // If this element is an immutable global, copy it to all using + // secondary modules to reduce the primary module size, because we can + // have multiple copies. If it is not used in the primary module, remove + // it from there. + if (!tracker.isUsedByPrimary(element->name, trackerElements)) { + elementsToRemove.insert(element->name); + } + for (auto* secondary : + tracker.getUsingSecondaries(element->name, trackerElements)) { + copyElement(element.get(), *secondary); + } } else { + // If this element is used by multiple modules (primary + multiple + // secondaries or just multiple secondaries), export them from the + // primary module and import them from using secondary modules. // We only import and export Importables, i.e., we don't do this for // segments. - using T = std::remove_pointer_t; if constexpr (std::is_base_of_v) { for (auto* secondary : tracker.getUsingSecondaries(element->name, trackerElements)) { diff --git a/test/lit/wasm-split/dispatch-table-base-global-used-elsewhere.wast b/test/lit/wasm-split/dispatch-table-base-global-used-elsewhere.wast index b860fb144a5..a19bf77b939 100644 --- a/test/lit/wasm-split/dispatch-table-base-global-used-elsewhere.wast +++ b/test/lit/wasm-split/dispatch-table-base-global-used-elsewhere.wast @@ -25,8 +25,6 @@ ;; PRIMARY: (export "table" (table $table)) - ;; PRIMARY: (export "global" (global $base)) - ;; PRIMARY: (export "keep" (func $keep)) ;; PRIMARY: (func $keep @@ -42,7 +40,7 @@ ) ;; SECONDARY: (import "primary" "table" (table $table 2 2 funcref)) - ;; SECONDARY: (import "primary" "global" (global $base i32)) + ;; SECONDARY: (import "env" "base" (global $base i32)) ;; SECONDARY: (import "primary" "keep" (func $keep)) diff --git a/test/lit/wasm-split/passive-deps.wast b/test/lit/wasm-split/passive-deps.wast index 9c14bb09e98..5f16ebc596c 100644 --- a/test/lit/wasm-split/passive-deps.wast +++ b/test/lit/wasm-split/passive-deps.wast @@ -22,7 +22,6 @@ ;; PRIMARY-NEXT: (type $0 (func)) ;; PRIMARY-NEXT: (global $g funcref (ref.null nofunc)) ;; PRIMARY-NEXT: (elem $passive-elem funcref (item (global.get $g))) -;; PRIMARY-NEXT: (export "global" (global $g)) ;; PRIMARY-NEXT: (func $keep (type $0) ;; PRIMARY-NEXT: (elem.drop $passive-elem) ;; PRIMARY-NEXT: ) @@ -30,7 +29,7 @@ ;; SECONDARY: (module ;; SECONDARY-NEXT: (type $0 (func)) -;; SECONDARY-NEXT: (import "primary" "global" (global $g funcref)) +;; SECONDARY-NEXT: (global $g funcref (ref.null nofunc)) ;; SECONDARY-NEXT: (func $split (type $0) ;; SECONDARY-NEXT: (drop ;; SECONDARY-NEXT: (global.get $g) diff --git a/test/lit/wasm-split/split-module-items.wast b/test/lit/wasm-split/split-module-items.wast index b4a8b3db881..eb5f59b962c 100644 --- a/test/lit/wasm-split/split-module-items.wast +++ b/test/lit/wasm-split/split-module-items.wast @@ -5,8 +5,10 @@ ;; Check that ;; 1. Items only used in the primary module stay in the primary module ;; 2. Items only used in the secondary module are moved to the secondary module -;; 3. Items used in both modules are exported from the primary and imported from -;; the secondary module +;; 3. Items (that are not immutable globals) used in both modules are exported +;; from the primary and imported from the secondary module +;; 3-1. Immutable globals used in both modules stay in the primary module and +;; also are copied to the secondary module (module (rec @@ -78,24 +80,23 @@ ;; PRIMARY-NEXT: (export "memory_1" (memory $shared-memory)) ;; PRIMARY-NEXT: (export "table" (table $keep-table2)) ;; PRIMARY-NEXT: (export "table_3" (table $shared-table)) - ;; PRIMARY-NEXT: (export "global" (global $shared-immutable-global)) - ;; PRIMARY-NEXT: (export "global_5" (global $shared-mutable-global)) + ;; PRIMARY-NEXT: (export "global" (global $shared-mutable-global)) ;; PRIMARY-NEXT: (export "tag" (tag $shared-tag)) ;; PRIMARY-NEXT: (export "keep" (func $keep)) - ;; PRIMARY-NEXT: (export "table_8" (table $3)) + ;; PRIMARY-NEXT: (export "table_7" (table $3)) ;; SECONDARY: (import "primary" "memory" (memory $keep-memory2 1 1)) ;; SECONDARY-NEXT: (import "primary" "memory_1" (memory $shared-memory 1 1)) ;; SECONDARY-NEXT: (import "primary" "table" (table $keep-table2 1 1 (ref null $2))) ;; SECONDARY-NEXT: (import "primary" "table_3" (table $shared-table 1 1 funcref)) - ;; SECONDARY-NEXT: (import "primary" "table_8" (table $timport$2 1 funcref)) - ;; SECONDARY-NEXT: (import "primary" "global" (global $shared-immutable-global i32)) - ;; SECONDARY-NEXT: (import "primary" "global_5" (global $shared-mutable-global (mut i32))) + ;; SECONDARY-NEXT: (import "primary" "table_7" (table $timport$2 1 funcref)) + ;; SECONDARY-NEXT: (import "primary" "global" (global $shared-mutable-global (mut i32))) ;; SECONDARY-NEXT: (import "primary" "keep" (func $keep (exact (param i32) (result i32)))) ;; SECONDARY-NEXT: (import "primary" "tag" (tag $shared-tag (type $1) (param i32))) ;; SECONDARY: (global $split-immutable-global i32 (i32.const 20)) ;; SECONDARY-NEXT: (global $split-mutable-global (mut i32) (i32.const 20)) + ;; SECONDARY-NEXT: (global $shared-immutable-global i32 (i32.const 20)) ;; SECONDARY-NEXT: (memory $split-memory 1 1) ;; SECONDARY-NEXT: (data $split-data (memory $split-memory) (i32.const 0) "a") ;; SECONDARY-NEXT: (table $split-table 1 1 funcref) diff --git a/test/lit/wasm-split/transitive-globals.wast b/test/lit/wasm-split/transitive-globals.wast index ad702a4363b..00fc2d3bd3e 100644 --- a/test/lit/wasm-split/transitive-globals.wast +++ b/test/lit/wasm-split/transitive-globals.wast @@ -3,13 +3,14 @@ ;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY ;; Check that transitive dependencies in global initializers are correctly -;; analyzed and moved to the secondary module. +;; analyzed and copied to the secondary module. (module - ;; There are two dependency chains: $a->$b->$c and $d->$e->$f. While all of - ;; $a, $b, and $c can be moved to the secondary module because all f them are - ;; used only there, $e is used in the primary module, preventing $e and $f - ;; from being moved to the secondary module. + ;; There are two dependency chains: $a->$b->$c and $d->$e->$f. Because these + ;; are immutable globals, globals are copied to whichever module they are + ;; used. The secondary module uses $a and $d, so it will have all globals + ;; copied to it. The primary module only uses $e, so it will have $e and its + ;; dependency $f. (global $c i32 (i32.const 42)) (global $b i32 (global.get $c)) @@ -29,7 +30,6 @@ ) ) - ;; Exclusively uses $a and $d, causing them to move to the secondary module (func $split (drop (global.get $a) @@ -44,7 +44,6 @@ ;; PRIMARY-NEXT: (type $0 (func)) ;; PRIMARY-NEXT: (global $f i32 (i32.const 42)) ;; PRIMARY-NEXT: (global $e i32 (global.get $f)) -;; PRIMARY-NEXT: (export "global" (global $e)) ;; PRIMARY-NEXT: (func $keep ;; PRIMARY-NEXT: (drop ;; PRIMARY-NEXT: (global.get $e) @@ -54,10 +53,11 @@ ;; SECONDARY: (module ;; SECONDARY-NEXT: (type $0 (func)) -;; SECONDARY-NEXT: (import "primary" "global" (global $e i32)) ;; SECONDARY-NEXT: (global $c i32 (i32.const 42)) ;; SECONDARY-NEXT: (global $b i32 (global.get $c)) ;; SECONDARY-NEXT: (global $a i32 (global.get $b)) +;; SECONDARY-NEXT: (global $f i32 (i32.const 42)) +;; SECONDARY-NEXT: (global $e i32 (global.get $f)) ;; SECONDARY-NEXT: (global $d i32 (global.get $e)) ;; SECONDARY-NEXT: (func $split ;; SECONDARY-NEXT: (drop diff --git a/test/lit/wasm-split/transitive-globals2.wast b/test/lit/wasm-split/transitive-globals2.wast index 56e96807aae..04944971521 100644 --- a/test/lit/wasm-split/transitive-globals2.wast +++ b/test/lit/wasm-split/transitive-globals2.wast @@ -3,8 +3,9 @@ ;; RUN: wasm-dis -all %t.2.wasm | filecheck %s --check-prefix SECONDARY ;; The dependence chain is $g4->$g3->$g2->$g1, and because $g4 is used in the -;; primary module, all four globals should end up in the primary module. Only -;; $g2 needs to be exported to the secondary module, not $g1. +;; primary module, all four globals should end up in the primary module. The +;; secondary module uses $g2, so it will have $g2 and $g1 it depends on. +;; (Immmutable globals are copied to whichever modules they are used.) (module (global $g1 i32 (i32.const 42)) @@ -27,7 +28,6 @@ ;; PRIMARY-NEXT: (global $g2 i32 (global.get $g1)) ;; PRIMARY-NEXT: (global $g3 i32 (global.get $g2)) ;; PRIMARY-NEXT: (global $g4 i32 (global.get $g3)) -;; PRIMARY-NEXT: (export "global" (global $g2)) ;; PRIMARY-NEXT: (func $keep (type $0) ;; PRIMARY-NEXT: (drop ;; PRIMARY-NEXT: (global.get $g4) @@ -37,7 +37,8 @@ ;; SECONDARY: (module ;; SECONDARY-NEXT: (type $0 (func)) -;; SECONDARY-NEXT: (import "primary" "global" (global $g2 i32)) +;; SECONDARY-NEXT: (global $g1 i32 (i32.const 42)) +;; SECONDARY-NEXT: (global $g2 i32 (global.get $g1)) ;; SECONDARY-NEXT: (func $split (type $0) ;; SECONDARY-NEXT: (drop ;; SECONDARY-NEXT: (global.get $g2) diff --git a/test/lit/wasm-split/transitive-immutable-globals-multi.wast b/test/lit/wasm-split/transitive-immutable-globals-multi.wast index 3f2cb97c392..72949f83972 100644 --- a/test/lit/wasm-split/transitive-immutable-globals-multi.wast +++ b/test/lit/wasm-split/transitive-immutable-globals-multi.wast @@ -3,8 +3,9 @@ ;; RUN: wasm-dis -all %t1.wasm | filecheck %s --check-prefix SECONDARY1 ;; RUN: wasm-dis -all %t2.wasm | filecheck %s --check-prefix SECONDARY2 -;; Because global $e is used in both module1 ($split1) and module2 ($split2), $e -;; will be exported / imported, but we don't need to export $f. +;; In case of immutable globals, they are copied to whichever modules they are +;; used. $e will be copied to both module1 ($split1) and module2 ($split2), and +;; this will also cause $f to be copied to both modules. (module (global $f i32 (i32.const 42)) @@ -25,9 +26,6 @@ ;; PRIMARY: (module ;; PRIMARY-NEXT: (type $0 (func)) -;; PRIMARY-NEXT: (global $f i32 (i32.const 42)) -;; PRIMARY-NEXT: (global $e i32 (global.get $f)) -;; PRIMARY-NEXT: (export "global" (global $e)) ;; PRIMARY-NEXT: (func $keep (type $0) ;; PRIMARY-NEXT: (nop) ;; PRIMARY-NEXT: ) @@ -35,7 +33,8 @@ ;; SECONDARY1: (module ;; SECONDARY1-NEXT: (type $0 (func)) -;; SECONDARY1-NEXT: (import "primary" "global" (global $e i32)) +;; SECONDARY1-NEXT: (global $f i32 (i32.const 42)) +;; SECONDARY1-NEXT: (global $e i32 (global.get $f)) ;; SECONDARY1-NEXT: (func $split1 (type $0) ;; SECONDARY1-NEXT: (drop ;; SECONDARY1-NEXT: (global.get $e) @@ -45,7 +44,8 @@ ;; SECONDARY2: (module ;; SECONDARY2-NEXT: (type $0 (func)) -;; SECONDARY2-NEXT: (import "primary" "global" (global $e i32)) +;; SECONDARY2-NEXT: (global $f i32 (i32.const 42)) +;; SECONDARY2-NEXT: (global $e i32 (global.get $f)) ;; SECONDARY2-NEXT: (func $split2 (type $0) ;; SECONDARY2-NEXT: (drop ;; SECONDARY2-NEXT: (global.get $e)