;; 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) ) ) ) )