blob: a0dd9846191503c6407496648f13f93bbdfab992 [file] [edit]
;; 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 (exact $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: (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 $4 (func (param (ref null $A))))
;; CHECK: (type $5 (func (param anyref)))
;; CHECK: (export "caller" (func $caller))
;; CHECK: (func $called (type $4) (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 $A (sub (struct)))
(type $A (sub (struct)))
;; CHECK: (type $1 (func))
;; 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 $1)))
(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 $1)
;; 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)
)
)
)
(module
;; CHECK: (type $0 (func (param i32) (result (ref null (shared any)))))
;; CHECK: (table $0 3 3 (ref null (shared any)))
(table $0 3 3 (ref null (shared any)))
;; CHECK: (elem $0 (table $0) (i32.const 0) (ref null (shared i31)) (item (ref.i31_shared
;; CHECK-NEXT: (i32.const 999)
;; CHECK-NEXT: )))
(elem $0 (table $0) (i32.const 0) (ref null (shared i31)) (item (ref.i31_shared (i32.const 999))))
;; CHECK: (export "get" (func $0))
;; CHECK: (func $0 (type $0) (param $0 i32) (result (ref null (shared any)))
;; CHECK-NEXT: (ref.cast (ref null (shared i31))
;; CHECK-NEXT: (table.get $0
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $0 (export "get") (param $0 i32) (result (ref null (shared any)))
;; Regression test for a bug where subtypes.h did not handle shared types
;; correctly, causing this example to be misoptimized to return null.
(ref.cast (ref null (shared i31))
(table.get $0
(i32.const 0)
)
)
)
)
;; Test writes to locals that interfere with inferences about casts.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (struct)))
(type $top (sub (struct)))
;; CHECK: (type $bot (sub $top (struct)))
(type $bot (sub $top (struct)))
)
;; CHECK: (type $2 (func (param (ref $top))))
;; CHECK: (type $3 (func (param (ref extern))))
;; CHECK: (export "$invokeMain" (func $invokeMain))
;; CHECK: (func $main-set (type $2) (param $0 (ref $top))
;; CHECK-NEXT: (local.set $0
;; CHECK-NEXT: (struct.new_default $bot)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref (exact $bot))
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $main-set (param (ref $top))
;; We receive a top as input, but write a bot to it, trampling the ignored
;; parameter. Thanks to the trampling, the cast below will succeed, and so we
;; should not make anything unreachable in the caller - nothing traps here.
(local.set 0
(struct.new $bot)
)
(drop
(ref.cast (ref $bot)
(local.get 0)
)
)
)
;; CHECK: (func $main-noset (type $2) (param $0 (ref $top))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $main-noset (param (ref $top))
;; As above, but without the local.set. Here we will trap, so the caller can
;; optimize to unreachable.
(drop
(ref.cast (ref $bot)
(local.get 0)
)
)
)
;; CHECK: (func $invokeMain (type $3) (param $0 (ref extern))
;; CHECK-NEXT: (call $main-set
;; CHECK-NEXT: (struct.new_default $top)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $main-noset
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $invokeMain (export "$invokeMain") (param (ref extern))
(call $main-set
(struct.new $top)
)
(call $main-noset
(struct.new $top)
)
)
)