diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index a333f88b79a..a77ff4e995b 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -445,8 +445,19 @@ struct SubtypingDiscoverer : public OverriddenVisitor { self()->noteSubtype(curr->replacement, type); } void visitRefAs(RefAs* curr) { - if (curr->op == RefAsNonNull) { - self()->noteCast(curr->value, curr); + switch (curr->op) { + case RefAsNonNull: + self()->noteCast(curr->value, curr); + return; + case AnyConvertExtern: + return; + case ExternConvertAny: + if (curr->type != Type::unreachable) { + auto any = + HeapTypes::any.getBasic(curr->type.getHeapType().getShared()); + self()->noteSubtype(curr->value, Type(any, Nullable)); + } + return; } } void visitStringNew(StringNew* curr) {} diff --git a/test/lit/passes/unsubtyping.wast b/test/lit/passes/unsubtyping.wast index cf80ac48d85..ec277643dcc 100644 --- a/test/lit/passes/unsubtyping.wast +++ b/test/lit/passes/unsubtyping.wast @@ -1920,3 +1920,85 @@ ) ) ) + +;; extern.convert_any requires its input to remain a subtype of any. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (struct))) + (type $A (sub (struct))) + ;; CHECK: (type $B (sub $A (struct))) + (type $B (sub $A (struct))) + + ;; CHECK: (type $2 (func (result externref))) + + ;; CHECK: (type $3 (func (param anyref))) + + ;; CHECK: (func $test (type $2) (result externref) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result externref) + (extern.convert_any + (struct.new $B) + ) + ) + + ;; CHECK: (func $cast (type $3) (param $0 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref null $A) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast (param anyref) + (drop + ;; Since $B flows into any, it must remain a subtype of $A to continue + ;; passing this cast. + (ref.cast (ref null $A) + (local.get 0) + ) + ) + ) +) + +;; Same as above with shared types. +(module + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (shared (struct)))) + (type $A (sub (shared (struct)))) + ;; CHECK: (type $B (sub $A (shared (struct)))) + (type $B (sub $A (shared (struct)))) + + ;; CHECK: (type $2 (func (result (ref null (shared extern))))) + + ;; CHECK: (type $3 (func (param (ref null (shared any))))) + + ;; CHECK: (func $test (type $2) (result (ref null (shared extern))) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default $B) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result (ref null (shared extern))) + (extern.convert_any + (struct.new $B) + ) + ) + + ;; CHECK: (func $cast (type $3) (param $0 (ref null (shared any))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref null $A) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast (param (ref null (shared any))) + (drop + ;; Since $B flows into any, it must remain a subtype of $A to continue + ;; passing this cast. + (ref.cast (ref null $A) + (local.get 0) + ) + ) + ) +)