;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-opt --closed-world --unsubtyping --remove-unused-types -all -S -o - | filecheck %s
(module
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (struct )))
(type $top (sub (struct)))
(type $mid (sub $top (struct)))
(type $bot (sub $mid (struct)))
;; CHECK: (type $1 (func))
;; CHECK: (func $cast-optimizable (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref none)
;; CHECK-NEXT: (struct.new_default $top)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cast-optimizable
(drop
;; Simply casting from $top to $mid does not require $mid <: $top, since we
;; haven't shown that it's possible for $mid to inhabit $top. We should
;; optimize this case.
(ref.cast (ref $mid)
(struct.new $top)
)
)
)
)
(module
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (struct )))
(type $top (sub (struct)))
;; CHECK: (type $mid (sub $top (struct )))
(type $mid (sub $top (struct)))
;; CHECK: (type $bot (sub $mid (struct )))
(type $bot (sub $mid (struct)))
;; CHECK: (type $3 (func))
;; CHECK: (func $cast (type $3)
;; CHECK-NEXT: (local $l (ref null $top))
;; CHECK-NEXT: (local.set $l
;; CHECK-NEXT: (struct.new_default $bot)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $mid)
;; CHECK-NEXT: (struct.new_default $top)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cast
(local $l (ref null $top))
(local.set $l
;; Require $bot <: $top.
(struct.new $bot)
)
(drop
;; Now the cast requires $mid <: $top so that a $bot value appearing in the
;; $top location would still pass the cast to $mid.
(ref.cast (ref $mid)
(struct.new $top)
)
)
)
)
(module
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (struct )))
(type $top (sub (struct)))
;; CHECK: (type $mid (sub $top (struct )))
(type $mid (sub $top (struct)))
;; CHECK: (type $bot (sub $mid (struct )))
(type $bot (sub $mid (struct)))
;; CHECK: (type $3 (func))
;; CHECK: (func $cast (type $3)
;; CHECK-NEXT: (local $l (ref null $top))
;; CHECK-NEXT: (local.set $l
;; CHECK-NEXT: (struct.new_default $bot)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.test (ref $mid)
;; CHECK-NEXT: (struct.new_default $top)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cast
(local $l (ref null $top))
(local.set $l
;; Require $bot <: $top.
(struct.new $bot)
)
(drop
;; Same as above, but with a ref.test.
(ref.test (ref $mid)
(struct.new $top)
)
)
)
)
(module
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (struct )))
(type $top (sub (struct)))
;; CHECK: (type $mid (sub $top (struct )))
(type $mid (sub $top (struct)))
;; CHECK: (type $bot (sub $mid (struct )))
(type $bot (sub $mid (struct)))
;; CHECK: (type $3 (func))
;; CHECK: (func $cast (type $3)
;; CHECK-NEXT: (local $l (ref null $top))
;; CHECK-NEXT: (local.set $l
;; CHECK-NEXT: (struct.new_default $bot)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block $l (result (ref $mid))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast $l (ref $top) (ref $mid)
;; CHECK-NEXT: (struct.new_default $top)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cast
(local $l (ref null $top))
(local.set $l
;; Require $bot <: $top.
(struct.new $bot)
)
(drop
(block $l (result (ref $mid))
;; Same as above, but with a br_on_cast.
(drop
(br_on_cast $l anyref (ref $mid)
(struct.new $top)
)
)
(unreachable)
)
)
)
)
(module
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (struct )))
(type $top (sub (struct)))
;; CHECK: (type $mid (sub $top (struct )))
(type $mid (sub $top (struct)))
;; CHECK: (type $bot (sub $mid (struct )))
(type $bot (sub $mid (struct)))
;; CHECK: (type $3 (func))
;; CHECK: (func $cast (type $3)
;; CHECK-NEXT: (local $l (ref null $top))
;; CHECK-NEXT: (local.set $l
;; CHECK-NEXT: (struct.new_default $bot)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block $l (result (ref $top))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br_on_cast_fail $l (ref $top) (ref $mid)
;; CHECK-NEXT: (struct.new_default $top)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cast
(local $l (ref null $top))
(local.set $l
;; Require $bot <: $top.
(struct.new $bot)
)
(drop
(block $l (result (ref $top))
;; Same as above, but with a br_on_cast_fail.
(drop
(br_on_cast_fail $l anyref (ref $mid)
(struct.new $top)
)
)
(unreachable)
)
)
)
)
(module
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (func)))
(type $top (sub (func)))
;; CHECK: (type $mid (sub $top (func)))
(type $mid (sub $top (func)))
;; CHECK: (type $bot (sub $mid (func)))
(type $bot (sub $mid (func)))
;; CHECK: (table $t 1 1 (ref null $top))
(table $t 1 1 (ref null $top))
;; CHECK: (elem declare func $cast)
;; CHECK: (func $cast (type $bot)
;; CHECK-NEXT: (local $l (ref null $top))
;; CHECK-NEXT: (local.set $l
;; CHECK-NEXT: (ref.func $cast)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_indirect $t (type $mid)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cast (type $bot)
(local $l (ref null $top))
(local.set $l
;; Require $bot <: $top.
(ref.func $cast)
)
;; Same as above, but with a call_indirect
(call_indirect $t (type $mid)
(i32.const 0)
)
)
)
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $unrelated (sub (func)))
(type $unrelated (sub (func)))
;; CHECK: (type $top (sub (func)))
(type $top (sub (func)))
;; CHECK: (type $bot (sub $top (func)))
(type $bot (sub $top (func)))
)
;; CHECK: (table $t 1 1 (ref null $bot))
(table $t 1 1 (ref null $bot))
;; CHECK: (func $call-indirect (type $bot)
;; CHECK-NEXT: (call_indirect $t (type $top)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_indirect $t (type $unrelated)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $call-indirect (type $bot)
;; This cast is guaranteed to succeed, so this directly requires $bot <: $top.
(call_indirect $t (type $top)
(i32.const 0)
)
;; This cast is guaraneed to fail. We should not introduce any subtype
;; relationships that were not already there.
(call_indirect $t (type $unrelated)
(i32.const 0)
)
)
)
(module
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (struct )))
(type $top (sub (struct)))
;; CHECK: (type $mid (sub (struct )))
(type $mid (sub $top (struct)))
;; CHECK: (type $bot (sub $mid (struct )))
(type $bot (sub $mid (struct)))
;; CHECK: (type $3 (func))
;; CHECK: (func $cast-optimizable (type $3)
;; CHECK-NEXT: (local $l (ref null $mid))
;; CHECK-NEXT: (local.set $l
;; CHECK-NEXT: (struct.new_default $bot)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref none)
;; CHECK-NEXT: (struct.new_default $top)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cast-optimizable
(local $l (ref null $mid))
(local.set $l
;; This time we require $bot <: $mid.
(struct.new $bot)
)
(drop
;; Even though $bot can now inhabit $mid, it cannot inhabit $top, so it can
;; never appear in this cast, so we do not require $mid <: $top or $bot <:
;; $top.
(ref.cast (ref $mid)
(struct.new $top)
)
)
)
)
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (struct )))
(type $top (sub (struct)))
;; CHECK: (type $mid (sub $top (struct )))
(type $mid (sub $top (struct)))
;; CHECK: (type $bot (sub (struct )))
(type $bot (sub $mid (struct)))
)
;; CHECK: (type $3 (func (param anyref)))
;; CHECK: (func $cast (type $3) (param $any anyref)
;; CHECK-NEXT: (local $l anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $bot)
;; CHECK-NEXT: (local.get $any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $top)
;; CHECK-NEXT: (local.get $any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $mid)
;; CHECK-NEXT: (local.get $any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $l
;; CHECK-NEXT: (struct.new_default $mid)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cast (param $any anyref)
(local $l anyref)
(drop
;; Cast any -> $bot
(ref.cast (ref $bot)
(local.get $any)
)
)
(drop
;; Cast any -> $top
(ref.cast (ref $top)
(local.get $any)
)
)
(drop
;; Cast any -> $mid
(ref.cast (ref $mid)
(local.get $any)
)
)
;; Require $mid <: any. This will transitively require $mid <: $top and
;; $top <: any, but $bot is unaffected.
(local.set $l
(struct.new $mid)
)
)
)
;; As above, but now with some ref.eq added. Those should not inhibit
;; optimizations: as before, $bot no longer needs to subtype from $mid (but
;; $mid must subtype from $top). ref.eq does add a requirement on subtyping
;; (that the type be a subtype of eq), but ref.eq does not actually flow the
;; inputs it receives anywhere, so that doesn't stop us from removing subtyping
;; from user types.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (struct )))
(type $top (sub (struct)))
;; CHECK: (type $mid (sub $top (struct )))
(type $mid (sub $top (struct)))
;; CHECK: (type $bot (sub (struct )))
(type $bot (sub $mid (struct)))
)
;; CHECK: (type $3 (func (param anyref (ref $top) (ref $mid) (ref $bot))))
;; CHECK: (func $cast (type $3) (param $any anyref) (param $top (ref $top)) (param $mid (ref $mid)) (param $bot (ref $bot))
;; CHECK-NEXT: (local $l anyref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.eq
;; CHECK-NEXT: (local.get $top)
;; CHECK-NEXT: (local.get $mid)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.eq
;; CHECK-NEXT: (local.get $top)
;; CHECK-NEXT: (local.get $bot)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.eq
;; CHECK-NEXT: (local.get $mid)
;; CHECK-NEXT: (local.get $bot)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $bot)
;; CHECK-NEXT: (local.get $any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $top)
;; CHECK-NEXT: (local.get $any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $mid)
;; CHECK-NEXT: (local.get $any)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $l
;; CHECK-NEXT: (struct.new_default $mid)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cast (param $any anyref) (param $top (ref $top)) (param $mid (ref $mid)) (param $bot (ref $bot))
(local $l anyref)
(drop
(ref.eq
(local.get $top)
(local.get $mid)
)
)
(drop
(ref.eq
(local.get $top)
(local.get $bot)
)
)
(drop
(ref.eq
(local.get $mid)
(local.get $bot)
)
)
(drop
;; Cast any -> $bot
(ref.cast (ref $bot)
(local.get $any)
)
)
(drop
;; Cast any -> $top
(ref.cast (ref $top)
(local.get $any)
)
)
(drop
;; Cast any -> $mid
(ref.cast (ref $mid)
(local.get $any)
)
)
(local.set $l
(struct.new $mid)
)
)
)
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $topC (sub (struct )))
(type $topC (sub (struct)))
;; CHECK: (type $midC (sub $topC (struct )))
(type $midC (sub $topC (struct)))
;; CHECK: (type $botC (sub $midC (struct )))
(type $botC (sub $midC (struct)))
;; CHECK: (type $topB (sub (struct (field (ref null $topC)))))
(type $topB (sub (struct (ref null $topC))))
;; CHECK: (type $midB (sub $topB (struct (field (ref null $botC)))))
(type $midB (sub $topB (struct (ref null $botC))))
;; CHECK: (type $botB (sub $midB (struct (field (ref null $botC)))))
(type $botB (sub $midB (struct (ref null $botC))))
;; CHECK: (type $topA (sub (struct (field (ref null $topB)))))
(type $topA (sub (struct (ref null $topB))))
;; CHECK: (type $midA (sub $topA (struct (field (ref null $botB)))))
(type $midA (sub $topA (struct (ref null $botB))))
;; CHECK: (type $botA (sub $midA (struct (field (ref null $botB)))))
(type $botA (sub $midA (struct (ref null $botB))))
)
;; CHECK: (type $9 (func))
;; CHECK: (func $cast (type $9)
;; CHECK-NEXT: (local $l (ref null $topA))
;; CHECK-NEXT: (local.set $l
;; CHECK-NEXT: (struct.new_default $botA)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $midA)
;; CHECK-NEXT: (struct.new_default $topA)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $midB)
;; CHECK-NEXT: (struct.new_default $topB)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $midC)
;; CHECK-NEXT: (struct.new_default $topC)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cast
(local $l (ref null $topA))
(local.set $l
;; Require $botA <: $topA.
(struct.new_default $botA)
)
(drop
;; Now the cast requires $midA <: $topA so that a $botA value appearing in
;; the $topA location would still pass the cast to $midA. This will
;; transitively require $botB <: $topB.
(ref.cast (ref $midA)
(struct.new_default $topA)
)
)
(drop
;; Same as before, but now for the B types. This requires $botC <: $topC, but
;; only after the previous cast has already been analyzed.
(ref.cast (ref $midB)
(struct.new_default $topB)
)
)
(drop
;; Same as before, but now for the C types. Now no types will able to be
;; optimized.
(ref.cast (ref $midC)
(struct.new $topC)
)
)
)
)