From 2b756413c7a4491f1bcbed35bcdef16426a0c2b4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:24:31 -0800 Subject: [PATCH 01/10] go --- src/ir/possible-contents.cpp | 14 ++++++++++---- src/ir/possible-contents.h | 32 ++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index b9f2213fc9a..6a47836f53c 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -208,9 +208,9 @@ void PossibleContents::intersect(const PossibleContents& other) { // Note the global's information, if we started as a global. In that case, the // code below will refine our type but we can remain a global, which we will // accomplish by restoring our global status at the end. - std::optional globalName; + std::optional global; if (isGlobal()) { - globalName = getGlobal(); + global = getGlobal(); } if (hasFullCone() && other.hasFullCone()) { @@ -230,9 +230,9 @@ void PossibleContents::intersect(const PossibleContents& other) { value = ConeType{newType, std::min(newDepth, otherNewDepth)}; } - if (globalName) { + if (global) { // Restore the global but keep the new and refined type. - value = GlobalInfo{*globalName, getType()}; + value = GlobalInfo{global->name, global->kind, getType()}; } } @@ -2826,6 +2826,12 @@ void Flower::filterExpressionContents(PossibleContents& contents, void Flower::filterGlobalContents(PossibleContents& contents, const GlobalLocation& globalLoc) { + // Function imports are always immutable. + auto kind = contents.getGlobal().kind; + if (kind == ExternalKind::Function) { + return; + } + assert(kind == ExternalKind::Global); auto* global = wasm.getGlobal(globalLoc.name); if (global->mutable_ == Immutable) { // This is an immutable global. We never need to consider this value as diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index eafb3d8d400..c3f89fd0568 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -40,10 +40,10 @@ namespace wasm { // // * Literal: One possible constant value like an i32 of 42. // -// * Global: The name of a global whose value is here. We do not know -// the actual value at compile time, but we know it is equal -// to that global. Typically we can only infer this for -// immutable globals. +// * Global: An immutable global value, something who we can identify +// but do not know the concree value of at runtime. This can +// be either a literal (immutable) global, or an imported +// function (which is effectively the same). // // * ConeType: Any possible value of a particular type, and a possible // "cone" of a certain depth below it. If the depth is 0 @@ -85,6 +85,7 @@ class PossibleContents { struct GlobalInfo { Name name; + ExternalKind kind; // The type of contents. Note that this may not match the type of the // global, if we were filtered. For example: // @@ -99,7 +100,7 @@ class PossibleContents { // way. In principle, not having depth info can lead to loss of // precision. bool operator==(const GlobalInfo& other) const { - return name == other.name && type == other.type; + return name == other.name && kind == other.kind && type == other.type; } }; @@ -144,8 +145,8 @@ class PossibleContents { static PossibleContents none() { return PossibleContents{None()}; } static PossibleContents literal(Literal c) { return PossibleContents{c}; } - static PossibleContents global(Name name, Type type) { - return PossibleContents{GlobalInfo{name, type}}; + static PossibleContents global(Name name, ExternalKind kind, Type type) { + return PossibleContents{GlobalInfo{name, kind, type}}; } // Helper for a cone type with depth 0, i.e., an exact type. static PossibleContents exactType(Type type) { @@ -216,9 +217,9 @@ class PossibleContents { return std::get(value); } - Name getGlobal() const { + GlobalInfo getGlobal() const { assert(isGlobal()); - return std::get(value).name; + return std::get(value); } bool isNull() const { return isLiteral() && getLiteral().isNull(); } @@ -316,11 +317,16 @@ class PossibleContents { if (isLiteral()) { return builder.makeConstantExpression(getLiteral()); } else { - auto name = getGlobal(); + auto info = getGlobal(); // Note that we load the type from the module, rather than use the type // in the GlobalInfo, as that type may not match the global (see comment // in the GlobalInfo declaration above). - return builder.makeGlobalGet(name, wasm.getGlobal(name)->type); + if (info.kind == ExternalKind::Global) { + return builder.makeGlobalGet(name, wasm.getGlobal(name)->type); + } else { + assert(info.kind == ExternalKind::Function) { + return builder.makeRefFunc(name, wasm.getFunction(name)->type.getHeapType()); + } } } @@ -352,6 +358,7 @@ class PossibleContents { rehash(ret, getLiteral()); } else if (auto* global = std::get_if(&value)) { rehash(ret, global->name); + rehash(ret, global->kind); rehash(ret, global->type); } else if (auto* coneType = std::get_if(&value)) { rehash(ret, coneType->type); @@ -374,7 +381,8 @@ class PossibleContents { o << " HT: " << h; } } else if (isGlobal()) { - o << "GlobalInfo $" << getGlobal() << " T: " << getType(); + auto info = getGlobal(); + o << "GlobalInfo $" << info.name << " K: " << info.kind << " T: " << getType(); } else if (auto* coneType = std::get_if(&value)) { auto t = coneType->type; o << "ConeType " << t; From 93221796b32da3a723b5924972f34f2bc0e7a7fd Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:26:46 -0800 Subject: [PATCH 02/10] more --- src/ir/possible-contents.h | 8 ++++---- test/gtest/possible-contents.cpp | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index c3f89fd0568..1fa9ec1889a 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -322,10 +322,10 @@ class PossibleContents { // in the GlobalInfo, as that type may not match the global (see comment // in the GlobalInfo declaration above). if (info.kind == ExternalKind::Global) { - return builder.makeGlobalGet(name, wasm.getGlobal(name)->type); + return builder.makeGlobalGet(info.name, wasm.getGlobal(info.name)->type); } else { - assert(info.kind == ExternalKind::Function) { - return builder.makeRefFunc(name, wasm.getFunction(name)->type.getHeapType()); + assert(info.kind == ExternalKind::Function); + return builder.makeRefFunc(info.name, wasm.getFunction(info.name)->type.getHeapType()); } } } @@ -382,7 +382,7 @@ class PossibleContents { } } else if (isGlobal()) { auto info = getGlobal(); - o << "GlobalInfo $" << info.name << " K: " << info.kind << " T: " << getType(); + o << "GlobalInfo $" << info.name << " K: " << int(info.kind) << " T: " << getType(); } else if (auto* coneType = std::get_if(&value)) { auto t = coneType->type; o << "ConeType " << t; diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index 2633bdb3714..9c81f26efb8 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -80,14 +80,14 @@ class PossibleContentsTest : public testing::Test { PossibleContents::literal(Literal::makeNull(HeapType::i31)); PossibleContents i32Global1 = - PossibleContents::global("i32Global1", Type::i32); + PossibleContents::global("i32Global1", ExternalKind::Global, Type::i32); PossibleContents i32Global2 = - PossibleContents::global("i32Global2", Type::i32); - PossibleContents f64Global = PossibleContents::global("f64Global", Type::f64); - PossibleContents anyGlobal = PossibleContents::global("anyGlobal", anyref); - PossibleContents funcGlobal = PossibleContents::global("funcGlobal", funcref); + PossibleContents::global("i32Global2", ExternalKind::Global, Type::i32); + PossibleContents f64Global = PossibleContents::global("f64Global", ExternalKind::Global, Type::f64); + PossibleContents anyGlobal = PossibleContents::global("anyGlobal", ExternalKind::Global, anyref); + PossibleContents funcGlobal = PossibleContents::global("funcGlobal", ExternalKind::Global, funcref); PossibleContents nonNullFuncGlobal = - PossibleContents::global("funcGlobal", Type(HeapType::func, NonNullable)); + PossibleContents::global("funcGlobal", ExternalKind::Global, Type(HeapType::func, NonNullable)); PossibleContents nonNullFunc = PossibleContents::literal( Literal::makeFunc("func", Signature(Type::none, Type::none))); @@ -787,7 +787,7 @@ TEST_F(PossibleContentsTest, TestStructCones) { // Filter a global to a more specific type. assertIntersection(funcGlobal, PossibleContents::coneType(signature), - PossibleContents::global("funcGlobal", signature)); + PossibleContents::global("funcGlobal", ExternalKind::Global, signature)); // Filter a global's nullability only. auto nonNullFuncRef = Type(HeapType::func, NonNullable); From 3de0316a9c103dd5c10b79c82ae9e635aae95b9d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:27:10 -0800 Subject: [PATCH 03/10] more --- src/ir/possible-contents.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 6a47836f53c..5b5a4eff34c 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -2842,7 +2842,7 @@ void Flower::filterGlobalContents(PossibleContents& contents, // a cone/exact type *and* that something is equal to a global, in some // cases. See https://github.com/WebAssembly/binaryen/pull/5083 if (contents.isMany() || contents.isConeType()) { - contents = PossibleContents::global(global->name, global->type); + contents = PossibleContents::global(global->name, ExternalKind::Global, global->type); // TODO: We could do better here, to set global->init->type instead of // global->type, or even the contents.getType() - either of those From 176e8eb4f5bceb76f765dea2eb19d7eaf2cc6a01 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:28:06 -0800 Subject: [PATCH 04/10] more --- test/lit/passes/gufa.wast | 104 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index 721c27eeb66..49676b493d3 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1154,3 +1154,107 @@ ) ) ) + +;; We cannot know the types of imported functions, so we should not be able to +;; optimize this exact cast. +(module + ;; CHECK: (type $func (sub (func))) + (type $func (sub (func))) + (type $sub (sub $func (func))) + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (import "" "" (func $f (type $func))) + (import "" "" (func $f (type $func))) + ;; CHECK: (elem declare func $f) + + ;; CHECK: (export "test" (func $test)) + + ;; CHECK: (func $test (type $1) (result i32) + ;; CHECK-NEXT: (ref.test (ref (exact $func)) + ;; CHECK-NEXT: (ref.func $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") (result i32) + (ref.test (ref (exact $func)) + (ref.func $f) + ) + ) +) + +;; As above, but now the cast is to a subtype. We should not be able to optimize +;; this either. +(module + ;; CHECK: (type $func (sub (func))) + (type $func (sub (func))) + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (type $sub (sub $func (func))) + (type $sub (sub $func (func))) + ;; CHECK: (import "" "" (func $f (type $func))) + (import "" "" (func $f (type $func))) + ;; CHECK: (elem declare func $f) + + ;; CHECK: (export "test" (func $test)) + + ;; CHECK: (func $test (type $1) (result i32) + ;; CHECK-NEXT: (ref.test (ref $sub) + ;; CHECK-NEXT: (ref.func $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") (result i32) + (ref.test (ref $sub) + (ref.func $f) + ) + ) +) + +;; This is another exact cast, but now the imported type is final. TODO: Use +;; finality in this pass, as we could optimize here. +(module + ;; CHECK: (type $func (func)) + (type $func (sub final (func))) + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (import "" "" (func $f (type $func))) + (import "" "" (func $f (type $func))) + ;; CHECK: (elem declare func $f) + + ;; CHECK: (export "test" (func $test)) + + ;; CHECK: (func $test (type $1) (result i32) + ;; CHECK-NEXT: (ref.test (ref (exact $func)) + ;; CHECK-NEXT: (ref.func $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") (result i32) + (ref.test (ref (exact $func)) + (ref.func $f) + ) + ) +) + +;; Now we use a ref.cast instead of a ref.test with the exact cast to the final +;; type. We cannot optimize even though we know the cast will succeed because +;; the Wasm type of the function reference is inexact. +(module + ;; CHECK: (type $func (func)) + (type $func (sub final (func))) + ;; CHECK: (type $1 (func (result funcref))) + + ;; CHECK: (import "" "" (func $f (type $func))) + (import "" "" (func $f (type $func))) + ;; CHECK: (elem declare func $f) + + ;; CHECK: (export "test" (func $test)) + + ;; CHECK: (func $test (type $1) (result funcref) + ;; CHECK-NEXT: (ref.cast (ref (exact $func)) + ;; CHECK-NEXT: (ref.func $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (export "test") (result funcref) + (ref.cast (ref (exact $func)) + (ref.func $f) + ) + ) +) From f40d2b128d13bfafff2f24811fc182edadbdbae7 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:35:57 -0800 Subject: [PATCH 05/10] work --- src/ir/possible-contents.cpp | 20 ++++---- test/lit/passes/gufa.wast | 98 +++++------------------------------- 2 files changed, 23 insertions(+), 95 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 5b5a4eff34c..b0b32c10d50 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -641,9 +641,17 @@ struct InfoCollector addRoot(curr); } void visitRefFunc(RefFunc* curr) { - addRoot(curr, - PossibleContents::literal( - Literal::makeFunc(curr->func, curr->type.getHeapType()))); + if (!getModule()->getFunction(curr->func)->imported()) { + // This is not imported, so we know the exact function literal. + addRoot(curr, + PossibleContents::literal( + Literal::makeFunc(curr->func, curr->type.getHeapType()))); + } else { + // This is imported, so it is effectively a global. + addRoot(curr, + PossibleContents::global(curr->func, ExternalKind::Function, curr->type) + ); + } // The presence of a RefFunc indicates the function may be called // indirectly, so add the relevant connections for this particular function. @@ -2826,12 +2834,6 @@ void Flower::filterExpressionContents(PossibleContents& contents, void Flower::filterGlobalContents(PossibleContents& contents, const GlobalLocation& globalLoc) { - // Function imports are always immutable. - auto kind = contents.getGlobal().kind; - if (kind == ExternalKind::Function) { - return; - } - assert(kind == ExternalKind::Global); auto* global = wasm.getGlobal(globalLoc.name); if (global->mutable_ == Immutable) { // This is an immutable global. We never need to consider this value as diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index 49676b493d3..ffb256c2702 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1155,106 +1155,32 @@ ) ) -;; We cannot know the types of imported functions, so we should not be able to -;; optimize this exact cast. +;; Imported functions can be inferred. (module - ;; CHECK: (type $func (sub (func))) - (type $func (sub (func))) - (type $sub (sub $func (func))) - ;; CHECK: (type $1 (func (result i32))) - - ;; CHECK: (import "" "" (func $f (type $func))) - (import "" "" (func $f (type $func))) - ;; CHECK: (elem declare func $f) - - ;; CHECK: (export "test" (func $test)) - - ;; CHECK: (func $test (type $1) (result i32) - ;; CHECK-NEXT: (ref.test (ref (exact $func)) - ;; CHECK-NEXT: (ref.func $f) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (export "test") (result i32) - (ref.test (ref (exact $func)) - (ref.func $f) - ) - ) -) - -;; As above, but now the cast is to a subtype. We should not be able to optimize -;; this either. -(module - ;; CHECK: (type $func (sub (func))) - (type $func (sub (func))) - ;; CHECK: (type $1 (func (result i32))) - - ;; CHECK: (type $sub (sub $func (func))) - (type $sub (sub $func (func))) - ;; CHECK: (import "" "" (func $f (type $func))) - (import "" "" (func $f (type $func))) - ;; CHECK: (elem declare func $f) - - ;; CHECK: (export "test" (func $test)) - - ;; CHECK: (func $test (type $1) (result i32) - ;; CHECK-NEXT: (ref.test (ref $sub) - ;; CHECK-NEXT: (ref.func $f) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (export "test") (result i32) - (ref.test (ref $sub) - (ref.func $f) - ) - ) -) - -;; This is another exact cast, but now the imported type is final. TODO: Use -;; finality in this pass, as we could optimize here. -(module - ;; CHECK: (type $func (func)) - (type $func (sub final (func))) - ;; CHECK: (type $1 (func (result i32))) - - ;; CHECK: (import "" "" (func $f (type $func))) - (import "" "" (func $f (type $func))) - ;; CHECK: (elem declare func $f) - - ;; CHECK: (export "test" (func $test)) - - ;; CHECK: (func $test (type $1) (result i32) - ;; CHECK-NEXT: (ref.test (ref (exact $func)) - ;; CHECK-NEXT: (ref.func $f) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - (func $test (export "test") (result i32) - (ref.test (ref (exact $func)) - (ref.func $f) - ) - ) -) + ;; CHECK: (type $0 (func)) -;; Now we use a ref.cast instead of a ref.test with the exact cast to the final -;; type. We cannot optimize even though we know the cast will succeed because -;; the Wasm type of the function reference is inexact. -(module - ;; CHECK: (type $func (func)) - (type $func (sub final (func))) ;; CHECK: (type $1 (func (result funcref))) - ;; CHECK: (import "" "" (func $f (type $func))) - (import "" "" (func $f (type $func))) + ;; CHECK: (import "" "" (func $f (type $0))) + (import "" "" (func $f)) + ;; CHECK: (elem declare func $f) ;; CHECK: (export "test" (func $test)) ;; CHECK: (func $test (type $1) (result funcref) - ;; CHECK-NEXT: (ref.cast (ref (exact $func)) + ;; CHECK-NEXT: (local $temp funcref) + ;; CHECK-NEXT: (local.set $temp ;; CHECK-NEXT: (ref.func $f) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.func $f) ;; CHECK-NEXT: ) (func $test (export "test") (result funcref) - (ref.cast (ref (exact $func)) + (local $temp funcref) + (local.set $temp (ref.func $f) ) + (local.get $temp) ) ) + From 32939a17dad86ead6bc70c604d8e025795542df1 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:37:27 -0800 Subject: [PATCH 06/10] comment --- test/lit/passes/gufa.wast | 1 + 1 file changed, 1 insertion(+) diff --git a/test/lit/passes/gufa.wast b/test/lit/passes/gufa.wast index ffb256c2702..a7cbf9ed38d 100644 --- a/test/lit/passes/gufa.wast +++ b/test/lit/passes/gufa.wast @@ -1180,6 +1180,7 @@ (local.set $temp (ref.func $f) ) + ;; This will become a ref.func $f. (local.get $temp) ) ) From b29bee080e063534655b71dd54d4d95c252f8d72 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:58:22 -0800 Subject: [PATCH 07/10] test --- test/gtest/possible-contents.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index 9c81f26efb8..e5875f947d1 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -89,6 +89,11 @@ class PossibleContentsTest : public testing::Test { PossibleContents nonNullFuncGlobal = PossibleContents::global("funcGlobal", ExternalKind::Global, Type(HeapType::func, NonNullable)); + PossibleContents importedFunc1 = + PossibleContents::global("impfunc1", ExternalKind::Function, Type(HeapType::func, NonNullable)); + PossibleContents importedFunc2 = + PossibleContents::global("impfunc2", ExternalKind::Function, Type(HeapType::func, NonNullable)); + PossibleContents nonNullFunc = PossibleContents::literal( Literal::makeFunc("func", Signature(Type::none, Type::none))); @@ -114,6 +119,7 @@ class PossibleContentsTest : public testing::Test { PossibleContents coneAnyref = PossibleContents::coneType(anyref); PossibleContents coneFuncref = PossibleContents::coneType(funcref); PossibleContents coneFuncref1 = PossibleContents::coneType(funcref, 1); + PossibleContents coneNonNullFuncref = PossibleContents::coneType(Type(HeapType::func, NonNullable)); }; TEST_F(PossibleContentsTest, TestComparisons) { @@ -135,6 +141,9 @@ TEST_F(PossibleContentsTest, TestComparisons) { assertNotEqualSymmetric(i32Global1, exactI32); assertNotEqualSymmetric(i32Global1, many); + assertEqualSymmetric(importedFunc1, importedFunc1); + assertNotEqualSymmetric(importedFunc1, importedFunc2); + assertEqualSymmetric(exactI32, exactI32); assertNotEqualSymmetric(exactI32, exactAnyref); assertNotEqualSymmetric(exactI32, many); @@ -257,6 +266,10 @@ TEST_F(PossibleContentsTest, TestCombinations) { assertCombination(anyGlobal, anyNull, coneAnyref); assertCombination(anyGlobal, i31Null, coneAnyref); + + // Imported functions. + assertCombination(importedFunc1, importedFunc1, importedFunc1); + assertCombination(importedFunc1, importedFunc2, coneNonNullFuncref); } static PassOptions options; @@ -338,6 +351,13 @@ TEST_F(PossibleContentsTest, TestIntersection) { // Separate hierarchies. assertLackIntersection(funcGlobal, anyGlobal); + + // Imported functions. + assertHaveIntersection(importedFunc1, importedFunc1); + assertHaveIntersection(importedFunc1, exactFuncSignatureType); + assertHaveIntersection(importedFunc1, exactNonNullFuncSignatureType); + assertHaveIntersection(importedFunc1, importedFunc2); + assertHaveIntersection(importedFunc1, funcGlobal); } TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { @@ -484,6 +504,8 @@ TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { funcGlobal, nonNullFuncGlobal, nonNullFunc, + importedFunc1, + importedFunc2, exactI32, exactAnyref, exactFuncref, @@ -814,6 +836,13 @@ TEST_F(PossibleContentsTest, TestStructCones) { assertIntersection(funcGlobal, none, none); assertIntersection(PossibleContents::coneType(signature), none, none); + // Imported functions. TODO: These are not yet supported, and assert instead. + // assertIntersection( + // importedFunc1, importedFunc1, importedFunc1); + // assertIntersection( + // importedFunc1, PossibleContents::coneType(nonNullFuncRef), + // importedFunc1); + // Subcontents. This API only supports the case where one of the inputs is a // full cone type. // First, compare exact types to such a cone. From 1e2feacaa0136cd3d1285d765f6ff6b967561a5f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 08:58:50 -0800 Subject: [PATCH 08/10] format --- src/ir/possible-contents.cpp | 7 ++++--- src/ir/possible-contents.h | 9 ++++++--- test/gtest/possible-contents.cpp | 33 ++++++++++++++++++-------------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index b0b32c10d50..a0ef03c197d 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -649,8 +649,8 @@ struct InfoCollector } else { // This is imported, so it is effectively a global. addRoot(curr, - PossibleContents::global(curr->func, ExternalKind::Function, curr->type) - ); + PossibleContents::global( + curr->func, ExternalKind::Function, curr->type)); } // The presence of a RefFunc indicates the function may be called @@ -2844,7 +2844,8 @@ void Flower::filterGlobalContents(PossibleContents& contents, // a cone/exact type *and* that something is equal to a global, in some // cases. See https://github.com/WebAssembly/binaryen/pull/5083 if (contents.isMany() || contents.isConeType()) { - contents = PossibleContents::global(global->name, ExternalKind::Global, global->type); + contents = PossibleContents::global( + global->name, ExternalKind::Global, global->type); // TODO: We could do better here, to set global->init->type instead of // global->type, or even the contents.getType() - either of those diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 1fa9ec1889a..5a964a4035f 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -322,10 +322,12 @@ class PossibleContents { // in the GlobalInfo, as that type may not match the global (see comment // in the GlobalInfo declaration above). if (info.kind == ExternalKind::Global) { - return builder.makeGlobalGet(info.name, wasm.getGlobal(info.name)->type); + return builder.makeGlobalGet(info.name, + wasm.getGlobal(info.name)->type); } else { assert(info.kind == ExternalKind::Function); - return builder.makeRefFunc(info.name, wasm.getFunction(info.name)->type.getHeapType()); + return builder.makeRefFunc( + info.name, wasm.getFunction(info.name)->type.getHeapType()); } } } @@ -382,7 +384,8 @@ class PossibleContents { } } else if (isGlobal()) { auto info = getGlobal(); - o << "GlobalInfo $" << info.name << " K: " << int(info.kind) << " T: " << getType(); + o << "GlobalInfo $" << info.name << " K: " << int(info.kind) + << " T: " << getType(); } else if (auto* coneType = std::get_if(&value)) { auto t = coneType->type; o << "ConeType " << t; diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index e5875f947d1..ca1f4313a27 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -83,16 +83,19 @@ class PossibleContentsTest : public testing::Test { PossibleContents::global("i32Global1", ExternalKind::Global, Type::i32); PossibleContents i32Global2 = PossibleContents::global("i32Global2", ExternalKind::Global, Type::i32); - PossibleContents f64Global = PossibleContents::global("f64Global", ExternalKind::Global, Type::f64); - PossibleContents anyGlobal = PossibleContents::global("anyGlobal", ExternalKind::Global, anyref); - PossibleContents funcGlobal = PossibleContents::global("funcGlobal", ExternalKind::Global, funcref); - PossibleContents nonNullFuncGlobal = - PossibleContents::global("funcGlobal", ExternalKind::Global, Type(HeapType::func, NonNullable)); - - PossibleContents importedFunc1 = - PossibleContents::global("impfunc1", ExternalKind::Function, Type(HeapType::func, NonNullable)); - PossibleContents importedFunc2 = - PossibleContents::global("impfunc2", ExternalKind::Function, Type(HeapType::func, NonNullable)); + PossibleContents f64Global = + PossibleContents::global("f64Global", ExternalKind::Global, Type::f64); + PossibleContents anyGlobal = + PossibleContents::global("anyGlobal", ExternalKind::Global, anyref); + PossibleContents funcGlobal = + PossibleContents::global("funcGlobal", ExternalKind::Global, funcref); + PossibleContents nonNullFuncGlobal = PossibleContents::global( + "funcGlobal", ExternalKind::Global, Type(HeapType::func, NonNullable)); + + PossibleContents importedFunc1 = PossibleContents::global( + "impfunc1", ExternalKind::Function, Type(HeapType::func, NonNullable)); + PossibleContents importedFunc2 = PossibleContents::global( + "impfunc2", ExternalKind::Function, Type(HeapType::func, NonNullable)); PossibleContents nonNullFunc = PossibleContents::literal( Literal::makeFunc("func", Signature(Type::none, Type::none))); @@ -119,7 +122,8 @@ class PossibleContentsTest : public testing::Test { PossibleContents coneAnyref = PossibleContents::coneType(anyref); PossibleContents coneFuncref = PossibleContents::coneType(funcref); PossibleContents coneFuncref1 = PossibleContents::coneType(funcref, 1); - PossibleContents coneNonNullFuncref = PossibleContents::coneType(Type(HeapType::func, NonNullable)); + PossibleContents coneNonNullFuncref = + PossibleContents::coneType(Type(HeapType::func, NonNullable)); }; TEST_F(PossibleContentsTest, TestComparisons) { @@ -807,9 +811,10 @@ TEST_F(PossibleContentsTest, TestStructCones) { nonNullFunc, PossibleContents::coneType(signature), nonNullFunc); // Filter a global to a more specific type. - assertIntersection(funcGlobal, - PossibleContents::coneType(signature), - PossibleContents::global("funcGlobal", ExternalKind::Global, signature)); + assertIntersection( + funcGlobal, + PossibleContents::coneType(signature), + PossibleContents::global("funcGlobal", ExternalKind::Global, signature)); // Filter a global's nullability only. auto nonNullFuncRef = Type(HeapType::func, NonNullable); From a53f7625b1c327d0c535bf26c2662b3825c06df6 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 09:08:32 -0800 Subject: [PATCH 09/10] typo --- src/ir/possible-contents.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ir/possible-contents.h b/src/ir/possible-contents.h index 5a964a4035f..6e0f110adb8 100644 --- a/src/ir/possible-contents.h +++ b/src/ir/possible-contents.h @@ -41,9 +41,10 @@ namespace wasm { // * Literal: One possible constant value like an i32 of 42. // // * Global: An immutable global value, something who we can identify -// but do not know the concree value of at runtime. This can -// be either a literal (immutable) global, or an imported -// function (which is effectively the same). +// but do not know the actual value of at runtime. This can +// be either a wasm (immutable) Global, or an imported wasm +// Function (which is effectively the same: we can refer to +// it, but do not know what is being imported there). // // * ConeType: Any possible value of a particular type, and a possible // "cone" of a certain depth below it. If the depth is 0 From 1f3d700dccd81bf1f26fdf3d51d6716d0a79c015 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 6 Nov 2025 11:11:52 -0800 Subject: [PATCH 10/10] Add a test for a global and function with identical name --- test/gtest/possible-contents.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index ca1f4313a27..4a42c0098a5 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -164,6 +164,23 @@ TEST_F(PossibleContentsTest, TestComparisons) { assertNotEqualSymmetric(exactNonNullAnyref, exactAnyref); } +TEST_F(PossibleContentsTest, TestComparisonsGlobals) { + // Check if two PossibleContents::global, one a wasm Global and one a wasm + // Function, and equal in their names and types, are still understood to be + // non-equal: the |kind| field differentiates them. + + PossibleContents wasmGlobal = PossibleContents::global( + "foo", ExternalKind::Global, Type(HeapType::func, NonNullable)); + PossibleContents wasmFunction = PossibleContents::global( + "foo", ExternalKind::Function, Type(HeapType::func, NonNullable)); + + assertNotEqualSymmetric(wasmGlobal, wasmFunction); + + // But they are equal to themselves, of course. + assertEqualSymmetric(wasmGlobal, wasmGlobal); + assertEqualSymmetric(wasmFunction, wasmFunction); +} + TEST_F(PossibleContentsTest, TestHash) { // Hashes should be deterministic. EXPECT_EQ(none.hash(), none.hash());