;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
;; RUN: wasm-opt %s --remove-unused-names --local-subtyping -all -S -o - \
;; RUN:   | filecheck %s

;; --remove-unused-names is run to avoid adding names to blocks. Block names
;; can prevent non-nullable local validation (we emit named blocks in the binary
;; format, if we need them, but never emit unnamed ones), which affects some
;; testcases.

(module
  (type $"{}" (sub (struct)))

  (type $"{i32}" (sub (struct (field i32))))

  (type $array (sub (array i8)))

  ;; CHECK:      (type $ret-any (sub (func (result anyref))))
  (type $ret-any (sub (func (result anyref))))
  ;; CHECK:      (type $ret-i31 (sub $ret-any (func (result i31ref))))
  (type $ret-i31 (sub $ret-any (func (result i31ref))))

  ;; CHECK:      (import "out" "i32" (func $i32 (type $1) (result i32)))
  (import "out" "i32" (func $i32 (result i32)))
  ;; CHECK:      (import "out" "i64" (func $i64 (type $4) (result i64)))
  (import "out" "i64" (func $i64 (result i64)))

  ;; CHECK:      (tag $e-anyref (type $7) (param anyref))
  (tag $e-anyref (param anyref))

  ;; Refinalization can find a more specific type, where the declared type was
  ;; not the optimal LUB.
  ;; CHECK:      (func $refinalize (type $2) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (if (result (ref i31))
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:    (then
  ;; CHECK-NEXT:     (ref.i31
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (else
  ;; CHECK-NEXT:     (ref.i31
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $block (result (ref i31))
  ;; CHECK-NEXT:    (br $block
  ;; CHECK-NEXT:     (ref.i31
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.i31
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $refinalize (param $x i32)
    (drop
      (if (result anyref)
        (local.get $x)
        (then
          (ref.i31 (i32.const 0))
        )
        (else
          (ref.i31 (i32.const 1))
        )
      )
    )
    (drop
      (block $block (result anyref)
        (br $block
          (ref.i31 (i32.const 0))
        )
        (ref.i31 (i32.const 1))
      )
    )
  )

  ;; A simple case where a local has a single assignment that we can use as a
  ;; more specific type. A similar thing with a parameter, however, is not a
  ;; thing we can optimize. Also, ignore a local with zero assignments.
  ;; CHECK:      (func $simple-local-but-not-param (type $8) (param $x funcref)
  ;; CHECK-NEXT:  (local $y (ref $1))
  ;; CHECK-NEXT:  (local $unused funcref)
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (ref.func $i32)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $y
  ;; CHECK-NEXT:   (ref.func $i32)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $simple-local-but-not-param (param $x funcref)
    (local $y funcref)
    (local $unused funcref)
    (local.set $x
      (ref.func $i32)
    )
    (local.set $y
      (ref.func $i32)
    )
  )

  ;; CHECK:      (func $locals-with-multiple-assignments (type $9) (param $struct structref)
  ;; CHECK-NEXT:  (local $x eqref)
  ;; CHECK-NEXT:  (local $y (ref i31))
  ;; CHECK-NEXT:  (local $z structref)
  ;; CHECK-NEXT:  (local $w (ref func))
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (ref.i31
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $y
  ;; CHECK-NEXT:   (ref.i31
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $y
  ;; CHECK-NEXT:   (ref.i31
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $z
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $z
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $w
  ;; CHECK-NEXT:   (ref.func $i32)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $w
  ;; CHECK-NEXT:   (ref.func $i64)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $locals-with-multiple-assignments (param $struct (ref null struct))
    (local $x anyref)
    (local $y anyref)
    (local $z anyref)
    (local $w funcref)
    ;; x is assigned two different types with a new LUB possible
    (local.set $x
      (ref.i31 (i32.const 0))
    )
    (local.set $x
      (local.get $struct)
    )
    ;; y and z are assigned the same more specific type twice
    (local.set $y
      (ref.i31 (i32.const 0))
    )
    (local.set $y
      (ref.i31 (i32.const 1))
    )
    (local.set $z
      (local.get $struct)
    )
    (local.set $z
      (local.get $struct)
    )
    ;; w is assigned two different types *without* a new LUB heap type possible,
    ;; as it already had the optimal LUB heap type (but it can become non-
    ;; nullable).
    (local.set $w
      (ref.func $i32)
    )
    (local.set $w
      (ref.func $i64)
    )
  )

  ;; In some cases multiple iterations are necessary, as one inferred new type
  ;; applies to a get which then allows another inference.
  ;; CHECK:      (func $multiple-iterations (type $0)
  ;; CHECK-NEXT:  (local $x (ref $1))
  ;; CHECK-NEXT:  (local $y (ref $1))
  ;; CHECK-NEXT:  (local $z (ref $1))
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (ref.func $i32)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $y
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $z
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $multiple-iterations
    (local $x funcref)
    (local $y funcref)
    (local $z funcref)
    (local.set $x
      (ref.func $i32)
    )
    (local.set $y
      (local.get $x)
    )
    (local.set $z
      (local.get $y)
    )
  )

  ;; Sometimes a refinalize is necessary in between the iterations.
  ;; CHECK:      (func $multiple-iterations-refinalize (type $2) (param $i i32)
  ;; CHECK-NEXT:  (local $x (ref $1))
  ;; CHECK-NEXT:  (local $y (ref $4))
  ;; CHECK-NEXT:  (local $z (ref func))
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (ref.func $i32)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $y
  ;; CHECK-NEXT:   (ref.func $i64)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $z
  ;; CHECK-NEXT:   (select (result (ref func))
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:    (local.get $i)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $multiple-iterations-refinalize (param $i i32)
    (local $x funcref)
    (local $y funcref)
    (local $z funcref)
    (local.set $x
      (ref.func $i32)
    )
    (local.set $y
      (ref.func $i64)
    )
    (local.set $z
      (select
        (local.get $x)
        (local.get $y)
        (local.get $i)
      )
    )
  )

  ;; CHECK:      (func $multiple-iterations-refinalize-call-ref (type $0)
  ;; CHECK-NEXT:  (local $f (ref (exact $ret-i31)))
  ;; CHECK-NEXT:  (local $x i31ref)
  ;; CHECK-NEXT:  (local.set $f
  ;; CHECK-NEXT:   (ref.func $ret-i31)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (call_ref $ret-i31
  ;; CHECK-NEXT:    (local.get $f)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $multiple-iterations-refinalize-call-ref
    (local $f (ref null $ret-any))
    (local $x anyref)
    (local.set $f
      (ref.func $ret-i31)
    )
    (local.set $x
      ;; After $f is refined to hold $ret-i31 and the call_ref is refinalized,
      ;; we will be able to refine $x to i31.
      (call_ref $ret-any
        (local.get $f)
      )
    )
  )

  ;; CHECK:      (func $multiple-iterations-refinalize-call-ref-bottom (type $0)
  ;; CHECK-NEXT:  (local $f nullfuncref)
  ;; CHECK-NEXT:  (local $x (ref none))
  ;; CHECK-NEXT:  (local.set $f
  ;; CHECK-NEXT:   (ref.null nofunc)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (block ;; (replaces unreachable CallRef we can't emit)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $f)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $multiple-iterations-refinalize-call-ref-bottom
    (local $f (ref null $ret-any))
    (local $x anyref)
    ;; Same as above, but now we refine $f to nullfuncref. Check that we don't crash.
    (local.set $f
      (ref.null nofunc)
    )
    (local.set $x
      ;; We can no longer refine $x because there is no result type we can use
      ;; after refining $f.
      (call_ref $ret-any
        (local.get $f)
      )
    )
  )

  ;; CHECK:      (func $ret-i31 (type $ret-i31) (result i31ref)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $ret-i31 (type $ret-i31) (result i31ref)
    (unreachable)
  )

  ;; CHECK:      (func $nondefaultable (type $0)
  ;; CHECK-NEXT:  (local $x (tuple funcref funcref))
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (tuple.make 2
  ;; CHECK-NEXT:    (ref.func $i32)
  ;; CHECK-NEXT:    (ref.func $i32)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nondefaultable
    (local $x (tuple funcref funcref))
    ;; This tuple is assigned non-nullable values, which means the subtype is
    ;; nondefaultable, and we must not apply it.
    (local.set $x
      (tuple.make 2
        (ref.func $i32)
        (ref.func $i32)
      )
    )
  )

  ;; CHECK:      (func $uses-default (type $2) (param $i i32)
  ;; CHECK-NEXT:  (local $x (ref null (exact $2)))
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $i)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (ref.func $uses-default)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $uses-default (param $i i32)
    (local $x funcref)
    (if
      (local.get $i)
      ;; The only set to this local uses a more specific type than funcref.
      (then
        (local.set $x (ref.func $uses-default))
      )
    )
    (drop
      ;; This get may use the default value, but it is ok to have a null of a
      ;; more refined type in the local.
      (local.get $x)
    )
  )

  ;; CHECK:      (func $unreachables (type $3) (result funcref)
  ;; CHECK-NEXT:  (local $temp (ref (exact $3)))
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (ref.func $unreachables)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $3)))
  ;; CHECK-NEXT:    (local.tee $temp
  ;; CHECK-NEXT:     (ref.func $unreachables)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $temp)
  ;; CHECK-NEXT: )
  (func $unreachables (result funcref)
    (local $temp funcref)
    ;; Set a value that allows us to refine the local's type.
    (local.set $temp
      (ref.func $unreachables)
    )
    (unreachable)
    ;; A tee that is not reachable. We must still update its type, and the
    ;; parents.
    (drop
      (block (result funcref)
        (local.tee $temp
          (ref.func $unreachables)
        )
      )
    )
    ;; A get that is not reachable. We must still update its type.
    (local.get $temp)
  )

  ;; CHECK:      (func $incompatible-sets (type $1) (result i32)
  ;; CHECK-NEXT:  (local $temp (ref null (exact $1)))
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (ref.func $incompatible-sets)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $temp
  ;; CHECK-NEXT:    (ref.null nofunc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (ref.null nofunc)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $incompatible-sets (result i32)
    (local $temp funcref)
    ;; Set a value that allows us to specialize the local type.
    (local.set $temp
      (ref.func $incompatible-sets)
    )
    ;; Make all code unreachable from here.
    (unreachable)
    ;; In unreachable code, assign values that are not compatible with the more
    ;; specific type we will optimize to. This prevents optimization here (we
    ;; will optimize better after --dce is run).
    (drop
      (local.tee $temp
        (ref.null func)
      )
    )
    (local.set $temp
      (ref.null func)
    )
    (unreachable)
  )

  ;; CHECK:      (func $become-non-nullable (type $0)
  ;; CHECK-NEXT:  (local $x (ref (exact $0)))
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (ref.func $become-non-nullable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $become-non-nullable
    (local $x (ref null func))
    (local.set $x
      (ref.func $become-non-nullable)
    )
    (drop
      (local.get $x)
    )
  )

  ;; CHECK:      (func $already-non-nullable (type $0)
  ;; CHECK-NEXT:  (local $x (ref (exact $0)))
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (ref.func $already-non-nullable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $already-non-nullable
    (local $x (ref func))
    (local.set $x
      (ref.func $already-non-nullable)
    )
    (drop
      (local.get $x)
    )
  )

  ;; CHECK:      (func $cannot-become-non-nullable (type $0)
  ;; CHECK-NEXT:  (local $x (ref null (exact $0)))
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (ref.func $become-non-nullable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $cannot-become-non-nullable
    (local $x (ref null func))
    ;; The set is in a nested scope, so we should not make the local non-
    ;; nullable, as it would not validate. (We can refine the heap type,
    ;; though.)
    (if
      (i32.const 1)
      (then
        (local.set $x
          (ref.func $become-non-nullable)
        )
      )
    )
    (drop
      (local.get $x)
    )
  )

  ;; CHECK:      (func $cannot-become-non-nullable-block (type $0)
  ;; CHECK-NEXT:  (local $x (ref null (exact $0)))
  ;; CHECK-NEXT:  (block $name
  ;; CHECK-NEXT:   (br_if $name
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $x
  ;; CHECK-NEXT:    (ref.func $become-non-nullable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $cannot-become-non-nullable-block
    (local $x (ref null func))
    ;; A named block prevents us from optimizing here, the same as above.
    (block $name
      ;; Add a br_if to avoid the name being removed.
      (br_if $name
        (i32.const 1)
      )
      (local.set $x
        (ref.func $become-non-nullable)
      )
    )
    (drop
      (local.get $x)
    )
  )

  ;; CHECK:      (func $become-non-nullable-block-unnamed (type $0)
  ;; CHECK-NEXT:  (local $x (ref (exact $0)))
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $x
  ;; CHECK-NEXT:    (ref.func $become-non-nullable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $become-non-nullable-block-unnamed
    (local $x (ref null func))
    ;; An named block does *not* prevent us from optimizing here. Unlike above,
    ;; an unnamed block is never emitted in the binary format, so it does not
    ;; prevent validation.
    (block
      (local.set $x
        (ref.func $become-non-nullable)
      )
    )
    (drop
      (local.get $x)
    )
  )

  ;; CHECK:      (func $try_table-catch-result (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $catch (result anyref)
  ;; CHECK-NEXT:    (try_table (catch $e-anyref $catch)
  ;; CHECK-NEXT:     (nop)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $try_table-catch-result
    (drop
      ;; Must not be refined to (result nullref).
      (block $catch (result anyref)
        (try_table (catch $e-anyref $catch)
          (nop)
        )
        (ref.null none)
      )
    )
  )

  ;; CHECK:      (func $try_table-ref (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $catch (result exnref)
  ;; CHECK-NEXT:    (try_table (catch_all_ref $catch)
  ;; CHECK-NEXT:     (nop)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null noexn)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $try_table-ref
    (drop
      ;; Must not be refined to nullexnref.
      ;; An exnref comes from the catch_all_ref.
      (block $catch (result exnref)
        (try_table (catch_all_ref $catch)
          (nop)
        )
        (ref.null exn)
      )
    )
  )
)
