diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index 30f3a98641d..af095d07a38 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -395,19 +395,32 @@ 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..35358c6f1a6 100644 --- a/test/lit/passes/abstract-type-refining-desc.wast +++ b/test/lit/passes/abstract-type-refining-desc.wast @@ -1038,6 +1038,8 @@ ;; 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 +1050,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 @@ -1063,5 +1067,125 @@ ) ) ) + + ;; 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 (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 + (struct.new_default $A.sub + (struct.new_default $B.sub) + ) + ) + ) + + ;; YESTNH: (func $test (type $5) (result (ref (exact $B.sub))) + ;; 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: (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: ) + (func $test (result (ref (exact $B))) + ;; We can still optimize here, thanks to exactness (when TNH). + (ref.get_desc $A + (struct.new_default $A + (ref.null none) + ) + ) + ) + + ;; 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: ) + ;; 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: ) + (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, using a cast (in TNH). + (ref.get_desc $A + (local.get $inexact) + ) + ) )