Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 85 additions & 9 deletions src/ir/module-splitting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ struct OwnershipTracker {
struct ItemInfo {
UsedNames* owner = nullptr;
SmallVector<Module*, 2> usingSecondaries;
bool usedByPrimary = false;
};

std::unordered_map<Name, ItemInfo> tables;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -420,13 +424,30 @@ struct OwnershipTracker {
}

bool isUnused(Name name, const std::unordered_map<Name, ItemInfo>& 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<Name, ItemInfo>& map) {
auto it = map.find(name);
if (it != map.end()) {
return it->second.usedByPrimary;
}
return false;
}

bool usedBySingleSecondary(Name name,
const std::unordered_map<Name, ItemInfo>& 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;
}
};

Expand Down Expand Up @@ -1047,9 +1068,39 @@ void ModuleSplitter::computeUsedNames() {
if (!global->init) {
continue;
}
if (UsedNames* owner = tracker.getOwner(global->name, tracker.globals)) {
for (auto* get : FindAll<GlobalGet>(global->init).list) {
tracker.insert<Global>(get->name, owner);
auto gets = FindAll<GlobalGet>(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)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice if we could have tracker.getOwner<Global>(global->name). This might be possible using struct specialization to create a type-level mapping between module item types (Global, Memory, etc) and OwnershipTracker member pointers. (This mechanism could also potentially replace the macros in OwnershipTracker::insert.)

for (auto* get : gets) {
tracker.insert<Global>(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<Global>(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()) {
Comment on lines +1097 to +1100

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like it's doing "for each secondary module mi, if usingSecs contains mi, then mark all the referenced globals as used in mi." Why do we need the std::find here? Can we not just do something like this?

for i in usingSecs:
  for get in gets:
    tracker.insert(get->name, &secondaryUsed[i]);

for (auto* get : gets) {
tracker.insert<Global>(get->name, &secondaryUsed[i]);
}
}
}
}
Expand All @@ -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,
Expand All @@ -1073,18 +1124,43 @@ void ModuleSplitter::shareImportableItems() {
ExternalKind kind = ExternalKind::Invalid) {
std::unordered_set<Name> elementsToRemove;
for (auto& element : elements) {
using T = std::remove_pointer_t<decltype(element.get())>;
bool isImmutableGlobal = false;
if constexpr (std::is_same_v<T, Global>) {
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<decltype(element.get())>;
if constexpr (std::is_base_of_v<Importable, T>) {
for (auto* secondary :
tracker.getUsingSecondaries(element->name, trackerElements)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@

;; PRIMARY: (export "table" (table $table))

;; PRIMARY: (export "global" (global $base))

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

;; PRIMARY: (func $keep
Expand All @@ -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))

Expand Down
3 changes: 1 addition & 2 deletions test/lit/wasm-split/passive-deps.wast
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,14 @@
;; 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: )
;; PRIMARY-NEXT: )

;; 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)
Expand Down
17 changes: 9 additions & 8 deletions test/lit/wasm-split/split-module-items.wast
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
16 changes: 8 additions & 8 deletions test/lit/wasm-split/transitive-globals.wast
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -29,7 +30,6 @@
)
)

;; Exclusively uses $a and $d, causing them to move to the secondary module
(func $split
(drop
(global.get $a)
Expand All @@ -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)
Expand All @@ -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
Expand Down
9 changes: 5 additions & 4 deletions test/lit/wasm-split/transitive-globals2.wast
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)
Expand All @@ -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)
Expand Down
14 changes: 7 additions & 7 deletions test/lit/wasm-split/transitive-immutable-globals-multi.wast
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -25,17 +26,15 @@

;; 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: )
;; PRIMARY-NEXT: )

;; 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)
Expand All @@ -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)
Expand Down
Loading