diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 7acbf1000d6..d3af7fa937c 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -871,6 +871,8 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { // primary module and scan it there. ModuleUtils::iterActiveDataSegments(primary, [&](DataSegment* segment) { UsedNames* owner = getOwner(segment->memory, &UsedNames::memories); + // Trapping segments should be kept in the primary module because they are + // evaluated at the instantiation time. if (mayTrap(segment)) { owner = &primaryUsed; } @@ -886,6 +888,39 @@ ModuleSplitter::PrimarySecondaryUsedNames ModuleSplitter::computeUsedNames() { ModuleUtils::iterActiveElementSegments(primary, [&](ElementSegment* segment) { UsedNames* owner = getOwner(segment->table, &UsedNames::tables); + + // If placeholders are NOT used, and if all functions in an element segment + // belong to a single secondary module, we can move the segment to that + // secondary module, because those functions aren't available until the + // secondary module is loaded anyway. + if (!config.usePlaceholders && segment->type.isFunction() && + owner == &primaryUsed) { + bool foundSecondary = false; + Index secondaryIndex = 0; + bool keepInPrimary = false; + for (auto* item : segment->data) { + if (item->is()) { + keepInPrimary = true; + break; + } else if (auto* ref = item->dynCast()) { + auto it = funcToSecondaryIndex.find(ref->func); + if (it == funcToSecondaryIndex.end()) { + keepInPrimary = true; + break; + } + if (foundSecondary && secondaryIndex != it->second) { + keepInPrimary = true; + break; + } + foundSecondary = true; + secondaryIndex = it->second; + } + } + if (!keepInPrimary && foundSecondary) { + owner = &secondaryUsed[secondaryIndex]; + } + } + if (mayTrap(segment)) { owner = &primaryUsed; } diff --git a/test/lit/wasm-split/multi-split-elems-no-placeholders.wast b/test/lit/wasm-split/multi-split-elems-no-placeholders.wast new file mode 100644 index 00000000000..8748d1dced6 --- /dev/null +++ b/test/lit/wasm-split/multi-split-elems-no-placeholders.wast @@ -0,0 +1,50 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-split -all -g --multi-split %s --no-placeholders --manifest %S/multi-split.wast.manifest --out-prefix=%t -o %t.wasm +;; RUN: wasm-dis %t.wasm | filecheck %s --check-prefix=PRIMARY +;; RUN: wasm-dis %t1.wasm | filecheck %s --check-prefix=MOD1 +;; RUN: wasm-dis %t2.wasm | filecheck %s --check-prefix=MOD2 +;; RUN: wasm-dis %t3.wasm | filecheck %s --check-prefix=MOD3 + +;; When placeholders are NOT used and all functions in an element segment belong +;; to a single secondary module, we can move the segment to that secondary +;; module. + +(module + ;; PRIMARY: (table $table 3 3 funcref) + (table $table 3 3 funcref) + ;; PRIMARY: (elem $primary-elem1 (table $table) (i32.const 0) func $trampoline_A $trampoline_B $trampoline_C) + + ;; PRIMARY: (elem $primary-elem2 (table $table) (i32.const 0) func $trampoline_A $trampoline_B $trampoline_A) + + ;; PRIMARY: (export "table" (table $table)) + (export "table" (table $table)) + (elem $primary-elem1 (table $table) (i32.const 0) func $A $B $C) + (elem $primary-elem2 (table $table) (i32.const 0) func $A $B $A) + ;; MOD1: (elem $A-elem (table $table) (i32.const 0) func $A $A $A) + (elem $A-elem (table $table) (i32.const 0) func $A $A $A) + ;; MOD2: (elem $B-elem (table $table) (i32.const 0) func $B $B $B) + (elem $B-elem (table $table) (i32.const 0) func $B $B $B) + ;; MOD3: (elem $C-elem (table $table) (i32.const 0) func $C $C $C) + (elem $C-elem (table $table) (i32.const 0) func $C $C $C) + + ;; MOD1: (func $A + ;; MOD1-NEXT: (call_indirect (type $0) + ;; MOD1-NEXT: (i32.const 0) + ;; MOD1-NEXT: ) + ;; MOD1-NEXT: ) + (func $A + (call_indirect $table + (i32.const 0) + ) + ) + + ;; MOD2: (func $B + ;; MOD2-NEXT: ) + (func $B + ) + + ;; MOD3: (func $C + ;; MOD3-NEXT: ) + (func $C + ) +) diff --git a/test/lit/wasm-split/split-elems-no-placeholders.wast b/test/lit/wasm-split/split-elems-no-placeholders.wast new file mode 100644 index 00000000000..a9002b48472 --- /dev/null +++ b/test/lit/wasm-split/split-elems-no-placeholders.wast @@ -0,0 +1,97 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-split %s -all --no-placeholders -g -o1 %t.1.wasm -o2 %t.2.wasm --keep-funcs=keep +;; RUN: wasm-dis %t.1.wasm -all | filecheck %s --check-prefix PRIMARY +;; RUN: wasm-dis %t.2.wasm -all | filecheck %s --check-prefix SECONDARY + +;; When placeholders are NOT used and all functions in an element segment belong +;; to a single secondary module, we can move the segment to that secondary +;; module. + +(module + ;; PRIMARY: (global $keep-global funcref (ref.func $trampoline_split)) + (global $keep-global funcref (ref.func $split)) + ;; PRIMARY: (table $keep-table 2 2 funcref) + (table $keep-table 2 2 funcref) + ;; PRIMARY: (table $keep-table2 1 1 externref) + (table $keep-table2 1 1 externref) + ;; This contains both a primary function and a secondary function, so keep this + ;; in the primary. + ;; PRIMARY: (elem $keep-elem1 (table $keep-table) (i32.const 0) func $keep $trampoline_split) + (elem $keep-elem1 (table $keep-table) (i32.const 0) func $keep $split) + ;; This contains a global.get, so keep it in the primary. + ;; PRIMARY: (elem $keep-elem2 (table $keep-table) (i32.const 0) funcref (item (ref.func $trampoline_split)) (item (global.get $keep-global))) + (elem $keep-elem2 (table $keep-table) (i32.const 0) funcref (ref.func $split) (global.get $keep-global)) + ;; This is not a funcref table, and the referenced table is kept in the + ;; primary. So keep this in the primary too. + ;; PRIMARY: (elem $keep-elem3 (table $keep-table2) (i32.const 0) externref (item (ref.null noextern))) + (elem $keep-elem3 (table $keep-table2) (i32.const 0) externref (ref.null extern)) + ;; The offset exceeds the table size, so keep this in the primary. + ;; PRIMARY: (elem $keep-elem4 (table $keep-table) (i32.const 10) func $trampoline_split) + (elem $keep-elem4 (table $keep-table) (i32.const 10) func $split) + + ;; SECONDARY: (global $split-global funcref (ref.func $split)) + (global $split-global funcref (ref.func $split)) + ;; SECONDARY: (table $split-table 2 2 funcref) + (table $split-table 2 2 funcref) + + ;; All functions are in the secondary module, so split this to the secondary, + ;; even if the referenced table is in the primary module. + ;; SECONDARY: (elem $split-elem1 (table $keep-table) (i32.const 0) func $split $split) + (elem $split-elem1 (table $keep-table) (i32.const 0) func $split $split) + ;; The same test with $split-table. + ;; SECONDARY: (elem $split-elem2 (table $split-table) (i32.const 0) func $split $split) + (elem $split-elem2 (table $split-table) (i32.const 0) func $split $split) + ;; ref.null within data doesn't affect the segment's splitability. + ;; SECONDARY: (elem $split-elem3 (table $split-table) (i32.const 0) funcref (item (ref.func $split)) (item (ref.null nofunc))) + (elem $split-elem3 (table $split-table) (i32.const 0) funcref (ref.func $split) (ref.null nofunc)) + + ;; PRIMARY: (func $keep (type $0) + ;; PRIMARY-NEXT: (call_indirect $keep-table (type $0) + ;; PRIMARY-NEXT: (i32.const 0) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: (drop + ;; PRIMARY-NEXT: (table.get $keep-table2 + ;; PRIMARY-NEXT: (i32.const 0) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: (drop + ;; PRIMARY-NEXT: (global.get $keep-global) + ;; PRIMARY-NEXT: ) + ;; PRIMARY-NEXT: ) + (func $keep + ;; Uses $keep-table + (call_indirect $keep-table + (i32.const 0) + ) + ;; Uses $keep-table2 + (drop + (table.get $keep-table2 + (i32.const 0) + ) + ) + ;; Uses $keep-global + (drop + (global.get $keep-global) + ) + ) + + ;; SECONDARY: (func $split (type $0) + ;; SECONDARY-NEXT: (call_indirect $split-table (type $0) + ;; SECONDARY-NEXT: (i32.const 0) + ;; SECONDARY-NEXT: ) + ;; SECONDARY-NEXT: (drop + ;; SECONDARY-NEXT: (global.get $split-global) + ;; SECONDARY-NEXT: ) + ;; SECONDARY-NEXT: ) + (func $split + ;; Uses $split-table + (call_indirect $split-table + (i32.const 0) + ) + ;; Uses $split-global + (drop + (global.get $split-global) + ) + ) +) +