diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index bc99156e0fb..05948e9a5e0 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -19,6 +19,7 @@ #include "ir/branch-utils.h" #include "wasm-traversal.h" +#include "wasm-type.h" #include "wasm.h" namespace wasm { @@ -52,8 +53,9 @@ namespace wasm { // subtype of anothers, for example, // a block and its last child. // -// * noteCast(HeapType, HeapType) - A fixed type is cast to another, for -// example, in a CallIndirect. +// * noteCast(HeapType, Type) - A fixed type is cast to another, for example, +// in a CallIndirect. The destination is a Type +// rather than HeapType because it may be exact. // * noteCast(Expression, Type) - An expression's type is cast to a fixed type, // for example, in RefTest. // * noteCast(Expression, Expression) - An expression's type is cast to @@ -165,7 +167,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { // this is a trivial situation that is not worth optimizing. self()->noteSubtype(tableType, curr->heapType); } else if (HeapType::isSubType(curr->heapType, tableType)) { - self()->noteCast(tableType, curr->heapType); + self()->noteCast(tableType, Type(curr->heapType, NonNullable, Inexact)); } else { // The types are unrelated and the cast will fail. We can keep the types // unrelated. diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index 2e45cdba316..ad371d19a28 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -599,13 +599,21 @@ struct Unsubtyping : Pass { // Otherwise, we must take this into account. noteSubtype(sub, super); } - void noteCast(HeapType src, HeapType dst) { + void noteCast(HeapType src, Type dstType) { + auto dst = dstType.getHeapType(); // Casts to self and casts that must fail because they have incompatible // types are uninteresting. if (dst == src) { return; } if (HeapType::isSubType(dst, src)) { + if (dstType.isExact()) { + // This cast only tests that the exact destination type is a subtype + // of the source type and does not impose additional requirements on + // subtypes of the destination type like a normal cast does. + info.subtypings.insert({dst, src}); + return; + } info.casts.insert({src, dst}); return; } @@ -617,12 +625,12 @@ struct Unsubtyping : Pass { } void noteCast(Expression* src, Type dst) { if (src->type.isRef() && dst.isRef()) { - noteCast(src->type.getHeapType(), dst.getHeapType()); + noteCast(src->type.getHeapType(), dst); } } void noteCast(Expression* src, Expression* dst) { if (src->type.isRef() && dst->type.isRef()) { - noteCast(src->type.getHeapType(), dst->type.getHeapType()); + noteCast(src->type.getHeapType(), dst->type); } } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index b7741f04c94..d77c0ac44b0 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1784,7 +1784,7 @@ void TranslateToFuzzReader::mutate(Function* func) { // TODO: Many casts can accept the top type. We may need to use visit*(), to // handle each expression class separately. - void noteCast(HeapType src, HeapType dst) {} + void noteCast(HeapType src, Type dst) {} void noteCast(Expression* src, Type dst) {} void noteCast(Expression* src, Expression* dst) {} } finder; diff --git a/test/lit/passes/unsubtyping-casts.wast b/test/lit/passes/unsubtyping-casts.wast index d27629c383d..9645d4265b2 100644 --- a/test/lit/passes/unsubtyping-casts.wast +++ b/test/lit/passes/unsubtyping-casts.wast @@ -563,3 +563,104 @@ ) ) ) + +;; Exact casts do not impose requirements on subtypes of the destination type. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $top (sub (struct))) + (type $top (sub (struct))) + ;; CHECK: (type $bot (sub (struct))) + (type $bot (sub $top (struct))) + ) + + ;; CHECK: (type $2 (func (param anyref))) + + ;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot)) + (global $bot-sub-any anyref (struct.new $bot)) + + ;; CHECK: (func $ref.cast-exact (type $2) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (ref null (exact $top)) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref.cast-exact (param $any anyref) + (drop + (ref.cast (ref null (exact $top)) + (local.get $any) + ) + ) + ) +) + +;; Same, but now with a br_on_cast. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $top (sub (struct))) + (type $top (sub (struct))) + ;; CHECK: (type $bot (sub (struct))) + (type $bot (sub $top (struct))) + ) + + ;; CHECK: (type $2 (func (param anyref))) + + ;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot)) + (global $bot-sub-any anyref (struct.new $bot)) + + ;; CHECK: (func $br_on_cast-exact (type $2) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (br_on_cast $l anyref (ref (exact $top)) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast-exact (param $any anyref) + (drop + (block $l (result anyref) + (br_on_cast $l anyref (ref (exact $top)) + (local.get $any) + ) + ) + ) + ) +) + +;; Same, but now with a br_on_cast_fail. +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $top (sub (struct))) + (type $top (sub (struct))) + ;; CHECK: (type $bot (sub (struct))) + (type $bot (sub $top (struct))) + ) + + ;; CHECK: (type $2 (func (param anyref))) + + ;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot)) + (global $bot-sub-any anyref (struct.new $bot)) + + ;; CHECK: (func $br_on_cast_fail-exact (type $2) (param $any anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l (result anyref) + ;; CHECK-NEXT: (br_on_cast_fail $l anyref (ref (exact $top)) + ;; CHECK-NEXT: (local.get $any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_fail-exact (param $any anyref) + (drop + (block $l (result anyref) + (br_on_cast_fail $l anyref (ref (exact $top)) + (local.get $any) + ) + ) + ) + ) +) diff --git a/test/lit/passes/unsubtyping-desc.wast b/test/lit/passes/unsubtyping-desc.wast index 5a76a1a4f65..bf0af50c0f2 100644 --- a/test/lit/passes/unsubtyping-desc.wast +++ b/test/lit/passes/unsubtyping-desc.wast @@ -427,28 +427,25 @@ ;; If the ref.cast_desc is exact, then it doesn't need to transitively require ;; any subtypings except that the cast destination is a subtype of the cast -;; source. TODO. +;; source. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct)))) (type $top (sub (descriptor $top.desc (struct)))) - ;; CHECK: (type $bot (sub $top (descriptor $bot.desc (struct)))) + ;; CHECK: (type $bot (sub (struct))) (type $bot (sub $top (descriptor $bot.desc (struct)))) ;; CHECK: (type $top.desc (sub (describes $top (struct)))) (type $top.desc (sub (describes $top (struct)))) - ;; CHECK: (type $bot.desc (sub $top.desc (describes $bot (struct)))) (type $bot.desc (sub $top.desc (describes $bot (struct)))) ) - ;; CHECK: (type $4 (func (param anyref (ref (exact $top.desc))))) + ;; CHECK: (type $3 (func (param anyref (ref (exact $top.desc))))) - ;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot - ;; CHECK-NEXT: (struct.new_default $bot.desc) - ;; CHECK-NEXT: )) + ;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot)) (global $bot-sub-any anyref (struct.new $bot (struct.new $bot.desc))) - ;; CHECK: (func $ref.cast_desc (type $4) (param $any anyref) (param $top.desc (ref (exact $top.desc))) + ;; CHECK: (func $ref.cast_desc (type $3) (param $any anyref) (param $top.desc (ref (exact $top.desc))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast_desc (ref null (exact $top)) ;; CHECK-NEXT: (local.get $any) @@ -513,28 +510,25 @@ ;; If the br_on_cast_desc is exact, then it doesn't need to transitively require ;; any subtypings except that the cast destination is a subtype of the cast -;; source. TODO. +;; source. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct)))) (type $top (sub (descriptor $top.desc (struct)))) - ;; CHECK: (type $bot (sub $top (descriptor $bot.desc (struct)))) + ;; CHECK: (type $bot (sub (struct))) (type $bot (sub $top (descriptor $bot.desc (struct)))) ;; CHECK: (type $top.desc (sub (describes $top (struct)))) (type $top.desc (sub (describes $top (struct)))) - ;; CHECK: (type $bot.desc (sub $top.desc (describes $bot (struct)))) (type $bot.desc (sub $top.desc (describes $bot (struct)))) ) - ;; CHECK: (type $4 (func (param anyref (ref (exact $top.desc))))) + ;; CHECK: (type $3 (func (param anyref (ref (exact $top.desc))))) - ;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot - ;; CHECK-NEXT: (struct.new_default $bot.desc) - ;; CHECK-NEXT: )) + ;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot)) (global $bot-sub-any anyref (struct.new $bot (struct.new $bot.desc))) - ;; CHECK: (func $br_on_cast_desc (type $4) (param $any anyref) (param $top.desc (ref (exact $top.desc))) + ;; CHECK: (func $br_on_cast_desc (type $3) (param $any anyref) (param $top.desc (ref (exact $top.desc))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $l (result anyref) ;; CHECK-NEXT: (br_on_cast_desc $l anyref (ref null (exact $top)) @@ -603,28 +597,25 @@ ;; If the br_on_cast_desc_fail is exact, then it doesn't need to transitively ;; require any subtypings except that the cast destination is a subtype of the -;; cast source. TODO. +;; cast source. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct)))) (type $top (sub (descriptor $top.desc (struct)))) - ;; CHECK: (type $bot (sub $top (descriptor $bot.desc (struct)))) + ;; CHECK: (type $bot (sub (struct))) (type $bot (sub $top (descriptor $bot.desc (struct)))) ;; CHECK: (type $top.desc (sub (describes $top (struct)))) (type $top.desc (sub (describes $top (struct)))) - ;; CHECK: (type $bot.desc (sub $top.desc (describes $bot (struct)))) (type $bot.desc (sub $top.desc (describes $bot (struct)))) ) - ;; CHECK: (type $4 (func (param anyref (ref (exact $top.desc))))) + ;; CHECK: (type $3 (func (param anyref (ref (exact $top.desc))))) - ;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot - ;; CHECK-NEXT: (struct.new_default $bot.desc) - ;; CHECK-NEXT: )) + ;; CHECK: (global $bot-sub-any anyref (struct.new_default $bot)) (global $bot-sub-any anyref (struct.new $bot (struct.new $bot.desc))) - ;; CHECK: (func $br_on_cast_desc_fail (type $4) (param $any anyref) (param $top.desc (ref (exact $top.desc))) + ;; CHECK: (func $br_on_cast_desc_fail (type $3) (param $any anyref) (param $top.desc (ref (exact $top.desc))) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $l (result anyref) ;; CHECK-NEXT: (br_on_cast_desc_fail $l anyref (ref null (exact $top))