From cf5fe15eb8b086994555e2f894dc4de4da738da6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Nov 2025 13:53:50 -0800 Subject: [PATCH 1/5] fix --- src/passes/GlobalStructInference.cpp | 27 +++++++++---- test/lit/passes/gsi-nontype.wast | 58 ++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 824d3731dca..77badc2148e 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -309,6 +309,12 @@ struct GlobalStructInference : public Pass { bool refinalize = false; + // As we prepare to un-nest globals, we create global.gets of the global + // that we will un-nest the content to. That global does not yet exist, + // and we note such globals as we go so we ignore them (they are invalid + // IR until the global is created, later in this pass). + std::unordered_set unnestingGlobalGets; + void visitStructGet(StructGet* curr) { optimize(curr, curr->ref, curr->index); } @@ -347,14 +353,18 @@ struct GlobalStructInference : public Pass { // This is a read of an immutable field. See if it is a trivial case, of // a read from an immutable global. if (auto* get = ref->dynCast()) { - auto* global = wasm.getGlobal(get->name); - if (!global->mutable_ && !global->imported()) { - if (auto* structNew = global->init->dynCast()) { - auto value = readFromStructNew(structNew, fieldIndex, field); - // We know the exact global being read here. - value.globals.push_back(global->name); - replaceCurrent(getReadValue(value, fieldIndex, field, curr)); - return; + // The global.get must be valid, and not in the process of being + // rewritten to point to a new un-nested global. + if (!unnestingGlobalGets.count(get)) { + auto* global = wasm.getGlobal(get->name); + if (!global->mutable_ && !global->imported()) { + if (auto* structNew = global->init->dynCast()) { + auto value = readFromStructNew(structNew, fieldIndex, field); + // We know the exact global being read here. + value.globals.push_back(global->name); + replaceCurrent(getReadValue(value, fieldIndex, field, curr)); + return; + } } } } @@ -541,6 +551,7 @@ struct GlobalStructInference : public Pass { globalsToUnnest.emplace_back( GlobalToUnnest{value.globals[0], fieldIndex, get}); + unnestingGlobalGets.insert(get); ret = get; } diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast index 01831b2bff3..4d0c158adb0 100644 --- a/test/lit/passes/gsi-nontype.wast +++ b/test/lit/passes/gsi-nontype.wast @@ -267,3 +267,61 @@ ) ) +;; Nested struct.gets that seem optimizable. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $func (func (result anyref))) + (type $func (func (result anyref))) + ;; CHECK: (type $outer (sub (struct (field (ref $inner))))) + (type $outer (sub (struct (field (ref $inner))))) + ;; CHECK: (type $inner (sub (struct (field (ref $func))))) + (type $inner (sub (struct (field (ref $func))))) + ) + + ;; CHECK: (type $3 (func (result anyref))) + + ;; CHECK: (global $global.unnested.0 (ref (exact $inner)) (struct.new $inner + ;; CHECK-NEXT: (ref.func $func) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $global (ref $outer) (struct.new $outer + ;; CHECK-NEXT: (global.get $global.unnested.0) + ;; CHECK-NEXT: )) + (global $global (ref $outer) (struct.new $outer + (struct.new $inner + (ref.func $func) + ) + )) + + ;; CHECK: (func $func (type $func) (result anyref) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $func (type $func) (result anyref) + (unreachable) + ) + + ;; CHECK: (func $caller (type $3) (result anyref) + ;; CHECK-NEXT: (call_ref $func + ;; CHECK-NEXT: (struct.get $inner 0 + ;; CHECK-NEXT: (global.get $global.unnested.0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $caller (result anyref) + (call_ref $func + ;; TODO: If we did two passes, we could optimize this one too. + (struct.get $inner 0 + ;; These two can be optimized, if we un-nest the global. When doing so we + ;; turn these into a global.get, with a global name that does not exist yet + ;; (we only create that global later in the pass). We must not think it is + ;; a complete global.get and try to optimize with it when we reach the + ;; parent struct.get. + (struct.get $outer 0 + (global.get $global) + ) + ) + ) + ) +) + From bf52b9173c41f1085788867cead23107ad58e540 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Nov 2025 15:56:51 -0800 Subject: [PATCH 2/5] fix --- scripts/fuzz_opt.py | 1 - src/passes/GlobalStructInference.cpp | 18 +++++++++++++++--- test/lit/passes/gsi-nontype.wast | 4 ++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index 6ddf2cab6fe..ff9ba10ea31 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -2393,7 +2393,6 @@ def write_commands(commands, filename): ("--abstract-type-refining",), ("--cfp",), ("--cfp-reftest",), - ("--gsi",), ("--type-ssa",), ("--type-merging",)} diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 77badc2148e..ca6d7adfaf5 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -14,6 +14,9 @@ * limitations under the License. */ +// +// GlobalStructInference: Analyze struct usage globally, in particular, structs +// created (perhaps only) in globals. // // Finds types which are only created in assignments to immutable globals. For // such types we can replace a struct.get with a global.get when there is a @@ -47,7 +50,7 @@ // // This also optimizes some related things - reads from structs created in // globals - that benefit from the infrastructure here (see unnesting, below), -// even without this type-based approach. +// even without this type-based approach, and even in open world. // // TODO: Only do the case with a select when shrinkLevel == 0? // @@ -81,6 +84,9 @@ struct GlobalStructInference : public Pass { // // We will remove unoptimizable types from here, so in practice, if a type is // optimizable it will have an entry here, and not if not. + // + // This is filled in when in closed world. In open world, we cannot do such + // type-based inference, and this remains empty. std::unordered_map> typeGlobals; void run(Module* module) override { @@ -88,10 +94,14 @@ struct GlobalStructInference : public Pass { return; } - if (!getPassOptions().closedWorld) { - Fatal() << "GSI requires --closed-world"; + if (getPassOptions().closedWorld) { + analyzeClosedWorld(module); } + optimize(module); + } + + void analyzeClosedWorld(Module* module) { // First, find all the information we need. We need to know which struct // types are created in functions, because we will not be able to optimize // those. @@ -213,7 +223,9 @@ struct GlobalStructInference : public Pass { for (auto& [type, globals] : typeGlobals) { std::sort(globals.begin(), globals.end()); } + } + void optimize(Module* module) { // We are looking for the case where we can pick between two values using a // single comparison. More than two values, or more than a single // comparison, lead to tradeoffs that may not be worth it. diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast index 4d0c158adb0..3616c1a10cf 100644 --- a/test/lit/passes/gsi-nontype.wast +++ b/test/lit/passes/gsi-nontype.wast @@ -1,7 +1,7 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --gsi -all --closed-world -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --gsi -all -S -o - | filecheck %s -;; Non-type-based optimizations in --gsi +;; Non-type-based optimizations in --gsi, which work in open world. ;; Create an immutable vtable in an immutable global, which we can optimize ;; with. From 95eb0d007396567255c3d9df7a807004db9888c5 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Nov 2025 16:15:36 -0800 Subject: [PATCH 3/5] test in both open and closed --- test/lit/passes/gsi-nontype.wast | 133 ++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast index 3616c1a10cf..78dfa5a7dcd 100644 --- a/test/lit/passes/gsi-nontype.wast +++ b/test/lit/passes/gsi-nontype.wast @@ -1,22 +1,31 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: foreach %s %t wasm-opt --gsi -all -S -o - | filecheck %s -;; Non-type-based optimizations in --gsi, which work in open world. +;; RUN: foreach %s %t wasm-opt --gsi -all -S -o - | filecheck %s +;; RUN: foreach %s %t wasm-opt --gsi --closed-world -all -S -o - | filecheck %s --check-prefix=CLOSD + +;; Non-type-based optimizations in --gsi, which work in open world too. ;; Create an immutable vtable in an immutable global, which we can optimize ;; with. (module ;; CHECK: (type $vtable (struct (field funcref))) + ;; CLOSD: (type $vtable (struct (field funcref))) (type $vtable (struct funcref)) ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $imported funcref)) + ;; CLOSD: (type $1 (func)) + + ;; CLOSD: (import "a" "b" (global $imported funcref)) (import "a" "b" (global $imported funcref)) ;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: )) + ;; CLOSD: (global $vtable (ref $vtable) (struct.new $vtable + ;; CLOSD-NEXT: (global.get $imported) + ;; CLOSD-NEXT: )) (global $vtable (ref $vtable) (struct.new $vtable (global.get $imported) @@ -28,6 +37,11 @@ ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CLOSD: (func $test (type $1) + ;; CLOSD-NEXT: (drop + ;; CLOSD-NEXT: (global.get $imported) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) (func $test ;; This get reads $import. (drop @@ -41,16 +55,23 @@ ;; As above, but the global is not immutable, so we cannot optimize. (module ;; CHECK: (type $vtable (struct (field funcref))) + ;; CLOSD: (type $vtable (struct (field funcref))) (type $vtable (struct funcref)) ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $imported funcref)) + ;; CLOSD: (type $1 (func)) + + ;; CLOSD: (import "a" "b" (global $imported funcref)) (import "a" "b" (global $imported funcref)) ;; CHECK: (global $vtable (mut (ref $vtable)) (struct.new $vtable ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: )) + ;; CLOSD: (global $vtable (mut (ref $vtable)) (struct.new $vtable + ;; CLOSD-NEXT: (global.get $imported) + ;; CLOSD-NEXT: )) (global $vtable (mut (ref $vtable)) (struct.new $vtable (global.get $imported) @@ -64,6 +85,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CLOSD: (func $test (type $1) + ;; CLOSD-NEXT: (drop + ;; CLOSD-NEXT: (struct.get $vtable 0 + ;; CLOSD-NEXT: (global.get $vtable) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) (func $test (drop (struct.get $vtable 0 @@ -77,14 +105,19 @@ ;; optimize. (module ;; CHECK: (type $vtable (struct (field funcref))) + ;; CLOSD: (type $vtable (struct (field funcref))) (type $vtable (struct funcref)) ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $imported funcref)) + ;; CLOSD: (type $1 (func)) + + ;; CLOSD: (import "a" "b" (global $imported funcref)) (import "a" "b" (global $imported funcref)) ;; CHECK: (global $vtable (ref null $vtable) (ref.null none)) + ;; CLOSD: (global $vtable (ref null $vtable) (ref.null none)) (global $vtable (ref null $vtable) (ref.null $vtable)) ;; CHECK: (func $test (type $1) @@ -94,6 +127,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CLOSD: (func $test (type $1) + ;; CLOSD-NEXT: (drop + ;; CLOSD-NEXT: (struct.get $vtable 0 + ;; CLOSD-NEXT: (global.get $vtable) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) (func $test (drop (struct.get $vtable 0 @@ -107,11 +147,15 @@ ;; it into another global. (module ;; CHECK: (type $table (struct (field anyref))) + ;; CLOSD: (type $table (struct (field anyref))) (type $table (struct anyref)) ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $imported funcref)) + ;; CLOSD: (type $1 (func)) + + ;; CLOSD: (import "a" "b" (global $imported funcref)) (import "a" "b" (global $imported funcref)) ;; CHECK: (global $table.unnested.0 (ref (exact $table)) (struct.new_default $table)) @@ -119,6 +163,11 @@ ;; CHECK: (global $table (ref $table) (struct.new $table ;; CHECK-NEXT: (global.get $table.unnested.0) ;; CHECK-NEXT: )) + ;; CLOSD: (global $table.unnested.0 (ref (exact $table)) (struct.new_default $table)) + + ;; CLOSD: (global $table (ref $table) (struct.new $table + ;; CLOSD-NEXT: (global.get $table.unnested.0) + ;; CLOSD-NEXT: )) (global $table (ref $table) (struct.new $table (struct.new_default $table) @@ -130,6 +179,11 @@ ;; CHECK-NEXT: (global.get $table.unnested.0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CLOSD: (func $test (type $1) + ;; CLOSD-NEXT: (drop + ;; CLOSD-NEXT: (global.get $table.unnested.0) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) (func $test (drop (struct.get $table 0 @@ -144,19 +198,28 @@ (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct)))) + ;; CLOSD: (rec + ;; CLOSD-NEXT: (type $struct (sub (descriptor $desc (struct)))) (type $struct (sub (descriptor $desc (struct)))) ;; CHECK: (type $desc (sub (describes $struct (struct)))) + ;; CLOSD: (type $desc (sub (describes $struct (struct)))) (type $desc (sub (describes $struct (struct)))) ) ;; CHECK: (type $2 (func)) ;; CHECK: (import "a" "b" (global $imported (ref (exact $desc)))) + ;; CLOSD: (type $2 (func)) + + ;; CLOSD: (import "a" "b" (global $imported (ref (exact $desc)))) (import "a" "b" (global $imported (ref (exact $desc)))) ;; CHECK: (global $struct (ref $struct) (struct.new_default $struct ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: )) + ;; CLOSD: (global $struct (ref $struct) (struct.new_default $struct + ;; CLOSD-NEXT: (global.get $imported) + ;; CLOSD-NEXT: )) (global $struct (ref $struct) (struct.new $struct (global.get $imported) @@ -168,6 +231,11 @@ ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CLOSD: (func $test (type $2) + ;; CLOSD-NEXT: (drop + ;; CLOSD-NEXT: (global.get $imported) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) (func $test ;; This get reads $import. (drop @@ -181,11 +249,15 @@ ;; Check we do not error on a global.get of an imported global. (module ;; CHECK: (type $vtable (struct (field funcref))) + ;; CLOSD: (type $vtable (struct (field funcref))) (type $vtable (struct funcref)) ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $imported (ref $vtable))) + ;; CLOSD: (type $1 (func)) + + ;; CLOSD: (import "a" "b" (global $imported (ref $vtable))) (import "a" "b" (global $imported (ref $vtable))) ;; CHECK: (func $test (type $1) @@ -195,6 +267,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CLOSD: (func $test (type $1) + ;; CLOSD-NEXT: (drop + ;; CLOSD-NEXT: (struct.get $vtable 0 + ;; CLOSD-NEXT: (global.get $imported) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) (func $test (drop (struct.get $vtable 0 @@ -207,6 +286,7 @@ ;; A packed field. (module ;; CHECK: (type $A (struct (field i8))) + ;; CLOSD: (type $A (struct (field i8))) (type $A (struct (field i8))) ;; CHECK: (type $1 (func)) @@ -214,6 +294,11 @@ ;; CHECK: (global $global (ref $A) (struct.new $A ;; CHECK-NEXT: (i32.const -1) ;; CHECK-NEXT: )) + ;; CLOSD: (type $1 (func)) + + ;; CLOSD: (global $global (ref $A) (struct.new $A + ;; CLOSD-NEXT: (i32.const -1) + ;; CLOSD-NEXT: )) (global $global (ref $A) (struct.new $A (i32.const -1) )) @@ -226,6 +311,14 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CLOSD: (func $test (type $1) + ;; CLOSD-NEXT: (drop + ;; CLOSD-NEXT: (i32.and + ;; CLOSD-NEXT: (i32.const -1) + ;; CLOSD-NEXT: (i32.const 255) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) (func $test ;; This should be 255, not -1. (drop @@ -241,8 +334,11 @@ (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) + ;; CLOSD: (rec + ;; CLOSD-NEXT: (type $A (sub (descriptor $A.desc (struct)))) (type $A (sub (descriptor $A.desc (struct)))) ;; CHECK: (type $A.desc (sub (describes $A (struct)))) + ;; CLOSD: (type $A.desc (sub (describes $A (struct)))) (type $A.desc (sub (describes $A (struct)))) ) @@ -251,6 +347,11 @@ ;; CHECK: (global $global (ref $A) (struct.new_default $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) + ;; CLOSD: (type $2 (func (result (ref $A.desc)))) + + ;; CLOSD: (global $global (ref $A) (struct.new_default $A + ;; CLOSD-NEXT: (ref.null none) + ;; CLOSD-NEXT: )) (global $global (ref $A) (struct.new_default $A (ref.null none) )) @@ -260,6 +361,11 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CLOSD: (func $test (type $2) (result (ref $A.desc)) + ;; CLOSD-NEXT: (ref.as_non_null + ;; CLOSD-NEXT: (ref.null none) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) (func $test (result (ref $A.desc)) (ref.get_desc $A (global.get $global) @@ -272,10 +378,14 @@ (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $func (func (result anyref))) + ;; CLOSD: (rec + ;; CLOSD-NEXT: (type $func (func (result anyref))) (type $func (func (result anyref))) ;; CHECK: (type $outer (sub (struct (field (ref $inner))))) + ;; CLOSD: (type $outer (sub (struct (field (ref $inner))))) (type $outer (sub (struct (field (ref $inner))))) ;; CHECK: (type $inner (sub (struct (field (ref $func))))) + ;; CLOSD: (type $inner (sub (struct (field (ref $func))))) (type $inner (sub (struct (field (ref $func))))) ) @@ -288,6 +398,15 @@ ;; CHECK: (global $global (ref $outer) (struct.new $outer ;; CHECK-NEXT: (global.get $global.unnested.0) ;; CHECK-NEXT: )) + ;; CLOSD: (type $3 (func (result anyref))) + + ;; CLOSD: (global $global.unnested.0 (ref (exact $inner)) (struct.new $inner + ;; CLOSD-NEXT: (ref.func $func) + ;; CLOSD-NEXT: )) + + ;; CLOSD: (global $global (ref $outer) (struct.new $outer + ;; CLOSD-NEXT: (global.get $global.unnested.0) + ;; CLOSD-NEXT: )) (global $global (ref $outer) (struct.new $outer (struct.new $inner (ref.func $func) @@ -297,6 +416,9 @@ ;; CHECK: (func $func (type $func) (result anyref) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) + ;; CLOSD: (func $func (type $func) (result anyref) + ;; CLOSD-NEXT: (unreachable) + ;; CLOSD-NEXT: ) (func $func (type $func) (result anyref) (unreachable) ) @@ -308,6 +430,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CLOSD: (func $caller (type $3) (result anyref) + ;; CLOSD-NEXT: (call_ref $func + ;; CLOSD-NEXT: (struct.get $inner 0 + ;; CLOSD-NEXT: (global.get $global.unnested.0) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) + ;; CLOSD-NEXT: ) (func $caller (result anyref) (call_ref $func ;; TODO: If we did two passes, we could optimize this one too. From 4a339bda4deaa3121f2d90f8b60fa2cfa336ee89 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Nov 2025 16:47:40 -0800 Subject: [PATCH 4/5] simpler --- test/lit/passes/gsi-nontype.wast | 140 +++---------------------------- 1 file changed, 12 insertions(+), 128 deletions(-) diff --git a/test/lit/passes/gsi-nontype.wast b/test/lit/passes/gsi-nontype.wast index 78dfa5a7dcd..899fd04fa5a 100644 --- a/test/lit/passes/gsi-nontype.wast +++ b/test/lit/passes/gsi-nontype.wast @@ -1,7 +1,7 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; RUN: foreach %s %t wasm-opt --gsi -all -S -o - | filecheck %s -;; RUN: foreach %s %t wasm-opt --gsi --closed-world -all -S -o - | filecheck %s --check-prefix=CLOSD +;; RUN: foreach %s %t wasm-opt --gsi --closed-world -all -S -o - | filecheck %s ;; Non-type-based optimizations in --gsi, which work in open world too. @@ -9,23 +9,17 @@ ;; with. (module ;; CHECK: (type $vtable (struct (field funcref))) - ;; CLOSD: (type $vtable (struct (field funcref))) (type $vtable (struct funcref)) + ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $imported funcref)) - ;; CLOSD: (type $1 (func)) - - ;; CLOSD: (import "a" "b" (global $imported funcref)) (import "a" "b" (global $imported funcref)) ;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: )) - ;; CLOSD: (global $vtable (ref $vtable) (struct.new $vtable - ;; CLOSD-NEXT: (global.get $imported) - ;; CLOSD-NEXT: )) (global $vtable (ref $vtable) (struct.new $vtable (global.get $imported) @@ -37,11 +31,6 @@ ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CLOSD: (func $test (type $1) - ;; CLOSD-NEXT: (drop - ;; CLOSD-NEXT: (global.get $imported) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) (func $test ;; This get reads $import. (drop @@ -55,23 +44,17 @@ ;; As above, but the global is not immutable, so we cannot optimize. (module ;; CHECK: (type $vtable (struct (field funcref))) - ;; CLOSD: (type $vtable (struct (field funcref))) (type $vtable (struct funcref)) + ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $imported funcref)) - ;; CLOSD: (type $1 (func)) - - ;; CLOSD: (import "a" "b" (global $imported funcref)) (import "a" "b" (global $imported funcref)) ;; CHECK: (global $vtable (mut (ref $vtable)) (struct.new $vtable ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: )) - ;; CLOSD: (global $vtable (mut (ref $vtable)) (struct.new $vtable - ;; CLOSD-NEXT: (global.get $imported) - ;; CLOSD-NEXT: )) (global $vtable (mut (ref $vtable)) (struct.new $vtable (global.get $imported) @@ -85,13 +68,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CLOSD: (func $test (type $1) - ;; CLOSD-NEXT: (drop - ;; CLOSD-NEXT: (struct.get $vtable 0 - ;; CLOSD-NEXT: (global.get $vtable) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) (func $test (drop (struct.get $vtable 0 @@ -105,19 +81,15 @@ ;; optimize. (module ;; CHECK: (type $vtable (struct (field funcref))) - ;; CLOSD: (type $vtable (struct (field funcref))) (type $vtable (struct funcref)) + ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $imported funcref)) - ;; CLOSD: (type $1 (func)) - - ;; CLOSD: (import "a" "b" (global $imported funcref)) (import "a" "b" (global $imported funcref)) ;; CHECK: (global $vtable (ref null $vtable) (ref.null none)) - ;; CLOSD: (global $vtable (ref null $vtable) (ref.null none)) (global $vtable (ref null $vtable) (ref.null $vtable)) ;; CHECK: (func $test (type $1) @@ -127,13 +99,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CLOSD: (func $test (type $1) - ;; CLOSD-NEXT: (drop - ;; CLOSD-NEXT: (struct.get $vtable 0 - ;; CLOSD-NEXT: (global.get $vtable) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) (func $test (drop (struct.get $vtable 0 @@ -147,27 +112,20 @@ ;; it into another global. (module ;; CHECK: (type $table (struct (field anyref))) - ;; CLOSD: (type $table (struct (field anyref))) (type $table (struct anyref)) + ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $imported funcref)) - ;; CLOSD: (type $1 (func)) - - ;; CLOSD: (import "a" "b" (global $imported funcref)) (import "a" "b" (global $imported funcref)) + ;; CHECK: (global $table.unnested.0 (ref (exact $table)) (struct.new_default $table)) ;; CHECK: (global $table (ref $table) (struct.new $table ;; CHECK-NEXT: (global.get $table.unnested.0) ;; CHECK-NEXT: )) - ;; CLOSD: (global $table.unnested.0 (ref (exact $table)) (struct.new_default $table)) - - ;; CLOSD: (global $table (ref $table) (struct.new $table - ;; CLOSD-NEXT: (global.get $table.unnested.0) - ;; CLOSD-NEXT: )) (global $table (ref $table) (struct.new $table (struct.new_default $table) @@ -179,11 +137,6 @@ ;; CHECK-NEXT: (global.get $table.unnested.0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CLOSD: (func $test (type $1) - ;; CLOSD-NEXT: (drop - ;; CLOSD-NEXT: (global.get $table.unnested.0) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) (func $test (drop (struct.get $table 0 @@ -198,28 +151,20 @@ (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $struct (sub (descriptor $desc (struct)))) - ;; CLOSD: (rec - ;; CLOSD-NEXT: (type $struct (sub (descriptor $desc (struct)))) (type $struct (sub (descriptor $desc (struct)))) ;; CHECK: (type $desc (sub (describes $struct (struct)))) - ;; CLOSD: (type $desc (sub (describes $struct (struct)))) (type $desc (sub (describes $struct (struct)))) ) + ;; CHECK: (type $2 (func)) ;; CHECK: (import "a" "b" (global $imported (ref (exact $desc)))) - ;; CLOSD: (type $2 (func)) - - ;; CLOSD: (import "a" "b" (global $imported (ref (exact $desc)))) (import "a" "b" (global $imported (ref (exact $desc)))) ;; CHECK: (global $struct (ref $struct) (struct.new_default $struct ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: )) - ;; CLOSD: (global $struct (ref $struct) (struct.new_default $struct - ;; CLOSD-NEXT: (global.get $imported) - ;; CLOSD-NEXT: )) (global $struct (ref $struct) (struct.new $struct (global.get $imported) @@ -231,11 +176,6 @@ ;; CHECK-NEXT: (global.get $imported) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CLOSD: (func $test (type $2) - ;; CLOSD-NEXT: (drop - ;; CLOSD-NEXT: (global.get $imported) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) (func $test ;; This get reads $import. (drop @@ -249,15 +189,12 @@ ;; Check we do not error on a global.get of an imported global. (module ;; CHECK: (type $vtable (struct (field funcref))) - ;; CLOSD: (type $vtable (struct (field funcref))) (type $vtable (struct funcref)) + ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $imported (ref $vtable))) - ;; CLOSD: (type $1 (func)) - - ;; CLOSD: (import "a" "b" (global $imported (ref $vtable))) (import "a" "b" (global $imported (ref $vtable))) ;; CHECK: (func $test (type $1) @@ -267,13 +204,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CLOSD: (func $test (type $1) - ;; CLOSD-NEXT: (drop - ;; CLOSD-NEXT: (struct.get $vtable 0 - ;; CLOSD-NEXT: (global.get $imported) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) (func $test (drop (struct.get $vtable 0 @@ -286,19 +216,14 @@ ;; A packed field. (module ;; CHECK: (type $A (struct (field i8))) - ;; CLOSD: (type $A (struct (field i8))) (type $A (struct (field i8))) + ;; CHECK: (type $1 (func)) ;; CHECK: (global $global (ref $A) (struct.new $A ;; CHECK-NEXT: (i32.const -1) ;; CHECK-NEXT: )) - ;; CLOSD: (type $1 (func)) - - ;; CLOSD: (global $global (ref $A) (struct.new $A - ;; CLOSD-NEXT: (i32.const -1) - ;; CLOSD-NEXT: )) (global $global (ref $A) (struct.new $A (i32.const -1) )) @@ -311,14 +236,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CLOSD: (func $test (type $1) - ;; CLOSD-NEXT: (drop - ;; CLOSD-NEXT: (i32.and - ;; CLOSD-NEXT: (i32.const -1) - ;; CLOSD-NEXT: (i32.const 255) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) (func $test ;; This should be 255, not -1. (drop @@ -334,24 +251,17 @@ (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $A (sub (descriptor $A.desc (struct)))) - ;; CLOSD: (rec - ;; CLOSD-NEXT: (type $A (sub (descriptor $A.desc (struct)))) (type $A (sub (descriptor $A.desc (struct)))) ;; CHECK: (type $A.desc (sub (describes $A (struct)))) - ;; CLOSD: (type $A.desc (sub (describes $A (struct)))) (type $A.desc (sub (describes $A (struct)))) ) + ;; CHECK: (type $2 (func (result (ref $A.desc)))) ;; CHECK: (global $global (ref $A) (struct.new_default $A ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: )) - ;; CLOSD: (type $2 (func (result (ref $A.desc)))) - - ;; CLOSD: (global $global (ref $A) (struct.new_default $A - ;; CLOSD-NEXT: (ref.null none) - ;; CLOSD-NEXT: )) (global $global (ref $A) (struct.new_default $A (ref.null none) )) @@ -361,11 +271,6 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CLOSD: (func $test (type $2) (result (ref $A.desc)) - ;; CLOSD-NEXT: (ref.as_non_null - ;; CLOSD-NEXT: (ref.null none) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) (func $test (result (ref $A.desc)) (ref.get_desc $A (global.get $global) @@ -378,17 +283,15 @@ (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $func (func (result anyref))) - ;; CLOSD: (rec - ;; CLOSD-NEXT: (type $func (func (result anyref))) (type $func (func (result anyref))) ;; CHECK: (type $outer (sub (struct (field (ref $inner))))) - ;; CLOSD: (type $outer (sub (struct (field (ref $inner))))) (type $outer (sub (struct (field (ref $inner))))) ;; CHECK: (type $inner (sub (struct (field (ref $func))))) - ;; CLOSD: (type $inner (sub (struct (field (ref $func))))) (type $inner (sub (struct (field (ref $func))))) ) + + ;; CHECK: (type $3 (func (result anyref))) ;; CHECK: (global $global.unnested.0 (ref (exact $inner)) (struct.new $inner @@ -398,15 +301,6 @@ ;; CHECK: (global $global (ref $outer) (struct.new $outer ;; CHECK-NEXT: (global.get $global.unnested.0) ;; CHECK-NEXT: )) - ;; CLOSD: (type $3 (func (result anyref))) - - ;; CLOSD: (global $global.unnested.0 (ref (exact $inner)) (struct.new $inner - ;; CLOSD-NEXT: (ref.func $func) - ;; CLOSD-NEXT: )) - - ;; CLOSD: (global $global (ref $outer) (struct.new $outer - ;; CLOSD-NEXT: (global.get $global.unnested.0) - ;; CLOSD-NEXT: )) (global $global (ref $outer) (struct.new $outer (struct.new $inner (ref.func $func) @@ -416,9 +310,6 @@ ;; CHECK: (func $func (type $func) (result anyref) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - ;; CLOSD: (func $func (type $func) (result anyref) - ;; CLOSD-NEXT: (unreachable) - ;; CLOSD-NEXT: ) (func $func (type $func) (result anyref) (unreachable) ) @@ -430,13 +321,6 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CLOSD: (func $caller (type $3) (result anyref) - ;; CLOSD-NEXT: (call_ref $func - ;; CLOSD-NEXT: (struct.get $inner 0 - ;; CLOSD-NEXT: (global.get $global.unnested.0) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) - ;; CLOSD-NEXT: ) (func $caller (result anyref) (call_ref $func ;; TODO: If we did two passes, we could optimize this one too. From 2d4d664262c48290589612863ef0be635687a706 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Mon, 3 Nov 2025 19:35:39 -0800 Subject: [PATCH 5/5] run gsi in the default pipeline in open world too --- src/passes/pass.cpp | 4 +++- test/unit/test_passes.py | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 8c8ba8d33d0..56a07ca2d44 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -769,7 +769,9 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() { } else { addIfNoDWARFIssues("cfp"); } - addIfNoDWARFIssues("gsi"); + } + addIfNoDWARFIssues("gsi"); + if (options.closedWorld) { addIfNoDWARFIssues("abstract-type-refining"); addIfNoDWARFIssues("unsubtyping"); } diff --git a/test/unit/test_passes.py b/test/unit/test_passes.py index 838f1d2f73f..a8a87c8819b 100644 --- a/test/unit/test_passes.py +++ b/test/unit/test_passes.py @@ -36,7 +36,6 @@ def test_O2(self): 'signature-refining', 'gto', 'cfp', - 'gsi', ] for pass_ in CLOSED_WORLD_PASSES: if closed_world: @@ -44,6 +43,13 @@ def test_O2(self): else: self.assertNotIn(pass_, passes) + # some passes run in open world too + OPEN_WORLD_PASSES = [ + 'gsi', + ] + for pass_ in OPEN_WORLD_PASSES: + self.assertIn(pass_, passes) + def test_O3_O1(self): # When we run something like -O3 -O1 we should run -O3 followed by -O1 # (and not -O1 -O1, which would be the case if the last commandline