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
54 changes: 31 additions & 23 deletions src/ir/module-splitting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -729,29 +729,6 @@ void ModuleSplitter::shareImportableItems() {
}

NameCollector collector(used);
// We shouldn't use collector.walkModuleCode here, because we don't want to
// walk global initializers. At this point, all globals are still in the
// primary module, so if we walk global initializers here, other globals
// appearing in their initializers will all be marked as used in the primary
// module, which is not what we want.
//
// For example, we have (global $a i32 (global.get $b)). Because $a is at
// this point still in the primary module, $b will be marked as "used" in
// the primary module. But $a can be moved to a secondary module later if it
// is used exclusively by that module. Then $b can be also moved, in case it
// doesn't have other uses. But if it is marked as "used" in the primary
// module, it can't.
walkSegments(collector, &module);
for (auto& segment : module.dataSegments) {
if (segment->isActive()) {
used.memories.insert(segment->memory);
}
}
for (auto& segment : module.elementSegments) {
if (segment->isActive()) {
used.tables.insert(segment->table);
}
}

// If primary module has exports, they are "used" in it. Secondary modules
// don't have exports, so this only applies to the primary module.
Expand Down Expand Up @@ -785,6 +762,37 @@ void ModuleSplitter::shareImportableItems() {
}
}

// We shouldn't use collector.walkModuleCode here, because we don't want to
// walk global initializers. At this point, all globals are still in the
// primary module, so if we walk global initializers here, other globals
// appearing in their initializers will all be marked as used in the primary
// module, which is not what we want.
//
// For example, we have (global $a i32 (global.get $b)). Because $a is at
// this point still in the primary module, $b will be marked as "used" in
// the primary module. But $a can be moved to a secondary module later if it
// is used exclusively by that module. Then $b can be also moved, in case it
// doesn't have other uses. But if it is marked as "used" in the primary
// module, it can't.
walkSegments(collector, &module);
for (auto& segment : module.dataSegments) {
if (segment->isActive()) {
used.memories.insert(segment->memory);
}
}
for (auto& segment : module.elementSegments) {
if (segment->isActive()) {
used.tables.insert(segment->table);
}
}
Comment on lines +765 to +787

@aheejin aheejin Jun 9, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This part was just moved, because I felt this was tidier

for (auto name : used.tables) {
if (auto* table = primary.getTableOrNull(name)) {
if (table->init) {
collector.walk(table->init);
}
}
}
Comment on lines +788 to +794

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.

Worth adding a TODO about not walking table initializers here just like we don't walk global initializers? Then we could let tables be moved to secondary modules and if their initializers are the only uses of globals, we could move those globals as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Here we don't walk table initializers assuming they are all used in the primary. We walk the expressions on the current module.


// Compute the transitive closure of globals referenced in other globals'
// initializers. Since globals can reference other globals, we must ensure
// that if a global is used in a module, all its dependencies are also
Expand Down
3 changes: 3 additions & 0 deletions src/ir/module-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ Table* copyTable(const Table* table, Module& out) {
ret->initial = table->initial;
ret->max = table->max;
ret->addressType = table->addressType;
if (table->init) {
ret->init = ExpressionManipulator::copy(table->init, out);
}

return out.addTable(std::move(ret));
}
Expand Down
6 changes: 4 additions & 2 deletions test/lit/wasm-split/split-module-items.wast
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
;; the secondary module

(module
(import "env" "g" (global $import-global-for-table funcref))
(memory $keep-memory 1 1)
(global $keep-global i32 (i32.const 20))
(table $keep-table 1 1 funcref)
(tag $keep-tag (param i32))

(memory $split-memory 1 1)
(global $split-global i32 (i32.const 20))
(table $split-table 1 1 funcref)
(table $split-table 1 1 funcref (global.get $import-global-for-table))
(tag $split-tag (param i32))

(memory $shared-memory 1 1)
Expand Down Expand Up @@ -44,13 +45,14 @@
;; SECONDARY: (import "primary" "memory" (memory $shared-memory 1 1))
;; SECONDARY-NEXT: (import "primary" "table" (table $shared-table 1 1 funcref))
;; SECONDARY-NEXT: (import "primary" "table_5" (table $timport$1 1 funcref))
;; SECONDARY-NEXT: (import "env" "g" (global $import-global-for-table funcref))
;; SECONDARY-NEXT: (import "primary" "global" (global $shared-global 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-global i32 (i32.const 20))
;; SECONDARY-NEXT: (memory $split-memory 1 1)
;; SECONDARY-NEXT: (table $split-table 1 1 funcref)
;; SECONDARY-NEXT: (table $split-table 1 1 funcref (global.get $import-global-for-table))
;; SECONDARY: (tag $split-tag (type $1) (param i32))

(func $keep (param i32) (result i32)
Expand Down
Loading