;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. ;; RUN: foreach %s %t wasm-opt -all --gufa -tnh -S -o - | filecheck %s (module ;; CHECK: (type $0 (func (param funcref funcref funcref funcref))) ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (global $unknown-i32 i32)) (import "a" "b" (global $unknown-i32 i32)) ;; CHECK: (import "a" "b" (global $unknown-funcref1 funcref)) (import "a" "b" (global $unknown-funcref1 funcref)) ;; CHECK: (import "a" "b" (global $unknown-funcref2 funcref)) (import "a" "b" (global $unknown-funcref2 funcref)) ;; CHECK: (import "a" "b" (global $unknown-nn-func1 (ref func))) (import "a" "b" (global $unknown-nn-func1 (ref func))) ;; CHECK: (import "a" "b" (global $unknown-nn-func2 (ref func))) (import "a" "b" (global $unknown-nn-func2 (ref func))) ;; CHECK: (func $called (type $0) (param $x funcref) (param $no-cast funcref) (param $y funcref) (param $z funcref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref func) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref func) ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref func) ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x funcref) (param $no-cast funcref) (param $y funcref) (param $z funcref) ;; All but the second parameter are cast here, which allows some ;; optimization in the caller. Nothing significant changes here in this ;; function. (drop (ref.cast (ref func) (local.get $x) ) ) (drop (ref.cast (ref func) (local.get $y) ) ) (drop (ref.cast (ref func) (local.get $z) ) ) ) ;; CHECK: (func $caller (type $1) ;; CHECK-NEXT: (local $f funcref) ;; CHECK-NEXT: (local.set $f ;; CHECK-NEXT: (select (result funcref) ;; CHECK-NEXT: (global.get $unknown-funcref1) ;; CHECK-NEXT: (global.get $unknown-funcref2) ;; CHECK-NEXT: (global.get $unknown-i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref func) ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast funcref ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: (ref.cast (ref func) ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref func) ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: (ref.cast (ref func) ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref func) ;; CHECK-NEXT: (local.get $f) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (local $f funcref) ;; Fill the local with an unknown value (so that trivial inference doesn't ;; optimize away the thing we care about below). (local.set $f (select (global.get $unknown-funcref1) (global.get $unknown-funcref2) (global.get $unknown-i32) ) ) ;; All but the third parameter are cast here. The cast has no effect by ;; itself as the type is funcref, but GUFA will refine casts when it can ;; (but not add a new cast, which might not be worth it). ;; ;; Specifically here, the first and last cast can be refined, since those ;; are cast both here and in the called function. Those casts will lose the ;; "null" and become non-nullable. (call $called (ref.cast funcref (local.get $f) ) (ref.cast funcref (local.get $f) ) (local.get $f) (ref.cast funcref (local.get $f) ) ) ;; Another call, but with different casts. (call $called (ref.cast funcref ;; this is now non-nullable, and will not change (local.get $f) ) (local.get $f) ;; this is not cast, and will not change. (ref.cast funcref (local.get $f) ;; this is now cast, and will be optimized. ) (ref.cast funcref ;; this is the same as before, and will be optimized. (local.get $f) ) ) ;; Test that we do not error in unreachable code. (call $called (unreachable) (unreachable) (unreachable) (unreachable) ) ) ) (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $2 (func)) ;; CHECK: (type $3 (func (param (ref null $A)))) ;; CHECK: (type $4 (func (param anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $maker (type $2) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $maker ;; A always contains 10, and B always contains 20. (drop (struct.new $A (i32.const 10) ) ) (drop (struct.new $B (i32.const 20) ) ) ) ;; CHECK: (func $called (type $3) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) ;; Cast the input to a $B, which will help the caller. (drop (ref.cast (ref $B) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $4) (param $any anyref) ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) (local $x (ref null $A)) ;; The called function casts to $B. This lets us infer the value of the ;; fallthrough ref.cast, which will turn into $B. Furthermore, that then ;; tells us what is written into the local $x, and the forward flow ;; analysis will use that fact in the local.get $x below. (call $called (local.tee $x (ref.cast (ref $A) (local.get $any) ) ) ) ;; We can't infer anything here at the moment, but a more sophisticated ;; analysis could. (Other passes can help here, however, by using $x where ;; $any appears.) (drop (struct.get $A 0 (ref.cast (ref $A) (local.get $any) ) ) ) (drop (ref.test (ref $B) (local.get $any) ) ) ;; We know that $x must contain $B, so this can be inferred to be 20, and ;; the ref.is to 1. (drop (struct.get $A 0 (local.get $x) ) ) (drop (ref.test (ref $B) (local.get $x) ) ) ) ) ;; A local.tee by itself, without a cast. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $2 (func (param (ref null $A)))) ;; CHECK: (type $3 (func)) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $maker (type $3) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $maker ;; A always contains 10, and B always contains 20. (drop (struct.new $A (i32.const 10) ) ) (drop (struct.new $B (i32.const 20) ) ) ) ;; CHECK: (func $called (type $2) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) ;; Cast the input to a $B, which will help the caller. (drop (ref.cast (ref $B) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $2) (param $a (ref null $A)) ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $a (ref null $A)) (local $x (ref null $A)) ;; The change compared to before is that we only have a local.tee here, and ;; no ref.cast. We can still infer the type of the tee's value, and ;; therefore the type of $x when it is read below, and optimize there. (call $called (local.tee $x (local.get $a) ) ) ;; This can be inferred to be 20. (drop (struct.get $A 0 (local.get $x) ) ) ) ) ;; As above, but add a local.tee etc. in the called function. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $2 (func (param (ref null $A)))) ;; CHECK: (type $3 (func (param anyref))) ;; CHECK: (global $global (mut i32) (i32.const 0)) (global $global (mut i32) (i32.const 0)) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $2) (param $x (ref null $A)) ;; CHECK-NEXT: (local $local (ref null $A)) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $global ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.tee $local ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (local $local (ref null $A)) ;; Some nops and such do not bother us. Even a side effect like setting a ;; global does not. (nop) (drop (i32.const 42) ) (global.set $global (i32.const 1337) ) (drop (ref.cast (ref $B) ;; This local.tee should not stop us from optimizing. (local.tee $local (local.get $x) ) ) ) ) ;; CHECK: (func $caller (type $3) (param $any anyref) ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) (local $x (ref null $A)) (call $called (local.tee $x (ref.cast (ref $A) ;; this cast will be refined (local.get $any) ) ) ) ) ) ;; As above, but now add some control flow before the cast in the function. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $1 (func (param (ref null $A)))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $3 (func (param anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $1) (param $x (ref null $A)) ;; CHECK-NEXT: (local $local (ref null $A)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (local $local (ref null $A)) ;; Control flow before the cast *does* stop us from optimizing. (if (i32.const 0) (then (return) ) ) (drop (ref.cast (ref $B) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $3) (param $any anyref) ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) (local $x (ref null $A)) (call $called (local.tee $x (ref.cast (ref $A) ;; this cast will *not* be refined (local.get $any) ) ) ) ) ) ;; As above, but make the cast uninteresting so we do not optimize. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $1 (func (param (ref null $A)))) ;; CHECK: (type $2 (func (param anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $1) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (drop (ref.cast (ref $A) ;; This cast only removes nullability. (local.get $x) ) ) ) ;; CHECK: (func $caller (type $2) (param $any anyref) ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) (local $x (ref null $A)) (call $called (local.tee $x (ref.cast (ref $A) ;; This cast will *not* be refined, as it is already non- ;; nullable here, and the other cast did not improve the ;; heap type. (local.get $any) ) ) ) ) ) ;; As above, but two casts in the called function. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $2 (func (param (ref null $A)))) ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) (type $C (sub $B (struct (field (mut i32))))) ;; CHECK: (type $4 (func (param anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $2) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) ;; Two casts. We keep the first, which is simple to do, and good enough in ;; the general case as other optimizations will leave the most-refined one. ;; (But in this test, it is less optimal actually.) (drop (ref.cast (ref $B) (local.get $x) ) ) (drop (ref.cast (ref $C) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $4) (param $any anyref) ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $x ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) (local $x (ref null $A)) (call $called (local.tee $x (ref.cast (ref $A) ;; this cast will be refined to $B. (local.get $any) ) ) ) ) ) ;; Multiple parameters with control flow between them. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $2 (func (param (ref null $A) (ref null $A) (ref null $A)))) ;; CHECK: (type $3 (func (param anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $2) (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) ;; All parameters are cast. (drop (ref.cast (ref $B) (local.get $x) ) ) (drop (ref.cast (ref $B) (local.get $y) ) ) (drop (ref.cast (ref $B) (local.get $z) ) ) ) ;; CHECK: (func $caller (type $3) (param $any anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result (ref $B)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result (ref $B)) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) ;; All three params get similar blocks+casts, but the middle one might ;; transfer control flow, so we cannot optimize the first parameter (we ;; might not reach the call, so we can't assume the call's cast suceeds). ;; As a result, only the last two casts are refined to $B, but not the ;; first. (call $called (block (result (ref $A)) (ref.cast (ref $A) (local.get $any) ) ) (block (result (ref $A)) (if (i32.const 0) (then (return) ) ) (ref.cast (ref $A) (local.get $any) ) ) (block (result (ref $A)) (ref.cast (ref $A) (local.get $any) ) ) ) ) ) ;; As above, but without the cast in the middle of the called function. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $2 (func (param (ref null $A) (ref null $A) (ref null $A)))) ;; CHECK: (type $3 (func (param anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $2) (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) (drop (ref.cast (ref $B) (local.get $x) ) ) (drop ;; This changed to not have a cast. (local.get $y) ) (drop (ref.cast (ref $B) (local.get $z) ) ) ) ;; CHECK: (func $caller (type $3) (param $any anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result (ref $B)) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) ;; We can't refine the first cast because of control flow, like before, and ;; we can't refine the middle because the called function has no cast, but ;; we can still refine the last one. (call $called (block (result (ref $A)) (ref.cast (ref $A) (local.get $any) ) ) (block (result (ref $A)) (if (i32.const 0) (then (return) ) ) (ref.cast (ref $A) (local.get $any) ) ) (block (result (ref $A)) (ref.cast (ref $A) (local.get $any) ) ) ) ) ) ;; As above, but with a different control flow transfer in the caller, a call. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $2 (func (result anyref))) ;; CHECK: (type $3 (func (param (ref null $A) (ref null $A) (ref null $A)))) ;; CHECK: (type $4 (func (param anyref))) ;; CHECK: (import "a" "b" (func $get-any (type $2) (result anyref))) (import "a" "b" (func $get-any (result anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $3) (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) ;; All parameters are cast. (drop (ref.cast (ref $B) (local.get $x) ) ) (drop (ref.cast (ref $B) (local.get $y) ) ) (drop (ref.cast (ref $B) (local.get $z) ) ) ) ;; CHECK: (func $caller (type $4) (param $any anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (call $get-any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) (call $called (ref.cast (ref $A) (local.get $any) ) (ref.cast (ref $A) ;; This call might transfer control flow (if it throws), so we ;; can't optimize before it, but the last two casts will become $B. (call $get-any) ) (ref.cast (ref $A) (local.get $any) ) ) ) ) ;; As above, but with yet another control flow transfer, using an if. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $2 (func (result anyref))) ;; CHECK: (type $3 (func (param (ref null $A) (ref null $A) (ref null $A)))) ;; CHECK: (type $4 (func (param anyref))) ;; CHECK: (import "a" "b" (func $get-any (type $2) (result anyref))) (import "a" "b" (func $get-any (result anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $3) (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (param $y (ref null $A)) (param $z (ref null $A)) ;; All parameters are cast. (drop (ref.cast (ref $B) (local.get $x) ) ) (drop (ref.cast (ref $B) (local.get $y) ) ) (drop (ref.cast (ref $B) (local.get $z) ) ) ) ;; CHECK: (func $caller (type $4) (param $any anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (if (result (ref $A)) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) (call $called (ref.cast (ref $A) (local.get $any) ) ;; One if arm transfers control flow, so while we have a fallthrough ;; value we cannot optimize it (in fact, it might never be reached, at ;; least if the constant were not 0). As a result we'll optimize only the ;; very last cast. (if (result (ref $A)) (i32.const 0) (then (return) ) (else (ref.cast (ref $A) (local.get $any) ) ) ) (ref.cast (ref $A) (local.get $any) ) ) ) ) ;; A cast that will fail. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $2 (func (param (ref null $A)))) ;; CHECK: (type $3 (func)) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $2) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (drop (ref.cast (ref $B) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $3) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (call $called ;; The called function will cast to $B, but this is an $A, so the cast ;; will fail. We can infer that the code here is unreachable. Note that no ;; ref.cast appears here - we infer this even without seeing a cast. (struct.new $A (i32.const 10) ) ) ;; Another call that will not be modified. (call $called (struct.new $B (i32.const 20) ) ) ) ) ;; Test that we refine using ref.as_non_null and not just ref.cast. (module ;; CHECK: (type $A (struct (field (mut i32)))) (type $A (struct (field (mut i32)))) ;; CHECK: (type $1 (func (param (ref null $A)))) ;; CHECK: (type $2 (func (param anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $1) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (drop (ref.as_non_null (local.get $x) ) ) ) ;; CHECK: (func $caller (type $2) (param $any anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) (call $called ;; This cast can become non-nullable. (ref.cast (ref null $A) (local.get $any) ) ) ) ) ;; Verify we do not propagate *less*-refined information. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) (type $C (sub $B (struct (field (mut i32))))) ;; CHECK: (type $3 (func (param anyref))) ;; CHECK: (type $4 (func (param (ref null $A)))) ;; CHECK: (type $5 (func)) ;; CHECK: (export "caller-C" (func $caller-C)) ;; CHECK: (export "caller-B" (func $caller-B)) ;; CHECK: (export "caller-A" (func $caller-A)) ;; CHECK: (func $called (type $4) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) ;; This function casts the A to a B. (drop (ref.cast (ref $B) (local.get $x) ) ) ) ;; CHECK: (func $maker (type $5) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $C ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $maker ;; A always contains 10, and B 20, and C 30. (drop (struct.new $A (i32.const 10) ) ) (drop (struct.new $B (i32.const 20) ) ) (drop (struct.new $C (i32.const 30) ) ) ) ;; CHECK: (func $caller-C (type $3) (param $any anyref) ;; CHECK-NEXT: (local $temp-C (ref $C)) ;; CHECK-NEXT: (local $temp-any anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $temp-C ;; CHECK-NEXT: (ref.cast (ref $C) ;; CHECK-NEXT: (local.tee $temp-any ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $B 0 ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $temp-any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller-C (export "caller-C") (param $any anyref) (local $temp-C (ref $C)) (local $temp-any anyref) (call $called (local.tee $temp-C (ref.cast (ref $C) ;; This cast is already more refined than even the called ;; function casts to. It should stay as it is. (local.tee $temp-any (local.get $any) ) ) ) ) (drop (struct.get $A 0 ;; the reference contains a C, so this value is 30. (ref.cast (ref $A) (local.get $temp-C) ) ) ) (drop (struct.get $A 0 ;; We infer that $temp-any is $B from the cast in the ;; call, so the cast here can be improved, but not the ;; value (which can be 20 or 30). ;; TODO: We can infer from the ref.cast $C in this ;; function backwards into the tee and its value. (ref.cast (ref $A) (local.get $temp-any) ) ) ) (drop (struct.get $A 0 ;; We have not inferred anything about the param, so this ;; is not optimized yet, but it could be. TODO (ref.cast (ref $A) (local.get $any) ) ) ) ) ;; CHECK: (func $caller-B (type $3) (param $any anyref) ;; CHECK-NEXT: (local $temp (ref $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $temp ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller-B (export "caller-B") (param $any anyref) (local $temp (ref $A)) (call $called (local.tee $temp (ref.cast (ref $B) ;; This cast is equal to the called cast. It should remain. (local.get $any) ) ) ) (drop (struct.get $A 0 ;; Nothing can be inferred here. (local.get $temp) ) ) ) ;; CHECK: (func $caller-A (type $3) (param $any anyref) ;; CHECK-NEXT: (local $temp (ref $A)) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (local.tee $temp ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (local.get $temp) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller-A (export "caller-A") (param $any anyref) (local $temp (ref $A)) (call $called (local.tee $temp (ref.cast (ref $A) ;; This cast is less refined, and can be improved to B. (local.get $any) ) ) ) (drop (struct.get $A 0 ;; Nothing can be inferred here. (local.get $temp) ) ) ) ) ;; Refine a type to unreachable. B1 and B2 are sibling subtypes of A, and the ;; caller passes in a B1 that is cast in the function to B2. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) (rec ;; CHECK: (type $1 (func (param (ref null $A)))) ;; CHECK: (rec ;; CHECK-NEXT: (type $B1 (sub $A (struct (field (mut i32))))) (type $B1 (sub $A (struct (field (mut i32))))) ;; CHECK: (type $B2 (sub $A (struct (field (mut i32))))) (type $B2 (sub $A (struct (field (mut i32))))) ;; CHECK: (type $C1 (sub $B1 (struct (field (mut i32))))) (type $C1 (sub $B1 (struct (field (mut i32))))) ) ;; CHECK: (type $5 (func (param anyref))) ;; CHECK: (export "caller" (func $caller)) ;; CHECK: (func $called (type $1) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B1) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (drop (ref.cast (ref $B1) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $5) (param $any anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (struct.new $B1 ;; CHECK-NEXT: (i32.const 20) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (struct.new $C1 ;; CHECK-NEXT: (i32.const 30) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "caller") (param $any anyref) ;; The cast of this A to B1 will fail, so it is unreachable. (call $called (struct.new $A (i32.const 10) ) ) ;; This cast of B1 to itself will succeed, so nothing changes. (call $called (struct.new $B1 (i32.const 20) ) ) ;; The cast of this C1 to its supertype B1 will succeed. (call $called (struct.new $C1 (i32.const 30) ) ) ;; Casting B2 to B1 will fail as they are sibling types. (call $called (struct.new $B2 (i32.const 40) ) ) ) ) ;; Check we ignore casts of non-param locals. (module ;; CHECK: (type $0 (func)) ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $0) ;; CHECK-NEXT: (local $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (local $x (ref null $A)) ;; This casts a local in the entry block, but it is not a parameter, so we ;; should ignore it and not error. (drop (ref.cast (ref null $B) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $called) ;; CHECK-NEXT: ) (func $caller (export "out") (call $called) ) ) ;; Check all combinations of types passed to a nullable cast. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) (type $C (sub $B (struct (field (mut i32))))) ;; CHECK: (type $3 (func (param (ref null $A)))) ;; CHECK: (type $4 (func (param anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $3) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref null $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (drop (ref.cast (ref null $B) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $4) (param $x anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref null $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref null $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref null $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $x anyref) ;; A non-nullable A passed into a cast of B means this value must be a non- ;; nullable B. (call $called (ref.cast (ref $A) (local.get $x) ) ) ;; This will be refined to (nullable) B. (call $called (ref.cast (ref null $A) (local.get $x) ) ) ;; Casts of B remain the same. (call $called (ref.cast (ref $B) (local.get $x) ) ) (call $called (ref.cast (ref null $B) (local.get $x) ) ) ;; Casts of C remain the same. (call $called (ref.cast (ref $C) (local.get $x) ) ) (call $called (ref.cast (ref null $C) (local.get $x) ) ) ) ) ;; Check all combinations of types passed to a *non*-nullable cast. (module ;; CHECK: (type $A (sub (struct (field (mut i32))))) (type $A (sub (struct (field (mut i32))))) ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) (type $B (sub $A (struct (field (mut i32))))) ;; CHECK: (type $C (sub $B (struct (field (mut i32))))) (type $C (sub $B (struct (field (mut i32))))) ;; CHECK: (type $3 (func (param (ref null $A)))) ;; CHECK: (type $4 (func (param anyref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $3) (param $x (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x (ref null $A)) (drop (ref.cast (ref $B) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $4) (param $x anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $C) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $x anyref) ;; Casts of A are refined. (call $called (ref.cast (ref $A) (local.get $x) ) ) ;; This will be refined to B. (call $called (ref.cast (ref null $A) (local.get $x) ) ) ;; Casts of B turn or stay as non-nullable B. (call $called (ref.cast (ref $B) (local.get $x) ) ) (call $called (ref.cast (ref null $B) (local.get $x) ) ) ;; This cast of C remains the same. (call $called (ref.cast (ref $C) (local.get $x) ) ) ;; A nullable C passed into a cast of non-null B means this value must be a ;; non-nullable C. (call $called (ref.cast (ref null $C) (local.get $x) ) ) ) ) ;; A cast of an array. (module ;; CHECK: (type $0 (func (param anyref))) ;; CHECK: (type $A (array (mut i32))) (type $A (array (mut i32))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $0) (param $x anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $x anyref) (drop (ref.cast (ref $A) (local.get $x) ) ) ) ;; CHECK: (func $caller (type $0) (param $x anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $x anyref) (call $called ;; This will be refined to a non-nullable cast. (ref.cast (ref null $A) (local.get $x) ) ) ) ) ;; Test array and struct operations. (module ;; CHECK: (type $B (array (mut i32))) ;; CHECK: (type $A (struct (field (mut i32)))) (type $A (struct (field (mut i32)))) (type $B (array (mut i32))) ;; CHECK: (type $C (array (mut funcref))) (type $C (array (mut funcref))) ;; CHECK: (type $3 (func (param (ref null $A) (ref null $A) (ref null $B) (ref null $B) (ref null $B) (ref null $B) (ref null $B) (ref null $B) (ref null $B) (ref null $C) (ref null $A)))) ;; CHECK: (type $4 (func (param anyref))) ;; CHECK: (data $d "a") (data $d "a") ;; CHECK: (elem $e func) (elem $e funcref) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $called (type $3) (param $struct.get (ref null $A)) (param $struct.set (ref null $A)) (param $array.get (ref null $B)) (param $array.set (ref null $B)) (param $array.len (ref null $B)) (param $array.copy.src (ref null $B)) (param $array.copy.dest (ref null $B)) (param $array.fill (ref null $B)) (param $array.init_data (ref null $B)) (param $array.init_elem (ref null $C)) (param $ref.test (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (local.get $struct.set) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $B ;; CHECK-NEXT: (local.get $array.get) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.set $B ;; CHECK-NEXT: (local.get $array.set) ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.len ;; CHECK-NEXT: (local.get $array.len) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.copy $B $B ;; CHECK-NEXT: (local.get $array.copy.src) ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: (local.get $array.copy.dest) ;; CHECK-NEXT: (i32.const 5) ;; CHECK-NEXT: (i32.const 6) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.fill $B ;; CHECK-NEXT: (local.get $array.fill) ;; CHECK-NEXT: (i32.const 7) ;; CHECK-NEXT: (i32.const 8) ;; CHECK-NEXT: (i32.const 9) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.init_data $B $d ;; CHECK-NEXT: (local.get $array.init_data) ;; CHECK-NEXT: (i32.const 10) ;; CHECK-NEXT: (i32.const 11) ;; CHECK-NEXT: (i32.const 12) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (array.init_elem $C $e ;; CHECK-NEXT: (local.get $array.init_elem) ;; CHECK-NEXT: (i32.const 13) ;; CHECK-NEXT: (i32.const 14) ;; CHECK-NEXT: (i32.const 15) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.test (ref $A) ;; CHECK-NEXT: (local.get $ref.test) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $struct.get (ref null $A)) (param $struct.set (ref null $A)) (param $array.get (ref null $B)) (param $array.set (ref null $B)) (param $array.len (ref null $B)) (param $array.copy.src (ref null $B)) (param $array.copy.dest (ref null $B)) (param $array.fill (ref null $B)) (param $array.init_data (ref null $B)) (param $array.init_elem (ref null $C)) (param $ref.test (ref null $A)) ;; All the operations trap on null, aside from ref.test. (drop (struct.get $A 0 (local.get $struct.get) ) ) (struct.set $A 0 (local.get $struct.set) (i32.const 0) ) (drop (array.get $B (local.get $array.get) (i32.const 1) ) ) (array.set $B (local.get $array.set) (i32.const 2) (i32.const 3) ) (drop (array.len (local.get $array.len) ) ) (array.copy $B $B (local.get $array.copy.src) (i32.const 4) (local.get $array.copy.dest) (i32.const 5) (i32.const 6) ) (array.fill $B (local.get $array.fill) (i32.const 7) (i32.const 8) (i32.const 9) ) (array.init_data $B $d (local.get $array.init_data) (i32.const 10) (i32.const 11) (i32.const 12) ) (array.init_elem $C $e (local.get $array.init_elem) (i32.const 13) (i32.const 14) (i32.const 15) ) (drop (ref.test (ref $A) (local.get $ref.test) ) ) ) ;; CHECK: (func $caller (type $4) (param $any anyref) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref $C) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (ref.cast (ref null $A) ;; CHECK-NEXT: (local.get $any) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $any anyref) ;; All these casts will be refined to non-nullable, aside from the last ;; param which is but a ref.test. (call $called (ref.cast (ref null $A) (local.get $any) ) (ref.cast (ref null $A) (local.get $any) ) (ref.cast (ref null $B) (local.get $any) ) (ref.cast (ref null $B) (local.get $any) ) (ref.cast (ref null $B) (local.get $any) ) (ref.cast (ref null $B) (local.get $any) ) (ref.cast (ref null $B) (local.get $any) ) (ref.cast (ref null $B) (local.get $any) ) (ref.cast (ref null $B) (local.get $any) ) (ref.cast (ref null $C) (local.get $any) ) (ref.cast (ref null $A) (local.get $any) ) ) ) ) ;; A CallRef with no non-trapping targets must be unreachable if traps never ;; happen. However, this requires closed world, so we do nothing here. (This ;; test is mirrored in gufa-tnh-closed, where closed world *is* enabled.) (module ;; CHECK: (type $A (func)) (type $A (func)) ;; CHECK: (type $1 (func (param funcref))) ;; CHECK: (export "out" (func $caller)) ;; CHECK: (func $impossible (type $A) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) (func $impossible (type $A) ;; This cannot be called if traps never happen. (unreachable) ) ;; CHECK: (func $caller (type $1) (param $x funcref) ;; CHECK-NEXT: (call_ref $A ;; CHECK-NEXT: (ref.cast (ref $A) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "out") (param $x funcref) ;; In closed world this call must trap, as the only function of the right ;; type will trap. But we are in open world so we do not optimize here. ;; TODO: We could analyze publicly visible tables, exports, etc. to see ;; whether any more functions could be possible even in an open world. (call_ref $A (ref.cast (ref $A) (local.get $x) ) ) ) ) ;; Control flow around calls. (module ;; CHECK: (type $0 (func)) ;; CHECK: (type $A (sub (struct ))) (type $A (sub (struct))) ;; CHECK: (type $B (sub $A (struct ))) (type $B (sub $A (struct))) ;; CHECK: (type $3 (func (param (ref null $A)))) ;; CHECK: (import "a" "b" (func $import-throw (type $0))) (import "a" "b" (func $import-throw)) ;; CHECK: (export "a" (func $caller)) ;; CHECK: (func $called (type $3) (param $0 (ref null $A)) ;; CHECK-NEXT: (call $import-throw) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $called (param $0 (ref null $A)) ;; This function calls an import, and later casts. We cannot use those casts ;; to infer anything in the callers, since the import might throw (in which ;; case we'd never reach the cast). (call $import-throw) (drop (ref.cast (ref $B) (local.get $0) ) ) ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (struct.new_default $B) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $called ;; CHECK-NEXT: (struct.new_default $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (export "a") ;; This call sends a $B which will be cast to $B (assuming the import does ;; not trap), so nothing should happen here. (call $called (struct.new $B) ) ;; This call sends an $A, which would fail the cast if it were reached. But ;; it might not, so we do nothing here. (call $called (struct.new $A) ) ) )