blob: 26163e866455c40847ee0e77469251c72cc92fd7 [file] [log] [blame]
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
;; RUN: wasm-opt %s --optimize-casts -all -S -o - | filecheck %s
(module
;; CHECK: (type $A (struct ))
(type $A (struct_subtype data))
;; CHECK: (type $B (struct_subtype $A))
(type $B (struct_subtype $A))
;; CHECK: (func $ref.as (type $ref?|$A|_=>_none) (param $x (ref null $A))
;; CHECK-NEXT: (local $1 (ref $A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $1
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $ref.as (param $x (ref null $A))
;; After the first ref.as, we can use the cast value in later gets, which is
;; more refined.
(drop
(local.get $x)
)
(drop
(ref.as_non_null
(local.get $x)
)
)
(drop
(local.get $x)
)
;; In this case we don't really need the last ref.as here, but we leave that
;; for later opts.
(drop
(ref.as_non_null
(local.get $x)
)
)
)
;; CHECK: (func $ref.as-no (type $ref|$A|_=>_none) (param $x (ref $A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $ref.as-no (param $x (ref $A))
;; As above, but the param is now non-nullable anyhow, so we should do
;; nothing.
(drop
(local.get $x)
)
(drop
(ref.as_non_null
(local.get $x)
)
)
(drop
(local.get $x)
)
(drop
(ref.as_non_null
(local.get $x)
)
)
)
;; CHECK: (func $ref.cast (type $ref|struct|_=>_none) (param $x (ref struct))
;; CHECK-NEXT: (local $1 (ref $A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $1
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $ref.cast (param $x (ref struct))
;; As $ref.as but with ref.casts: we should use the cast value after it has
;; been computed, in both gets.
(drop
(ref.cast $A
(local.get $x)
)
)
(drop
(local.get $x)
)
(drop
(local.get $x)
)
)
;; CHECK: (func $not-past-set (type $ref|struct|_=>_none) (param $x (ref struct))
;; CHECK-NEXT: (local $1 (ref $A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $1
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (call $get)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $not-past-set (param $x (ref struct))
(drop
(ref.cast $A
(local.get $x)
)
)
(drop
(local.get $x)
)
;; The local.set in the middle stops us from helping the last get.
(local.set $x
(call $get)
)
(drop
(local.get $x)
)
)
;; CHECK: (func $best (type $ref|struct|_=>_none) (param $x (ref struct))
;; CHECK-NEXT: (local $1 (ref $A))
;; CHECK-NEXT: (local $2 (ref $B))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $1
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $2
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $best (param $x (ref struct))
(drop
(ref.cast $A
(local.get $x)
)
)
;; Here we should use $A.
(drop
(local.get $x)
)
(drop
(ref.cast $B
(local.get $x)
)
)
;; Here we should use $B, which is even better.
(drop
(local.get $x)
)
)
;; CHECK: (func $best-2 (type $ref|struct|_=>_none) (param $x (ref struct))
;; CHECK-NEXT: (local $1 (ref $B))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $1
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $best-2 (param $x (ref struct))
;; As above, but with the casts reversed. Now we should use $B in both
;; gets.
(drop
(ref.cast $B
(local.get $x)
)
)
(drop
(local.get $x)
)
(drop
(ref.cast $A
(local.get $x)
)
)
(drop
(local.get $x)
)
)
;; CHECK: (func $fallthrough (type $ref|struct|_=>_none) (param $x (ref struct))
;; CHECK-NEXT: (local $1 (ref $A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $1
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (block (result (ref struct))
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (block (result (ref $A))
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (call $get)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $fallthrough (param $x (ref struct))
(drop
(ref.cast $A
;; We look through the block, and optimize.
(block (result (ref struct))
(local.get $x)
)
)
)
(drop
(local.get $x)
)
(local.set $x
;; Cannot look through for sets at the moment
(block (result (ref $A))
(ref.cast $A
(call $get)
)
)
)
(drop
(local.get $x)
)
)
;; CHECK: (func $past-basic-block (type $ref|struct|_=>_none) (param $x (ref struct))
;; CHECK-NEXT: (local $1 (ref $A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (local.tee $1
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (return)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $past-basic-block (param $x (ref struct))
(drop
(ref.cast $A
(local.get $x)
)
)
;; The if means the later get is in another basic block. We do not handle
;; this atm.
(if
(i32.const 0)
(return)
)
(drop
(local.get $x)
)
(local.set $x
(ref.cast $A
(local.get $x)
)
)
(drop
(local.get $x)
)
;; Same behaviour for sets.
(if
(i32.const 0)
(return)
)
(drop
(local.get $x)
)
)
;; CHECK: (func $multiple (type $ref|struct|_ref|struct|_=>_none) (param $x (ref struct)) (param $y (ref struct))
;; CHECK-NEXT: (local $a (ref struct))
;; CHECK-NEXT: (local $b (ref struct))
;; CHECK-NEXT: (local $4 (ref $A))
;; CHECK-NEXT: (local $5 (ref $A))
;; CHECK-NEXT: (local.set $a
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $b
;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $4
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (local.get $a)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $5
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (local.get $b)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $4)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $5)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $b
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $4)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $b)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $multiple (param $x (ref struct)) (param $y (ref struct))
(local $a (ref struct))
(local $b (ref struct))
;; Two different locals, with overlapping lives.
(local.set $a
(local.get $x)
)
(local.set $b
(local.get $y)
)
(drop
(ref.cast $A
(local.get $a)
)
)
(drop
(ref.cast $A
(local.get $b)
)
)
;; These two can be optimized.
(drop
(local.get $a)
)
(drop
(local.get $b)
)
(local.set $b
(local.get $x)
)
;; Now only the first can be, since $b changed.
(drop
(local.get $a)
)
(drop
(local.get $b)
)
)
;; CHECK: (func $check-set-basic (type $ref|$A|_ref?|$A|_=>_none) (param $x (ref $A)) (param $y (ref null $A))
;; CHECK-NEXT: (local $a (ref struct))
;; CHECK-NEXT: (local $b structref)
;; CHECK-NEXT: (local $4 (ref $A))
;; CHECK-NEXT: (local $5 (ref $B))
;; CHECK-NEXT: (local.set $a
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $b
;; CHECK-NEXT: (local.tee $4
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $4)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $a
;; CHECK-NEXT: (local.tee $5
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $5)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $check-set-basic (param $x (ref $A)) (param $y (ref null $A))
(local $a (ref struct))
(local $b (ref null struct))
;; Param is already non-nullable, so set won't do anything
(local.set $a
(ref.as_non_null
(local.get $x)
)
)
(drop
(local.get $x)
)
(local.set $b
(ref.as_non_null
(local.get $y)
)
)
(drop
(local.get $b)
)
(drop
(local.tee $a
(ref.cast $B
(local.get $x)
)
)
)
(drop
(local.get $a)
)
)
;; CHECK: (func $check-set-uses-most-casted (type $none_=>_none)
;; CHECK-NEXT: (local $a (ref struct))
;; CHECK-NEXT: (local $1 (ref $B))
;; CHECK-NEXT: (local $2 (ref $A))
;; CHECK-NEXT: (local $3 (ref $B))
;; CHECK-NEXT: (local.set $a
;; CHECK-NEXT: (local.tee $1
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (call $get)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $a
;; CHECK-NEXT: (local.tee $2
;; CHECK-NEXT: (ref.cast $A
;; CHECK-NEXT: (call $get)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.tee $3
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (local.get $2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast $B
;; CHECK-NEXT: (local.get $3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $check-set-uses-most-casted
(local $a (ref struct))
(local.set $a
(ref.cast $B
(call $get)
)
)
(drop
(local.get $a)
)
(drop
;; This will use the value from the cast in the above local.set
;; since both casts are equally specific
(ref.cast $B
(local.get $a)
)
)
(drop
(ref.cast $A
(local.get $a)
)
)
(local.set $a
(ref.cast $A
(call $get)
)
)
(drop
(local.get $a)
)
(drop
;; This cast is more specific than the one in the set, so it will be used henceforth
(ref.cast $B
(local.get $a)
)
)
(drop
(ref.cast $A
(local.get $a)
)
)
)
;; CHECK: (func $get (type $none_=>_ref|struct|) (result (ref struct))
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $get (result (ref struct))
;; Helper for the above.
(unreachable)
)
)