From 7a7aff528fce4a7528e9ec15e1facb4e6681efed Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Tue, 2 Jun 2026 01:40:27 +0000 Subject: [PATCH 1/2] [wasm-split] Rename active table to dispatch table (NFC) This renames active table and active segments to dispatch table and dispatch segments. I feel "Active segments" sound confusing because this can read as non-passive segments. --- src/ir/module-splitting.cpp | 158 +++++++++--------- ...tch-table-base-global-used-elsewhere.wast} | 8 +- test/lit/wasm-split/multi-split2.wast | 4 +- ...-segment.wast => no-dispatch-segment.wast} | 3 +- test/lit/wasm-split/table-name-conflict.wast | 2 +- 5 files changed, 89 insertions(+), 86 deletions(-) rename test/lit/wasm-split/{active-table-base-global-used-elsewhere.wast => dispatch-table-base-global-used-elsewhere.wast} (91%) rename test/lit/wasm-split/{no-active-segment.wast => no-dispatch-segment.wast} (91%) diff --git a/src/ir/module-splitting.cpp b/src/ir/module-splitting.cpp index 619b00d92b6..8132ffa8fef 100644 --- a/src/ir/module-splitting.cpp +++ b/src/ir/module-splitting.cpp @@ -43,7 +43,7 @@ // export the primary function if it is not already exported and import it // into each secondary module using it. // -// 8. For each secondary module, create new active table segments in the +// 8. For each secondary module, create new dispatch table segments in the // module that will replace all the placeholder function references in the // table with references to their corresponding secondary functions upon // instantiation. @@ -64,7 +64,7 @@ // // 2. It assumes that either all table segment offsets are constants or there // is exactly one segment that may have a non-constant offset. It also -// assumes that all segments are active segments. +// assumes that all segments are dispatch segments. // // 3. It assumes that if exact function references are required for validity // (because they are stored in a local with an exact function type, for @@ -114,11 +114,11 @@ struct TableSlotManager { }; Module& module; const std::vector>& secondaries; - Table* activeTable = nullptr; - ElementSegment* activeSegment = nullptr; - Slot activeBase; + Table* dispatchTable = nullptr; + ElementSegment* dispatchSegment = nullptr; + Slot dispatchBase; std::map funcIndices; - std::vector activeTableSegments; + std::vector dispatchTableSegments; TableSlotManager(Module& module, const std::vector>& secondaries); @@ -187,43 +187,43 @@ TableSlotManager::TableSlotManager( return; } - activeTable = it->get(); + dispatchTable = it->get(); ModuleUtils::iterTableSegments( - module, activeTable->name, [&](ElementSegment* segment) { - activeTableSegments.push_back(segment); + module, dispatchTable->name, [&](ElementSegment* segment) { + dispatchTableSegments.push_back(segment); }); - if (activeTableSegments.empty()) { - // There are no active segments, so we will lazily create one and start + if (dispatchTableSegments.empty()) { + // There are no dispatch segments, so we will lazily create one and start // filling it at index 0. - activeBase = {activeTable->name, Name(), 0}; - } else if (activeTableSegments.size() == 1 && - activeTableSegments[0]->type == funcref && - !activeTableSegments[0]->offset->is()) { + dispatchBase = {dispatchTable->name, Name(), 0}; + } else if (dispatchTableSegments.size() == 1 && + dispatchTableSegments[0]->type == funcref && + !dispatchTableSegments[0]->offset->is()) { // If there is exactly one table segment and that segment has a non-constant // offset, append new items to the end of that segment. In all other cases, // append new items at constant offsets after all existing items at constant // offsets. - assert(activeTableSegments[0]->offset->is() && + assert(dispatchTableSegments[0]->offset->is() && "Unexpected initializer instruction"); - activeSegment = activeTableSegments[0]; - activeBase = {activeTable->name, - activeTableSegments[0]->offset->cast()->name, - 0}; + dispatchSegment = dispatchTableSegments[0]; + dispatchBase = {dispatchTable->name, + dispatchTableSegments[0]->offset->cast()->name, + 0}; } else { // Finds the segment with the highest occupied table slot so that new items // can be inserted contiguously at the end of it without accidentally // overwriting any other items. TODO: be more clever about filling gaps in // the table, if that is ever useful. Index maxIndex = 0; - for (auto& segment : activeTableSegments) { + for (auto& segment : dispatchTableSegments) { assert(segment->offset->is() && "Unexpected non-const segment offset with multiple segments"); Index segmentBase = segment->offset->cast()->value.getInteger(); if (segmentBase + segment->data.size() >= maxIndex) { maxIndex = segmentBase + segment->data.size(); - activeSegment = segment; - activeBase = {activeTable->name, Name(), segmentBase}; + dispatchSegment = segment; + dispatchBase = {dispatchTable->name, Name(), segmentBase}; } } } @@ -238,7 +238,7 @@ TableSlotManager::TableSlotManager( } Table* TableSlotManager::makeTable() { - // Because the active table will be imported in secondary modules, its name + // Because the dispatch table will be imported in secondary modules, its name // should not collide with any existing tables in primary and secondary // modules. std::unordered_set secondaryTableNames; @@ -262,10 +262,10 @@ Table* TableSlotManager::makeTable() { ElementSegment* TableSlotManager::makeElementSegment() { Builder builder(module); Expression* offset = - builder.makeConst(Literal::makeFromInt32(0, activeTable->addressType)); + builder.makeConst(Literal::makeFromInt32(0, dispatchTable->addressType)); return module.addElementSegment(Builder::makeElementSegment( Names::getValidElementSegmentName(module, Name::fromInt(0)), - activeTable->name, + dispatchTable->name, offset)); } @@ -276,40 +276,40 @@ TableSlotManager::Slot TableSlotManager::getSlot(Name func, HeapType type) { } // If there are no segments yet, allocate one. - if (activeSegment == nullptr) { - if (activeTable == nullptr) { - activeTable = makeTable(); - activeBase = {activeTable->name, Name(), 0}; + if (dispatchSegment == nullptr) { + if (dispatchTable == nullptr) { + dispatchTable = makeTable(); + dispatchBase = {dispatchTable->name, Name(), 0}; } - // None of the existing segments should refer to the active table + // None of the existing segments should refer to the dispatch table assert(std::all_of(module.elementSegments.begin(), module.elementSegments.end(), [&](std::unique_ptr& segment) { - return segment->table != activeTable->name; + return segment->table != dispatchTable->name; })); - activeSegment = makeElementSegment(); + dispatchSegment = makeElementSegment(); } - Slot newSlot = {activeBase.tableName, - activeBase.global, - activeBase.index + Index(activeSegment->data.size())}; + Slot newSlot = {dispatchBase.tableName, + dispatchBase.global, + dispatchBase.index + Index(dispatchSegment->data.size())}; Builder builder(module); auto funcType = Type(type, NonNullable, Inexact); - activeSegment->data.push_back(builder.makeRefFunc(func, funcType)); + dispatchSegment->data.push_back(builder.makeRefFunc(func, funcType)); addSlot(func, newSlot); - if (activeTable->initial <= newSlot.index) { - activeTable->initial = newSlot.index + 1; - // TODO: handle the active table not being the dylink table (#3823) + if (dispatchTable->initial <= newSlot.index) { + dispatchTable->initial = newSlot.index + 1; + // TODO: handle the dispatch table not being the dylink table (#3823) if (module.dylinkSection) { - module.dylinkSection->tableSize = activeTable->initial; + module.dylinkSection->tableSize = dispatchTable->initial; } } - if (activeTable->max <= newSlot.index) { - activeTable->max = newSlot.index + 1; + if (dispatchTable->max <= newSlot.index) { + dispatchTable->max = newSlot.index + 1; } return newSlot; } @@ -343,7 +343,7 @@ struct ModuleSplitter { // Map from original secondary function name to its trampoline std::unordered_map trampolineMap; - void shareActiveTable(Module* secondary); + void shareDispatchTable(Module* secondary); // Initialization helpers static std::unique_ptr initSecondary(const Module& primary); @@ -593,24 +593,26 @@ Name ModuleSplitter::getTrampoline(Name funcName) { return trampoline; } -void ModuleSplitter::shareActiveTable(Module* secondary) { - assert(tableManager.activeTable); +void ModuleSplitter::shareDispatchTable(Module* secondary) { + assert(tableManager.dispatchTable); auto secondaryTable = - secondary->getTableOrNull(tableManager.activeTable->name); + secondary->getTableOrNull(tableManager.dispatchTable->name); if (secondaryTable) { // In case it's already in the secondary module, sync the initial/max - secondaryTable->initial = tableManager.activeTable->initial; - secondaryTable->max = tableManager.activeTable->max; + secondaryTable->initial = tableManager.dispatchTable->initial; + secondaryTable->max = tableManager.dispatchTable->max; } else { secondaryTable = - ModuleUtils::copyTable(tableManager.activeTable, *secondary); - makeImportExport( - *tableManager.activeTable, *secondaryTable, "table", ExternalKind::Table); + ModuleUtils::copyTable(tableManager.dispatchTable, *secondary); + makeImportExport(*tableManager.dispatchTable, + *secondaryTable, + "table", + ExternalKind::Table); } - if (tableManager.activeBase.global) { - auto* primaryGlobal = primary.getGlobal(tableManager.activeBase.global); + if (tableManager.dispatchBase.global) { + auto* primaryGlobal = primary.getGlobal(tableManager.dispatchBase.global); auto* secondaryGlobal = - secondary->getGlobalOrNull(tableManager.activeBase.global); + secondary->getGlobalOrNull(tableManager.dispatchBase.global); if (!secondaryGlobal) { secondaryGlobal = ModuleUtils::copyGlobal(primaryGlobal, *secondary); makeImportExport( @@ -803,13 +805,13 @@ void ModuleSplitter::shareImportableItems() { secondaryUsed.push_back(getUsedNames(*secondaryPtr)); } - // We need to assume the active table and its base global are used in the + // We need to assume the dispatch table and its base global are used in the // primary module, because we will create segments there later. - if (tableManager.activeTable) { - primaryUsed.tables.insert(tableManager.activeTable->name); + if (tableManager.dispatchTable) { + primaryUsed.tables.insert(tableManager.dispatchTable->name); } - if (tableManager.activeBase.global) { - primaryUsed.globals.insert(tableManager.activeBase.global); + if (tableManager.dispatchBase.global) { + primaryUsed.globals.insert(tableManager.dispatchBase.global); } // If custom-descirptors is enabled, global initializers can trap. Trapping @@ -979,13 +981,13 @@ void ModuleSplitter::indirectReferencesToSecondaryFunctions() { gatherer.walkModule(secondaryPtr.get()); } - // Ignore references to secondary functions that occur in the active segment + // Ignore references to secondary functions that occur in the dispatch segment // that will contain the imported placeholders. Indirect calls to table slots // initialized by that segment will already go to the right place once the // secondary module has been loaded and the table has been patched. std::unordered_set ignore; - if (tableManager.activeSegment) { - for (auto* expr : tableManager.activeSegment->data) { + if (tableManager.dispatchSegment) { + for (auto* expr : tableManager.dispatchSegment->data) { if (auto* ref = expr->dynCast()) { ignore.insert(ref); } @@ -1023,7 +1025,7 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { // corresponding table indices instead. struct CallIndirector : public PostWalker { ModuleSplitter& parent; - std::unordered_set activeTableUsingSecondaries; + std::unordered_set dispatchTableUsingSecondaries; CallIndirector(ModuleSplitter& parent) : parent(parent) {} void visitCall(Call* curr) { // Return if the call's target is not in one of the secondary module. @@ -1051,10 +1053,10 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { func->type.getHeapType(), curr->isReturn)); - // Share the active table with the current module (caller). We share the - // active table with with calleeModule later in setupTablePathing. + // Share the dispatch table with the current module (caller). We share the + // dispatch table with with calleeModule later in setupTablePathing. if (currModule != &parent.primary) { - activeTableUsingSecondaries.insert(currModule); + dispatchTableUsingSecondaries.insert(currModule); } } }; @@ -1064,8 +1066,8 @@ void ModuleSplitter::indirectCallsToSecondaryFunctions() { callIndirector.walkModule(secondaryPtr.get()); } - for (auto* secondary : callIndirector.activeTableUsingSecondaries) { - shareActiveTable(secondary); + for (auto* secondary : callIndirector.dispatchTableUsingSecondaries) { + shareDispatchTable(secondary); } } @@ -1116,7 +1118,7 @@ void ModuleSplitter::exportImportCalledPrimaryFunctions() { } void ModuleSplitter::setupTablePatching() { - if (!tableManager.activeTable) { + if (!tableManager.dispatchTable) { return; } @@ -1135,7 +1137,7 @@ void ModuleSplitter::setupTablePatching() { if (!allSecondaryFuncs.contains(ref->func)) { return; } - assert(table == tableManager.activeTable->name); + assert(table == tableManager.dispatchTable->name); placeholderMap[table][index] = ref->func; Index secondaryIndex = funcToSecondaryIndex.at(ref->func); @@ -1159,7 +1161,7 @@ void ModuleSplitter::setupTablePatching() { } else { // !config.usePlaceholders if (primary.features.hasReferenceTypes()) { - // TODO: This can create active element segments with lots of nulls. + // TODO: This can create dispatch element segments with lots of nulls. // We should optimize them like we do data segments with zeros. elem = Builder(primary).makeRefNull(HeapType::nofunc); return; @@ -1184,11 +1186,11 @@ void ModuleSplitter::setupTablePatching() { for (auto& [secondaryPtr, replacedElems] : moduleToReplacedElems) { Module& secondary = *secondaryPtr; - shareActiveTable(&secondary); - auto* secondaryTable = secondary.getTable(tableManager.activeTable->name); + shareDispatchTable(&secondary); + auto* secondaryTable = secondary.getTable(tableManager.dispatchTable->name); - if (tableManager.activeBase.global) { - assert(tableManager.activeTableSegments.size() == 1 && + if (tableManager.dispatchBase.global) { + assert(tableManager.dispatchTableSegments.size() == 1 && "Unexpected number of segments with non-const base"); assert(secondary.tables.size() == 1 && secondary.elementSegments.empty()); // Since addition is not currently allowed in initializer expressions, we @@ -1199,7 +1201,7 @@ void ModuleSplitter::setupTablePatching() { // to be imported into the second module. TODO: use better strategies // here, such as using ref.func in the start function or standardizing // addition in initializer expressions. - ElementSegment* primarySeg = tableManager.activeTableSegments.front(); + ElementSegment* primarySeg = tableManager.dispatchTableSegments.front(); std::vector secondaryElems; secondaryElems.reserve(primarySeg->data.size()); @@ -1233,7 +1235,7 @@ void ModuleSplitter::setupTablePatching() { return; } - // Create active table segments in the secondary module to patch in the + // Create dispatch table segments in the secondary module to patch in the // original functions when it is instantiated. Index currBase = replacedElems.begin()->first; std::vector currData; diff --git a/test/lit/wasm-split/active-table-base-global-used-elsewhere.wast b/test/lit/wasm-split/dispatch-table-base-global-used-elsewhere.wast similarity index 91% rename from test/lit/wasm-split/active-table-base-global-used-elsewhere.wast rename to test/lit/wasm-split/dispatch-table-base-global-used-elsewhere.wast index ca3380ce405..b860fb144a5 100644 --- a/test/lit/wasm-split/active-table-base-global-used-elsewhere.wast +++ b/test/lit/wasm-split/dispatch-table-base-global-used-elsewhere.wast @@ -1,12 +1,12 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; We need to disable reference-types to reuse the existing table as the active -;; table +;; We need to disable reference-types to reuse the existing table as the +;; dispatch table ;; RUN: wasm-split %s --disable-reference-types --split-funcs=split -g -o1 %t.1.wasm -o2 %t.2.wasm ;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY ;; RUN: wasm-dis %t.2.wasm | filecheck %s --check-prefix SECONDARY -;; This tests the case when an existing table is used as the active table, and -;; the active table and its base global already has existing uses in the +;; This tests the case when an existing table is used as the dispatch table, and +;; the dispatch table and its base global already has existing uses in the ;; secondary module. (module diff --git a/test/lit/wasm-split/multi-split2.wast b/test/lit/wasm-split/multi-split2.wast index 73578fd814e..9dc10461509 100644 --- a/test/lit/wasm-split/multi-split2.wast +++ b/test/lit/wasm-split/multi-split2.wast @@ -6,8 +6,8 @@ ;; RUN: wasm-dis %t2.wasm | filecheck %s --check-prefix=MOD2 ;; RUN: wasm-dis %t3.wasm | filecheck %s --check-prefix=MOD3 -;; A regresion test for the case the active table was not correctly shared with -;; MOD1. Because func $A is not called by any function, MOD1 is a secondary +;; A regresion test for the case the dispatch table was not correctly shared +;; with MOD1. Because func $A is not called by any function, MOD1 is a secondary ;; module who only acts as a caller. (module diff --git a/test/lit/wasm-split/no-active-segment.wast b/test/lit/wasm-split/no-dispatch-segment.wast similarity index 91% rename from test/lit/wasm-split/no-active-segment.wast rename to test/lit/wasm-split/no-dispatch-segment.wast index 4a044a8b846..de11f8e6417 100644 --- a/test/lit/wasm-split/no-active-segment.wast +++ b/test/lit/wasm-split/no-dispatch-segment.wast @@ -1,6 +1,7 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; Test that splitting succeeds even if there are no active segments for the function table. +;; Test that splitting succeeds even if there are no dispatch segments for the +;; function table. ;; RUN: wasm-split %s --split-funcs=foo -g -o1 %t.1.wasm -o2 %t.2.wasm ;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY diff --git a/test/lit/wasm-split/table-name-conflict.wast b/test/lit/wasm-split/table-name-conflict.wast index 983234b8854..47457297bbb 100644 --- a/test/lit/wasm-split/table-name-conflict.wast +++ b/test/lit/wasm-split/table-name-conflict.wast @@ -3,7 +3,7 @@ ;; RUN: cat %t.2.wast | filecheck %s --check-prefix SECONDARY ;; Regression test for a bug when an existing table, which is to be split to the -;; secondary module, has the name '0'. The newly created active table should +;; secondary module, has the name '0'. The newly created dispatch table should ;; have a different name. (module From ffc0c4981a34efdbb25d611ade539a028a7710a8 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Tue, 2 Jun 2026 06:05:24 +0000 Subject: [PATCH 2/2] Rename in test --- ...s.wast => trampoline-ignore-dispatch-segments.wast} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename test/lit/wasm-split/{trampoline-ignore-active-segments.wast => trampoline-ignore-dispatch-segments.wast} (77%) diff --git a/test/lit/wasm-split/trampoline-ignore-active-segments.wast b/test/lit/wasm-split/trampoline-ignore-dispatch-segments.wast similarity index 77% rename from test/lit/wasm-split/trampoline-ignore-active-segments.wast rename to test/lit/wasm-split/trampoline-ignore-dispatch-segments.wast index 35c1e6485e9..6bbadc9d2cd 100644 --- a/test/lit/wasm-split/trampoline-ignore-active-segments.wast +++ b/test/lit/wasm-split/trampoline-ignore-dispatch-segments.wast @@ -3,20 +3,20 @@ ;; RUN: wasm-dis %t.1.wasm | filecheck %s --check-prefix PRIMARY ;; Regression test for a bug that we incorrectly created a trampoline for -;; (elem $e0 (i32.const 0) $split) segment, even though it was one of active +;; (elem $e0 (i32.const 0) $split) segment, even though it was one of dispatch ;; segments. (module ;; PRIMARY: (type $0 (func)) (type $0 (func)) - ;; PRIMARY: (table $active_table 2 2 funcref) - (table $active_table 2 2 funcref) + ;; PRIMARY: (table $dispatch_table 2 2 funcref) + (table $dispatch_table 2 2 funcref) ;; PRIMARY: (elem $e0 (i32.const 0) $placeholder_0) ;; PRIMARY: (elem $e1 (i32.const 1) $keep) - ;; PRIMARY: (export "active_table" (table $active_table)) - (export "active_table" (table $active_table)) + ;; PRIMARY: (export "dispatch_table" (table $dispatch_table)) + (export "dispatch_table" (table $dispatch_table)) (elem $e0 (i32.const 0) $split) (elem $e1 (i32.const 1) $keep)