From 65d3cdf22e0bf9d39af2d637dbba37e58fc810eb Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 4 May 2026 14:24:02 -0700 Subject: [PATCH 1/7] fix --- src/ir/table-utils.cpp | 49 ++++++++---- src/ir/table-utils.h | 5 ++ src/passes/Directize.cpp | 8 +- test/lit/passes/directize_all-features.wast | 88 +++++++++++++++++++++ 4 files changed, 132 insertions(+), 18 deletions(-) diff --git a/src/ir/table-utils.cpp b/src/ir/table-utils.cpp index cb10aff82b9..5d48fbfec39 100644 --- a/src/ir/table-utils.cpp +++ b/src/ir/table-utils.cpp @@ -118,39 +118,60 @@ TableInfoMap computeTableInfo(Module& wasm, bool initialContentsImmutable) { return tables; } - using TablesWithSet = std::unordered_set; - - ModuleUtils::ParallelFunctionAnalysis analysis( - wasm, [&](Function* func, TablesWithSet& tablesWithSet) { + // Miniature form of TableInfo, without things we don't need (some of which + // cause compilation errors on the copies below). + struct MiniTableInfo { + // Whether the table may be modified at runtime, either because it is imported + // or exported, or table.set operations exist for it in the code. + bool mayBeModified = false; + + // Whether the table may grow. Growing does modify the table, but it only + // appends, so we track this separately from mayBeModified, which allows more + // optimizations in tables that grow but have no other sets. + bool mayGrow = false; + }; + + using MiniTableInfoMap = std::unordered_map; + + ModuleUtils::ParallelFunctionAnalysis analysis( + wasm, [&](Function* func, MiniTableInfoMap& tableInfoMap) { if (func->imported()) { return; } struct Finder : public PostWalker { - TablesWithSet& tablesWithSet; + MiniTableInfoMap& tableInfoMap; - Finder(TablesWithSet& tablesWithSet) : tablesWithSet(tablesWithSet) {} + Finder(MiniTableInfoMap& tableInfoMap) : tableInfoMap(tableInfoMap) {} void visitTableSet(TableSet* curr) { - tablesWithSet.insert(curr->table); + tableInfoMap[curr->table].mayBeModified = true; } void visitTableFill(TableFill* curr) { - tablesWithSet.insert(curr->table); + tableInfoMap[curr->table].mayBeModified = true; } void visitTableCopy(TableCopy* curr) { - tablesWithSet.insert(curr->destTable); + tableInfoMap[curr->destTable].mayBeModified = true; } void visitTableInit(TableInit* curr) { - tablesWithSet.insert(curr->table); + tableInfoMap[curr->table].mayBeModified = true; + } + void visitTableGrow(TableGrow* curr) { + tableInfoMap[curr->table].mayGrow = true; } }; - Finder(tablesWithSet).walkFunction(func); + Finder(tableInfoMap).walkFunction(func); }); - for (auto& [_, names] : analysis.map) { - for (auto name : names) { - tables[name].mayBeModified = true; + for (auto& [_, tableInfoMap] : analysis.map) { + for (auto& [tableName, info] : tableInfoMap) { + if (info.mayBeModified) { + tables[tableName].mayBeModified = true; + } + if (info.mayGrow) { + tables[tableName].mayGrow = true; + } } } diff --git a/src/ir/table-utils.h b/src/ir/table-utils.h index 4e788a2685e..d810eb249e7 100644 --- a/src/ir/table-utils.h +++ b/src/ir/table-utils.h @@ -126,6 +126,11 @@ struct TableInfo { // or exported, or table.set operations exist for it in the code. bool mayBeModified = false; + // Whether the table may grow. Growing does modify the table, but it only + // appends, so we track this separately from mayBeModified, which allows more + // optimizations in tables that grow but have no other sets. + bool mayGrow = false; + // Whether we can assume that the initial contents are immutable. That is, if // a table looks like [a, b, c] in the wasm, and we see a call to index 1, we // will assume it must call b. It is possible that the table is appended to, diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp index c0cd9e221ab..baaac576c36 100644 --- a/src/passes/Directize.cpp +++ b/src/passes/Directize.cpp @@ -127,13 +127,13 @@ struct FunctionDirectizer : public WalkerPass> { // The index is out of bounds for the initial table's content. This may // trap, but it may also not trap if the table is modified later (if a // function is appended to it). - if (!table.mayBeModified) { + if (!table.mayBeModified && !table.mayGrow) { return CallUtils::Trap{}; } else { // The table may be modified, so it might be appended to. We should only - // get here in the case that the initial contents are immutable, as - // otherwise we have nothing to optimize at all. - assert(table.initialContentsImmutable); + // get here in the case that the initial contents are immutable, or the + // table can grow, as otherwise we have nothing to optimize at all. + assert(table.initialContentsImmutable || table.mayGrow); return CallUtils::Unknown{}; } } diff --git a/test/lit/passes/directize_all-features.wast b/test/lit/passes/directize_all-features.wast index 03ff576245d..952f2b93683 100644 --- a/test/lit/passes/directize_all-features.wast +++ b/test/lit/passes/directize_all-features.wast @@ -1953,3 +1953,91 @@ ) ) +(module + ;; CHECK: (type $func (func)) + ;; IMMUT: (type $func (func)) + (type $func (func)) + + ;; CHECK: (table $table 5 funcref) + ;; IMMUT: (table $table 5 funcref) + (table $table 5 funcref) + + ;; CHECK: (elem $table (i32.const 1) $target) + ;; IMMUT: (elem $table (i32.const 1) $target) + (elem $table (i32.const 1) $target) + + ;; CHECK: (elem declare func $grow) + + ;; CHECK: (export "caller" (func $caller)) + + ;; CHECK: (func $grow (type $func) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $table + ;; CHECK-NEXT: (ref.func $grow) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; IMMUT: (elem declare func $grow) + + ;; IMMUT: (export "caller" (func $caller)) + + ;; IMMUT: (func $grow (type $func) + ;; IMMUT-NEXT: (drop + ;; IMMUT-NEXT: (table.grow $table + ;; IMMUT-NEXT: (ref.func $grow) + ;; IMMUT-NEXT: (i32.const 42) + ;; IMMUT-NEXT: ) + ;; IMMUT-NEXT: ) + ;; IMMUT-NEXT: ) + (func $grow + (drop + (table.grow $table + (ref.func $grow) + (i32.const 42) + ) + ) + ) + + ;; CHECK: (func $caller (type $func) + ;; CHECK-NEXT: (call $target) + ;; CHECK-NEXT: (call_indirect $table (type $func) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call_indirect $table (type $func) + ;; CHECK-NEXT: (i32.const 1000) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; IMMUT: (func $caller (type $func) + ;; IMMUT-NEXT: (call $target) + ;; IMMUT-NEXT: (call_indirect $table (type $func) + ;; IMMUT-NEXT: (i32.const 10) + ;; IMMUT-NEXT: ) + ;; IMMUT-NEXT: (call_indirect $table (type $func) + ;; IMMUT-NEXT: (i32.const 1000) + ;; IMMUT-NEXT: ) + ;; IMMUT-NEXT: ) + (func $caller (export "caller") + ;; This is in the elem segment, so we can optimize it. Growth can only append. + (call_indirect (type $func) + (i32.const 1) + ) + ;; This is in the range that we grow to, if grow() is called, but we don't + ;; know if it will, so we don't optimize. + (call_indirect (type $func) + (i32.const 10) + ) + ;; This is in the range that we grow to, if grow() is called multiple times, + ;; but again we can't optimize. + (call_indirect (type $func) + (i32.const 1000) + ) + ) + + ;; CHECK: (func $target (type $func) + ;; CHECK-NEXT: ) + ;; IMMUT: (func $target (type $func) + ;; IMMUT-NEXT: ) + (func $target + ) +) From 92d3689a87ed4abf584aaff76760e2eb4288ffec Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 4 May 2026 14:24:08 -0700 Subject: [PATCH 2/7] format --- src/ir/table-utils.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ir/table-utils.cpp b/src/ir/table-utils.cpp index 5d48fbfec39..251681779e8 100644 --- a/src/ir/table-utils.cpp +++ b/src/ir/table-utils.cpp @@ -121,13 +121,13 @@ TableInfoMap computeTableInfo(Module& wasm, bool initialContentsImmutable) { // Miniature form of TableInfo, without things we don't need (some of which // cause compilation errors on the copies below). struct MiniTableInfo { - // Whether the table may be modified at runtime, either because it is imported - // or exported, or table.set operations exist for it in the code. + // Whether the table may be modified at runtime, either because it is + // imported or exported, or table.set operations exist for it in the code. bool mayBeModified = false; // Whether the table may grow. Growing does modify the table, but it only - // appends, so we track this separately from mayBeModified, which allows more - // optimizations in tables that grow but have no other sets. + // appends, so we track this separately from mayBeModified, which allows + // more optimizations in tables that grow but have no other sets. bool mayGrow = false; }; From 3168533acc5ab37545be3189be08c9dba9a7c097 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 4 May 2026 14:25:10 -0700 Subject: [PATCH 3/7] clan --- src/ir/table-utils.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/ir/table-utils.cpp b/src/ir/table-utils.cpp index 251681779e8..cf8cc2e6e8f 100644 --- a/src/ir/table-utils.cpp +++ b/src/ir/table-utils.cpp @@ -121,13 +121,7 @@ TableInfoMap computeTableInfo(Module& wasm, bool initialContentsImmutable) { // Miniature form of TableInfo, without things we don't need (some of which // cause compilation errors on the copies below). struct MiniTableInfo { - // Whether the table may be modified at runtime, either because it is - // imported or exported, or table.set operations exist for it in the code. bool mayBeModified = false; - - // Whether the table may grow. Growing does modify the table, but it only - // appends, so we track this separately from mayBeModified, which allows - // more optimizations in tables that grow but have no other sets. bool mayGrow = false; }; From 0f7d10cce02a33781449c7369f80a387253ef02a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 4 May 2026 14:25:55 -0700 Subject: [PATCH 4/7] clan --- src/ir/table-utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/table-utils.h b/src/ir/table-utils.h index d810eb249e7..6fe458c4751 100644 --- a/src/ir/table-utils.h +++ b/src/ir/table-utils.h @@ -127,7 +127,7 @@ struct TableInfo { bool mayBeModified = false; // Whether the table may grow. Growing does modify the table, but it only - // appends, so we track this separately from mayBeModified, which allows more + // appends, so we track this separately from mayBeModified. This allows more // optimizations in tables that grow but have no other sets. bool mayGrow = false; From 13b3c4ba3e9fa36c54d953996113070d73722a83 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 4 May 2026 14:26:55 -0700 Subject: [PATCH 5/7] clan --- test/lit/passes/directize_all-features.wast | 1 + 1 file changed, 1 insertion(+) diff --git a/test/lit/passes/directize_all-features.wast b/test/lit/passes/directize_all-features.wast index 952f2b93683..adb064e8a96 100644 --- a/test/lit/passes/directize_all-features.wast +++ b/test/lit/passes/directize_all-features.wast @@ -1953,6 +1953,7 @@ ) ) +;; table.grow inhibits some optimizations. (module ;; CHECK: (type $func (func)) ;; IMMUT: (type $func (func)) From bf6eaaa1393d259af4b4e29f2c491f06c83b89f0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 4 May 2026 14:36:32 -0700 Subject: [PATCH 6/7] add a comment --- src/passes/Directize.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp index baaac576c36..03b2513743c 100644 --- a/src/passes/Directize.cpp +++ b/src/passes/Directize.cpp @@ -139,6 +139,11 @@ struct FunctionDirectizer : public WalkerPass> { } auto name = flatTable.names[index]; if (!name.is()) { + // No segment wrote to this part of the initial contents of the table. + // This must trap, as we only get here if we can optimize such cases, + // relying on the fact that the table cannot be modified, or at least the + // initial contents cannot be. + assert(!table.mayBeModified || table.initialContentsImmutable); return CallUtils::Trap{}; } auto* func = getModule()->getFunction(name); From 7200272e5f20eb2cd4ed87b357d84b89772746d9 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 4 May 2026 15:39:09 -0700 Subject: [PATCH 7/7] feedback --- src/ir/table-utils.cpp | 28 ++--- src/ir/table-utils.h | 16 ++- src/passes/Directize.cpp | 6 +- src/passes/RemoveUnusedModuleElements.cpp | 6 +- .../remove-unused-module-elements-tables.wast | 115 +++++++++++++++++- 5 files changed, 146 insertions(+), 25 deletions(-) diff --git a/src/ir/table-utils.cpp b/src/ir/table-utils.cpp index cf8cc2e6e8f..516b12a215b 100644 --- a/src/ir/table-utils.cpp +++ b/src/ir/table-utils.cpp @@ -95,13 +95,13 @@ TableInfoMap computeTableInfo(Module& wasm, bool initialContentsImmutable) { for (auto& table : wasm.tables) { if (table->imported()) { - tables[table->name].mayBeModified = true; + tables[table->name].hasSet = true; } } for (auto& ex : wasm.exports) { if (ex->kind == ExternalKind::Table) { - tables[*ex->getInternalName()].mayBeModified = true; + tables[*ex->getInternalName()].hasSet = true; } } @@ -109,7 +109,7 @@ TableInfoMap computeTableInfo(Module& wasm, bool initialContentsImmutable) { // might learn anything new. auto hasUnmodifiableTable = false; for (auto& [_, info] : tables) { - if (!info.mayBeModified) { + if (!info.hasSet) { hasUnmodifiableTable = true; break; } @@ -121,8 +121,8 @@ TableInfoMap computeTableInfo(Module& wasm, bool initialContentsImmutable) { // Miniature form of TableInfo, without things we don't need (some of which // cause compilation errors on the copies below). struct MiniTableInfo { - bool mayBeModified = false; - bool mayGrow = false; + bool hasSet = false; + bool hasGrow = false; }; using MiniTableInfoMap = std::unordered_map; @@ -139,19 +139,19 @@ TableInfoMap computeTableInfo(Module& wasm, bool initialContentsImmutable) { Finder(MiniTableInfoMap& tableInfoMap) : tableInfoMap(tableInfoMap) {} void visitTableSet(TableSet* curr) { - tableInfoMap[curr->table].mayBeModified = true; + tableInfoMap[curr->table].hasSet = true; } void visitTableFill(TableFill* curr) { - tableInfoMap[curr->table].mayBeModified = true; + tableInfoMap[curr->table].hasSet = true; } void visitTableCopy(TableCopy* curr) { - tableInfoMap[curr->destTable].mayBeModified = true; + tableInfoMap[curr->destTable].hasSet = true; } void visitTableInit(TableInit* curr) { - tableInfoMap[curr->table].mayBeModified = true; + tableInfoMap[curr->table].hasSet = true; } void visitTableGrow(TableGrow* curr) { - tableInfoMap[curr->table].mayGrow = true; + tableInfoMap[curr->table].hasGrow = true; } }; @@ -160,11 +160,11 @@ TableInfoMap computeTableInfo(Module& wasm, bool initialContentsImmutable) { for (auto& [_, tableInfoMap] : analysis.map) { for (auto& [tableName, info] : tableInfoMap) { - if (info.mayBeModified) { - tables[tableName].mayBeModified = true; + if (info.hasSet) { + tables[tableName].hasSet = true; } - if (info.mayGrow) { - tables[tableName].mayGrow = true; + if (info.hasGrow) { + tables[tableName].hasGrow = true; } } } diff --git a/src/ir/table-utils.h b/src/ir/table-utils.h index 6fe458c4751..884f2309797 100644 --- a/src/ir/table-utils.h +++ b/src/ir/table-utils.h @@ -122,14 +122,14 @@ bool usesExpressions(ElementSegment* curr, Module* module); // Information about a table's optimizability. struct TableInfo { - // Whether the table may be modified at runtime, either because it is imported - // or exported, or table.set operations exist for it in the code. - bool mayBeModified = false; + // Whether the table has writes to it (anything but a grow, see below). The + // writes may be internal, or through imports and exports. + bool hasSet = false; // Whether the table may grow. Growing does modify the table, but it only // appends, so we track this separately from mayBeModified. This allows more // optimizations in tables that grow but have no other sets. - bool mayGrow = false; + bool hasGrow = false; // Whether we can assume that the initial contents are immutable. That is, if // a table looks like [a, b, c] in the wasm, and we see a call to index 1, we @@ -149,6 +149,9 @@ struct TableInfo { std::unique_ptr flatTable; + // Whether the contents may change. + bool mayBeModified() const { return hasSet || hasGrow; } + // Whether we can optimize using this table's data on the entry level, that // is, individual entries in the table are known to us, so calls through the // table with known indexes can be inferred, etc. @@ -159,7 +162,10 @@ struct TableInfo { // contents, even if other things might be appended later, which we // cannot infer). // * The table is flat (so we can see what is in it, by index). - return (!mayBeModified || initialContentsImmutable) && flatTable->valid; + // + // Note that we do not check hasGrow, as we can optimize at least *some* + // entries in that case (growth only appends). + return (!hasSet || initialContentsImmutable) && flatTable->valid; } }; diff --git a/src/passes/Directize.cpp b/src/passes/Directize.cpp index 03b2513743c..2c0809b8dba 100644 --- a/src/passes/Directize.cpp +++ b/src/passes/Directize.cpp @@ -127,13 +127,13 @@ struct FunctionDirectizer : public WalkerPass> { // The index is out of bounds for the initial table's content. This may // trap, but it may also not trap if the table is modified later (if a // function is appended to it). - if (!table.mayBeModified && !table.mayGrow) { + if (!table.mayBeModified()) { return CallUtils::Trap{}; } else { // The table may be modified, so it might be appended to. We should only // get here in the case that the initial contents are immutable, or the // table can grow, as otherwise we have nothing to optimize at all. - assert(table.initialContentsImmutable || table.mayGrow); + assert(table.initialContentsImmutable || table.hasGrow); return CallUtils::Unknown{}; } } @@ -143,7 +143,7 @@ struct FunctionDirectizer : public WalkerPass> { // This must trap, as we only get here if we can optimize such cases, // relying on the fact that the table cannot be modified, or at least the // initial contents cannot be. - assert(!table.mayBeModified || table.initialContentsImmutable); + assert(!table.hasSet || table.initialContentsImmutable); return CallUtils::Trap{}; } auto* func = getModule()->getFunction(name); diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index 8bba990af8d..fd026490cb1 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -432,11 +432,13 @@ struct Analyzer { // Note a possible call of a function reference as well, if something else // might be written into the table during runtime. - // TODO: Add an option for immutable initial content like Directize? + // TODO: Add an option for immutable initial content like Directize? Can + // also check for grow without set, which leaves initial entries + // fixed. if (!tableInfoMap) { tableInfoMap = TableUtils::computeTableInfo(*module); } - if ((*tableInfoMap)[table].mayBeModified) { + if ((*tableInfoMap)[table].mayBeModified()) { useCallRefType(type); } } diff --git a/test/lit/passes/remove-unused-module-elements-tables.wast b/test/lit/passes/remove-unused-module-elements-tables.wast index b0b6bd323a8..a556c09948f 100644 --- a/test/lit/passes/remove-unused-module-elements-tables.wast +++ b/test/lit/passes/remove-unused-module-elements-tables.wast @@ -333,7 +333,7 @@ ) ) -;; As above, but now the table has an initialistion expression +;; As above, but now the table has an initialization expression (module (rec ;; CHECK: (rec @@ -447,3 +447,116 @@ (drop (i32.const 30)) ) ) + +;; As above, but now the table has a table.grow. Like table.set, this prevents +;; optimization. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $foo (func)) + ;; OPEN_WORLD: (rec + ;; OPEN_WORLD-NEXT: (type $foo (func)) + (type $foo (func)) + ;; CHECK: (type $bar (func)) + ;; OPEN_WORLD: (type $bar (func)) + (type $bar (func)) + ) + + ;; CHECK: (type $2 (func)) + + ;; CHECK: (table $table 10 funcref) + ;; OPEN_WORLD: (type $2 (func)) + + ;; OPEN_WORLD: (table $table 10 funcref) + (table $table 10 funcref) + ;; CHECK: (elem $table (i32.const 0) $foo-in-table $bar) + ;; OPEN_WORLD: (elem $table (i32.const 0) $foo-in-table $bar) + (elem $table (i32.const 0) $foo-in-table $bar) + + ;; CHECK: (elem declare func $foo-not-in-table) + + ;; CHECK: (export "export" (func $export)) + + ;; CHECK: (func $export (type $2) + ;; CHECK-NEXT: (call_indirect $table (type $foo) + ;; CHECK-NEXT: (i32.const 5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $table + ;; CHECK-NEXT: (ref.func $foo-not-in-table) + ;; CHECK-NEXT: (i32.const 7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPEN_WORLD: (elem declare func $foo-not-in-table) + + ;; OPEN_WORLD: (export "export" (func $export)) + + ;; OPEN_WORLD: (func $export (type $2) + ;; OPEN_WORLD-NEXT: (call_indirect $table (type $foo) + ;; OPEN_WORLD-NEXT: (i32.const 5) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: (drop + ;; OPEN_WORLD-NEXT: (table.grow $table + ;; OPEN_WORLD-NEXT: (ref.func $foo-not-in-table) + ;; OPEN_WORLD-NEXT: (i32.const 7) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + (func $export (export "export") + (call_indirect $table (type $foo) + (i32.const 5) + ) + (drop + (table.grow $table + (ref.func $foo-not-in-table) + (i32.const 7) + ) + ) + ) + + ;; CHECK: (func $foo-in-table (type $foo) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPEN_WORLD: (func $foo-in-table (type $foo) + ;; OPEN_WORLD-NEXT: (drop + ;; OPEN_WORLD-NEXT: (i32.const 10) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + (func $foo-in-table (type $foo) + ;; This is in the table, and might be reached. + (drop (i32.const 10)) + ) + + ;; CHECK: (func $foo-not-in-table (type $foo) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPEN_WORLD: (func $foo-not-in-table (type $foo) + ;; OPEN_WORLD-NEXT: (drop + ;; OPEN_WORLD-NEXT: (i32.const 20) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + (func $foo-not-in-table (type $foo) + ;; The reference taken of this function might be added to the table using + ;; table.grow, so we can do nothing here. + (drop (i32.const 20)) + ) + + ;; CHECK: (func $bar (type $bar) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; OPEN_WORLD: (func $bar (type $bar) + ;; OPEN_WORLD-NEXT: (drop + ;; OPEN_WORLD-NEXT: (i32.const 30) + ;; OPEN_WORLD-NEXT: ) + ;; OPEN_WORLD-NEXT: ) + (func $bar (type $bar) + ;; Not even references, so this is unreachable in closed world. + (drop (i32.const 30)) + ) +) +