From 1f47cbffe542111abf63ce0443491a70f839e859 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 9 Oct 2025 17:38:08 -0700 Subject: [PATCH 1/3] start --- src/passes/AbstractTypeRefining.cpp | 24 +++- .../passes/abstract-type-refining-desc.wast | 103 ++++++++++++++++++ 2 files changed, 121 insertions(+), 6 deletions(-) diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index 30f3a98641d..b1728b92bec 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -395,19 +395,31 @@ struct AbstractTypeRefining : public Pass { } // If we optimize away a descriptor type, then we must fix up any - // ref.get_desc of it, as ReFinalize would fix us up to return it. This - // can only happen when no such descriptor is created, which means the - // instruction will never be reached (no struct with such a descriptor was - // created). + // ref.get_desc of it, as ReFinalize would fix us up to return it. void visitRefGetDesc(RefGetDesc* curr) { auto optimized = getOptimized(curr->type); if (!optimized) { return; } + // We are optimizing the descriptor to a subtype. + auto subDescriptor = *optimized; + Builder builder(*getModule()); - replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), - builder.makeUnreachable())); + if (curr->type.isExact() || subDescriptor == HeapTypes::none) { + // This is exact, so we can ignore subtypes, or there is no subtype to + // optimize to. In this case it must trap. + replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref), + builder.makeUnreachable())); + } else { + // The descriptor is abstract, but it has a subtype that is not + // (which we want to optimize to). We know this will only succeed if + // we receive the subtype of the described type, so that that + // descriptor is fetched. Add a cast so that we validate. + auto subDescribed = subDescriptor.getDescribedType(); + assert(subDescribed); + curr->ref = builder.makeRefCast(curr->ref, curr->ref->type.with(*subDescribed)); + } } void visitBrOn(BrOn* curr) { diff --git a/test/lit/passes/abstract-type-refining-desc.wast b/test/lit/passes/abstract-type-refining-desc.wast index 047f6f293c9..f674faef577 100644 --- a/test/lit/passes/abstract-type-refining-desc.wast +++ b/test/lit/passes/abstract-type-refining-desc.wast @@ -1038,6 +1038,86 @@ ;; YESTNH: (type $2 (func (result (ref none)))) + ;; YESTNH: (type $3 (func (param (ref $A)) (result (ref none)))) + + ;; YESTNH: (func $test (type $2) (result (ref none)) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (struct.new_default $A + ;; YESTNH-NEXT: (ref.null none) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (type $2 (func (result (ref none)))) + + ;; NO_TNH: (type $3 (func (param (ref $A)) (result (ref none)))) + + ;; NO_TNH: (func $test (type $2) (result (ref none)) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (struct.new_default $A + ;; NO_TNH-NEXT: (ref.null none) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + (func $test (result (ref (exact $B))) + (ref.get_desc $A + (struct.new_default $A + (ref.null none) + ) + ) + ) + + ;; YESTNH: (func $inexact (type $3) (param $inexact (ref $A)) (result (ref none)) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (local.get $inexact) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $inexact (type $3) (param $inexact (ref $A)) (result (ref none)) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (local.get $inexact) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + (func $inexact (param $inexact (ref $A)) (result (ref $B)) + ;; As above, but now the input type is inexact. There are no subtypes, so + ;; we still optimize. + (ref.get_desc $A + (local.get $inexact) + ) + ) +) + +(module + ;; As above, but now there are subtypes $A.sub, $B.sub. + (rec + ;; YESTNH: (rec + ;; YESTNH-NEXT: (type $A (sub (descriptor $B (struct)))) + ;; NO_TNH: (rec + ;; NO_TNH-NEXT: (type $A (sub (descriptor $B (struct)))) + (type $A (sub (descriptor $B (struct)))) + ;; YESTNH: (type $B (describes $A (struct))) + ;; NO_TNH: (type $B (describes $A (struct))) + (type $B (sub (describes $A (struct)))) + + (type $A.sub (sub $A (descriptor $B.sub (struct)))) + (type $B.sub (sub $B (describes $A.sub (struct)))) + ) + + (func $create + ;; Make the subtypes not abstract. + (drop + (struct.new_default $A.sub + (struct.new_default $B.sub) + ) + ) + ) + + ;; YESTNH: (type $2 (func (result (ref none)))) + + ;; YESTNH: (type $3 (func (param (ref $A)) (result (ref none)))) + ;; YESTNH: (func $test (type $2) (result (ref none)) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (struct.new_default $A @@ -1048,6 +1128,8 @@ ;; YESTNH-NEXT: ) ;; NO_TNH: (type $2 (func (result (ref none)))) + ;; NO_TNH: (type $3 (func (param (ref $A)) (result (ref none)))) + ;; NO_TNH: (func $test (type $2) (result (ref none)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (struct.new_default $A @@ -1057,11 +1139,32 @@ ;; NO_TNH-NEXT: (unreachable) ;; NO_TNH-NEXT: ) (func $test (result (ref (exact $B))) + ;; We can still optimize here, thanks to exactness. (ref.get_desc $A (struct.new_default $A (ref.null none) ) ) ) + + ;; YESTNH: (func $inexact (type $3) (param $inexact (ref $A)) (result (ref none)) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (local.get $inexact) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: (unreachable) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (func $inexact (type $3) (param $inexact (ref $A)) (result (ref none)) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (local.get $inexact) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: (unreachable) + ;; NO_TNH-NEXT: ) + (func $inexact (param $inexact (ref $A)) (result (ref $B)) + ;; We do not optimize to an unreachable here, as the inexact reference may + ;; contain an $A.sub. We optimize to that. + (ref.get_desc $A + (local.get $inexact) + ) + ) ) From ca8ba0707ffd237bc32a8fb2f46af370b6b9546f Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 9 Oct 2025 17:38:13 -0700 Subject: [PATCH 2/3] go --- src/passes/AbstractTypeRefining.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index b1728b92bec..af095d07a38 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -418,7 +418,8 @@ struct AbstractTypeRefining : public Pass { // descriptor is fetched. Add a cast so that we validate. auto subDescribed = subDescriptor.getDescribedType(); assert(subDescribed); - curr->ref = builder.makeRefCast(curr->ref, curr->ref->type.with(*subDescribed)); + curr->ref = + builder.makeRefCast(curr->ref, curr->ref->type.with(*subDescribed)); } } From bcaf23218542925cfe856da3463aeed4e892f27d Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 9 Oct 2025 17:39:42 -0700 Subject: [PATCH 3/3] finish --- .../passes/abstract-type-refining-desc.wast | 67 ++++++++++++------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/test/lit/passes/abstract-type-refining-desc.wast b/test/lit/passes/abstract-type-refining-desc.wast index f674faef577..35358c6f1a6 100644 --- a/test/lit/passes/abstract-type-refining-desc.wast +++ b/test/lit/passes/abstract-type-refining-desc.wast @@ -1097,14 +1097,44 @@ ;; NO_TNH: (rec ;; NO_TNH-NEXT: (type $A (sub (descriptor $B (struct)))) (type $A (sub (descriptor $B (struct)))) - ;; YESTNH: (type $B (describes $A (struct))) - ;; NO_TNH: (type $B (describes $A (struct))) + ;; YESTNH: (type $B (sub (describes $A (struct)))) + ;; NO_TNH: (type $B (sub (describes $A (struct)))) (type $B (sub (describes $A (struct)))) + ;; YESTNH: (type $A.sub (sub $A (descriptor $B.sub (struct)))) + ;; NO_TNH: (type $A.sub (sub $A (descriptor $B.sub (struct)))) (type $A.sub (sub $A (descriptor $B.sub (struct)))) + ;; YESTNH: (type $B.sub (sub $B (describes $A.sub (struct)))) + ;; NO_TNH: (type $B.sub (sub $B (describes $A.sub (struct)))) (type $B.sub (sub $B (describes $A.sub (struct)))) ) + ;; YESTNH: (type $4 (func)) + + ;; YESTNH: (type $5 (func (result (ref (exact $B.sub))))) + + ;; YESTNH: (type $6 (func (param (ref $A)) (result (ref $B.sub)))) + + ;; YESTNH: (func $create (type $4) + ;; YESTNH-NEXT: (drop + ;; YESTNH-NEXT: (struct.new_default $A.sub + ;; YESTNH-NEXT: (struct.new_default $B.sub) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; YESTNH-NEXT: ) + ;; NO_TNH: (type $4 (func)) + + ;; NO_TNH: (type $5 (func (result (ref (exact $B))))) + + ;; NO_TNH: (type $6 (func (param (ref $A)) (result (ref $B)))) + + ;; NO_TNH: (func $create (type $4) + ;; NO_TNH-NEXT: (drop + ;; NO_TNH-NEXT: (struct.new_default $A.sub + ;; NO_TNH-NEXT: (struct.new_default $B.sub) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) + ;; NO_TNH-NEXT: ) (func $create ;; Make the subtypes not abstract. (drop @@ -1114,11 +1144,7 @@ ) ) - ;; YESTNH: (type $2 (func (result (ref none)))) - - ;; YESTNH: (type $3 (func (param (ref $A)) (result (ref none)))) - - ;; YESTNH: (func $test (type $2) (result (ref none)) + ;; YESTNH: (func $test (type $5) (result (ref (exact $B.sub))) ;; YESTNH-NEXT: (drop ;; YESTNH-NEXT: (struct.new_default $A ;; YESTNH-NEXT: (ref.null none) @@ -1126,20 +1152,15 @@ ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: (unreachable) ;; YESTNH-NEXT: ) - ;; NO_TNH: (type $2 (func (result (ref none)))) - - ;; NO_TNH: (type $3 (func (param (ref $A)) (result (ref none)))) - - ;; NO_TNH: (func $test (type $2) (result (ref none)) - ;; NO_TNH-NEXT: (drop + ;; NO_TNH: (func $test (type $5) (result (ref (exact $B))) + ;; NO_TNH-NEXT: (ref.get_desc $A ;; NO_TNH-NEXT: (struct.new_default $A ;; NO_TNH-NEXT: (ref.null none) ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: ) - ;; NO_TNH-NEXT: (unreachable) ;; NO_TNH-NEXT: ) (func $test (result (ref (exact $B))) - ;; We can still optimize here, thanks to exactness. + ;; We can still optimize here, thanks to exactness (when TNH). (ref.get_desc $A (struct.new_default $A (ref.null none) @@ -1147,21 +1168,21 @@ ) ) - ;; YESTNH: (func $inexact (type $3) (param $inexact (ref $A)) (result (ref none)) - ;; YESTNH-NEXT: (drop - ;; YESTNH-NEXT: (local.get $inexact) + ;; YESTNH: (func $inexact (type $6) (param $inexact (ref $A)) (result (ref $B.sub)) + ;; YESTNH-NEXT: (ref.get_desc $A.sub + ;; YESTNH-NEXT: (ref.cast (ref $A.sub) + ;; YESTNH-NEXT: (local.get $inexact) + ;; YESTNH-NEXT: ) ;; YESTNH-NEXT: ) - ;; YESTNH-NEXT: (unreachable) ;; YESTNH-NEXT: ) - ;; NO_TNH: (func $inexact (type $3) (param $inexact (ref $A)) (result (ref none)) - ;; NO_TNH-NEXT: (drop + ;; NO_TNH: (func $inexact (type $6) (param $inexact (ref $A)) (result (ref $B)) + ;; NO_TNH-NEXT: (ref.get_desc $A ;; NO_TNH-NEXT: (local.get $inexact) ;; NO_TNH-NEXT: ) - ;; NO_TNH-NEXT: (unreachable) ;; NO_TNH-NEXT: ) (func $inexact (param $inexact (ref $A)) (result (ref $B)) ;; We do not optimize to an unreachable here, as the inexact reference may - ;; contain an $A.sub. We optimize to that. + ;; contain an $A.sub. We optimize to that, using a cast (in TNH). (ref.get_desc $A (local.get $inexact) )