;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
;; RUN: wasm-opt %s --rse -all -S -o - | filecheck %s

(module
 ;; CHECK:      (type $A (sub (struct (field structref))))
 (type $A (sub (struct (field (ref null struct)))))

 ;; $B is a subtype of $A, and its field has a more refined type (it is non-
 ;; nullable).
 ;; CHECK:      (type $B (sub $A (struct (field (ref struct)))))
 (type $B (sub $A (struct (field (ref struct)))))

 ;; CHECK:      (func $test (type $3)
 ;; CHECK-NEXT:  (local $single (ref func))
 ;; CHECK-NEXT:  (local $tuple (tuple (ref any) (ref any)))
 ;; CHECK-NEXT: )
 (func $test
  ;; A non-nullable local. The pass should ignore it (as we cannot optimize
  ;; anything here anyhow: the code must assign to the local before reading from
  ;; it, so no sets can be redundant in that sense).
  (local $single (ref func))
  ;; A non-nullable tuple.
  (local $tuple (tuple (ref any) (ref any)))
 )

 ;; CHECK:      (func $needs-refinalize (type $4) (param $b (ref $B)) (result anyref)
 ;; CHECK-NEXT:  (local $a (ref null $A))
 ;; CHECK-NEXT:  (local.set $a
 ;; CHECK-NEXT:   (local.get $b)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (struct.get $B 0
 ;; CHECK-NEXT:   (local.get $b)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $needs-refinalize (param $b (ref $B)) (result anyref)
  (local $a (ref null $A))
  ;; Make $a contain $b.
  (local.set $a
    (local.get $b)
  )
  (struct.get $A 0
   ;; Once more, make $a contain $b. This set is redundant. After removing it,
   ;; the struct.get will be reading from type $B, which has a more refined
   ;; field, so we must refinalize to get the right type for the instruction.
   (local.tee $a
    (local.get $b)
   )
  )
 )

 ;; CHECK:      (func $pick-refined (type $5) (param $A (ref null $A)) (param $x i32)
 ;; CHECK-NEXT:  (local $B (ref null $B))
 ;; CHECK-NEXT:  (local.set $B
 ;; CHECK-NEXT:   (ref.cast (ref null $B)
 ;; CHECK-NEXT:    (local.get $A)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $B)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $B)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (if
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:   (then
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (local.get $B)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (else
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (local.get $B)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $B)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $B)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $pick-refined (param $A (ref null $A)) (param $x i32)
  (local $B (ref null $B))
  (local.set $B
   (ref.cast (ref null $B)
    (local.get $A)
   )
  )
  ;; All these can refer to $B, the more refined type, even in branching and
  ;; merging control flow later.
  (drop
   (local.get $A)
  )
  (drop
   (local.get $B)
  )
  (if
   (local.get $x)
   (then
    (drop
     (local.get $A)
    )
   )
   (else
    (drop
     (local.get $B)
    )
   )
  )
  (drop
   (local.get $A)
  )
  (drop
   (local.get $B)
  )
 )

 ;; CHECK:      (func $pick-refined-nn (type $2) (param $A (ref $A))
 ;; CHECK-NEXT:  (local $B (ref $B))
 ;; CHECK-NEXT:  (local.set $B
 ;; CHECK-NEXT:   (ref.cast (ref $B)
 ;; CHECK-NEXT:    (local.get $A)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $B)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $B)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $pick-refined-nn (param $A (ref $A))
  (local $B (ref $B))
  ;; As above, but now the types are both non-nullable. We should still switch
  ;; to $B.
  (local.set $B
   (ref.cast (ref $B)
    (local.get $A)
   )
  )
  (drop
   (local.get $A)
  )
  (drop
   (local.get $B)
  )
 )

 ;; CHECK:      (func $avoid-unrefined (type $2) (param $A (ref $A))
 ;; CHECK-NEXT:  (local $B (ref null $B))
 ;; CHECK-NEXT:  (local.set $B
 ;; CHECK-NEXT:   (ref.cast (ref $B)
 ;; CHECK-NEXT:    (local.get $A)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $A)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $B)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $avoid-unrefined (param $A (ref $A))
  (local $B (ref null $B))
  ;; As above, but now the local is nullable. Since the parameter is non-
  ;; nullable, that means neither is a subtype of the other, and we will make
  ;; no changes.
  (local.set $B
   (ref.cast (ref $B)
    (local.get $A)
   )
  )
  (drop
   (local.get $A)
  )
  (drop
   (local.get $B)
  )
 )

 ;; CHECK:      (func $pick-refined-earlier (type $2) (param $A (ref $A))
 ;; CHECK-NEXT:  (local $A2 (ref null $A))
 ;; CHECK-NEXT:  (local.set $A2
 ;; CHECK-NEXT:   (local.get $A)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $A)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $A)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $pick-refined-earlier (param $A (ref $A))
  ;; As above but now the local has the same heap type but is nullable. Now we
  ;; prefer the non-nullable parameter.
  (local $A2 (ref null $A))
  (local.set $A2
   (local.get $A)
  )
  (drop
   (local.get $A)
  )
  (drop
   (local.get $A2)
  )
 )

 ;; CHECK:      (func $different-choices (type $2) (param $non-nullable (ref $A))
 ;; CHECK-NEXT:  (local $nullable (ref null $A))
 ;; CHECK-NEXT:  (local.set $nullable
 ;; CHECK-NEXT:   (local.get $non-nullable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $non-nullable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $nullable
 ;; CHECK-NEXT:   (ref.null none)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $nullable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $nullable
 ;; CHECK-NEXT:   (local.get $non-nullable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $non-nullable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $different-choices (param $non-nullable (ref $A))
  (local $nullable (ref null $A))
  (local.set $nullable
   (local.get $non-nullable)
  )
  ;; Here we can switch to the non-nullable one.
  (drop
   (local.get $nullable)
  )

  (local.set $nullable
   (ref.null $A)
  )
  ;; Here we cannot.
  (drop
   (local.get $nullable)
  )

  (local.set $nullable
   (local.get $non-nullable)
  )
  ;; Here we can switch once more.
  (drop
   (local.get $nullable)
  )
 )

 ;; CHECK:      (func $string (type $3)
 ;; CHECK-NEXT:  (local $s stringref)
 ;; CHECK-NEXT:  (local $t stringref)
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $s)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $s
 ;; CHECK-NEXT:   (string.const "hello")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $t
 ;; CHECK-NEXT:   (local.get $s)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $s)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $t
 ;; CHECK-NEXT:   (string.const "world!")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $t
 ;; CHECK-NEXT:   (local.get $s)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $string
  (local $s stringref)
  (local $t stringref)
  ;; This set is redundant (both are null).
  (local.set $t
    (local.get $s)
  )
  (local.set $s
   (string.const "hello")
  )
  ;; This set is not (one is not null).
  (local.set $t
    (local.get $s)
  )
  ;; This set is redundant (both are "hello").
  (local.set $t
    (local.get $s)
  )
  (local.set $t
   (string.const "world!")
  )
  ;; This set is not (one is "world!").
  (local.set $t
    (local.get $s)
  )
 )

 ;; CHECK:      (func $any-extern (type $3)
 ;; CHECK-NEXT:  (local $any anyref)
 ;; CHECK-NEXT:  (local.set $any
 ;; CHECK-NEXT:   (any.convert_extern
 ;; CHECK-NEXT:    (string.const "hello")
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (any.convert_extern
 ;; CHECK-NEXT:    (string.const "hello")
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $any
 ;; CHECK-NEXT:   (any.convert_extern
 ;; CHECK-NEXT:    (string.const "world")
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $any-extern
  ;; Test internalized strings.
  (local $any anyref)
  (local.set $any
   (any.convert_extern
    (string.const "hello")
   )
  )
  ;; This set can turn into a drop, as the value is already in the local.
  (local.set $any
   (any.convert_extern
    (string.const "hello")
   )
  )
  ;; This is a different string.
  (local.set $any
   (any.convert_extern
    (string.const "world")
   )
  )
 )
)
