;; 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 --closed-world -S -o - | filecheck %s

;; A CallRef with no non-trapping targets must be unreachable if traps never
;; happen, in a closed world, which is what this file tests.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (func))
    (type $A (func))
    ;; CHECK:       (type $B (func))
    (type $B (func))
  )

  ;; CHECK:      (type $2 (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 $irrelevant (type $B)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 20)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $irrelevant (type $B)
    ;; This has a similar but different type, so it is irrelevant to this
    ;; optimization.
    (drop
      (i32.const 20)
    )
  )

  ;; CHECK:      (func $caller (type $2) (param $x funcref)
  ;; CHECK-NEXT:  (block ;; (replaces unreachable CallRef we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller (export "out") (param $x funcref)
    ;; This call must trap: the only function of the right type will trap.
    (call_ref $A
      (ref.cast (ref $A)
        (local.get $x)
      )
    )
  )
)

;; As above, but now there are no functions of type $A at all.
(module
  (rec
    ;; CHECK:      (type $0 (func (param funcref)))

    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (func))
    (type $A (func))
    ;; CHECK:       (type $B (func))
    (type $B (func))
  )

  ;; CHECK:      (export "out" (func $caller))

  ;; CHECK:      (func $irrelevant (type $B)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 20)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $irrelevant (type $B)
    ;; This has a similar but different type, so it is irrelevant to this
    ;; optimization.
    (drop
      (i32.const 20)
    )
  )

  ;; CHECK:      (func $caller (type $0) (param $x funcref)
  ;; CHECK-NEXT:  (block ;; (replaces unreachable CallRef we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller (export "out") (param $x funcref)
    ;; No function exists of type $A, so this will trap.
    (call_ref $A
      (ref.cast (ref $A)
        (local.get $x)
      )
    )
  )
)

;; As above, but now there is a function that can be called.
(module
  ;; CHECK:      (type $A (func))
  (type $A (func))

  ;; CHECK:      (type $1 (func (param funcref)))

  ;; CHECK:      (elem declare func $possible)

  ;; CHECK:      (export "out" (func $caller))

  ;; CHECK:      (func $possible (type $A)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible (type $A)
    (drop
      (i32.const 10)
    )
  )

  ;; CHECK:      (func $caller (type $1) (param $x funcref)
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (ref.func $possible)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller (export "out") (param $x funcref)
    ;; This must call $possible.
    (call_ref $A
      (ref.cast (ref $A)
        (local.get $x)
      )
    )
  )
)

;; As above, with another function of that type that traps.
(module
  ;; CHECK:      (type $A (func))
  (type $A (func))

  ;; CHECK:      (type $1 (func (param funcref)))

  ;; CHECK:      (elem declare func $possible)

  ;; CHECK:      (export "out" (func $caller))

  ;; CHECK:      (func $possible (type $A)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible (type $A)
    (drop
      (i32.const 10)
    )
  )

  ;; CHECK:      (func $impossible (type $A)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $impossible (type $A)
    (unreachable)
  )

  ;; CHECK:      (func $caller (type $1) (param $x funcref)
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (ref.func $possible)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller (export "out") (param $x funcref)
    ;; This must call $possible, as the trapping function won't be called.
    (call_ref $A
      (ref.cast (ref $A)
        (local.get $x)
      )
    )
  )
)

;; As above, but now we have two possible functions. We cannot optimize here.
(module
  ;; CHECK:      (type $A (func))
  (type $A (func))

  ;; CHECK:      (type $1 (func (param funcref)))

  ;; CHECK:      (export "out" (func $caller))

  ;; CHECK:      (func $possible (type $A)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible (type $A)
    (drop
      (i32.const 10)
    )
  )

  ;; CHECK:      (func $possible-2 (type $A)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 20)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible-2 (type $A)
    (drop
      (i32.const 20)
    )
  )

  ;; 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)
    (call_ref $A
      (ref.cast (ref $A)
        (local.get $x)
      )
    )
  )
)

;; As above, but now the second possible function is of a subtype. We still
;; cannot optimize a call to the parent, but we can for the child.
(module
  ;; CHECK:      (type $A (sub (func)))
  (type $A (sub (func)))

  ;; CHECK:      (type $B (sub $A (func)))
  (type $B (sub $A (func)))

  ;; CHECK:      (type $2 (func (param funcref)))

  ;; CHECK:      (elem declare func $possible-2)

  ;; CHECK:      (export "out" (func $caller))

  ;; CHECK:      (func $possible (type $A)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible (type $A)
    (drop
      (i32.const 10)
    )
  )

  ;; CHECK:      (func $possible-2 (type $B)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 20)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible-2 (type $B)
    (drop
      (i32.const 20)
    )
  )

  ;; CHECK:      (func $caller (type $2) (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:  (call_ref $B
  ;; CHECK-NEXT:   (ref.func $possible-2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller (export "out") (param $x funcref)
    ;; A has one function, but it has a subtype with another, so we cannot
    ;; optimize
    (call_ref $A
      (ref.cast (ref $A)
        (local.get $x)
      )
    )
    ;; The second call can be optimized, as B has just one function.
    (call_ref $B
      (ref.cast (ref $B)
        (local.get $x)
      )
    )
  )
)

;; Two possible functions, but one has a parameter that will trap.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $X (sub (struct)))
    (type $X (sub (struct)))

    ;; CHECK:       (type $Y1 (sub $X (struct)))
    (type $Y1 (sub $X (struct)))

    ;; CHECK:       (type $Y2 (sub $X (struct)))
    (type $Y2 (sub $X (struct)))

    ;; CHECK:       (type $A (func (param anyref)))
    (type $A (func (param anyref)))
  )

  ;; CHECK:      (type $4 (func (param funcref funcref funcref structref)))

  ;; CHECK:      (type $5 (func))

  ;; CHECK:      (elem declare func $possible-Y1 $possible-Y2)

  ;; CHECK:      (export "out" (func $caller))

  ;; CHECK:      (export "out2" (func $reffer))

  ;; CHECK:      (func $possible-Y1 (type $A) (param $ref anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y1)
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible-Y1 (type $A) (param $ref anyref)
    (drop
      (ref.cast (ref $Y1)
        (local.get $ref)
      )
    )
  )

  ;; CHECK:      (func $possible-Y2 (type $A) (param $ref anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y2)
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible-Y2 (type $A) (param $ref anyref)
    (drop
      (ref.cast (ref $Y2)
        (local.get $ref)
      )
    )
  )

  ;; CHECK:      (func $caller (type $4) (param $func1 funcref) (param $func2 funcref) (param $func3 funcref) (param $struct structref)
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (struct.new_default $Y1)
  ;; CHECK-NEXT:   (ref.func $possible-Y1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (struct.new_default $Y2)
  ;; CHECK-NEXT:   (ref.func $possible-Y2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $func3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller (export "out")
    (param $func1 funcref)
    (param $func2 funcref)
    (param $func3 funcref)
    (param $struct structref)

    ;; This would trap if we called the function that casts to Y2, so we must be
    ;; calling possible-Y1.
    (call_ref $A
      (struct.new $Y1)
      (ref.cast (ref $A)
        (local.get $func1)
      )
    )
    ;; Inverse of the above: we must call possible-Y2.
    (call_ref $A
      (struct.new $Y2)
      (ref.cast (ref $A)
        (local.get $func2)
      )
    )
    ;; This can call either one, and cannot be optimized.
    (call_ref $A
      (local.get $struct)
      (ref.cast (ref $A)
        (local.get $func3)
      )
    )
  )

  ;; CHECK:      (func $reffer (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $possible-Y1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $possible-Y2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $reffer (export "out2")
    ;; Take references to the possible functions so that GUFA does not optimize
    ;; calls to them away.
    (drop
      (ref.func $possible-Y1)
    )
    (drop
      (ref.func $possible-Y2)
    )
  )
)

;; As above, but now the target functions have two parameters, to check for
;; both of their casts being noticed. The second function still casts to Y2, but
;; it casts the second parameter to that.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $X (sub (struct)))
    (type $X (sub (struct)))

    ;; CHECK:       (type $Y1 (sub $X (struct)))
    (type $Y1 (sub $X (struct)))

    ;; CHECK:       (type $Y2 (sub $X (struct)))
    (type $Y2 (sub $X (struct)))

    ;; CHECK:       (type $A (func (param anyref anyref)))
    (type $A (func (param anyref) (param anyref)))
  )

  ;; CHECK:      (type $4 (func (param funcref funcref funcref funcref)))

  ;; CHECK:      (type $5 (func (param funcref funcref funcref funcref structref)))

  ;; CHECK:      (type $6 (func))

  ;; CHECK:      (elem declare func $possible-Y1 $possible-Y2)

  ;; CHECK:      (export "out" (func $caller1))

  ;; CHECK:      (export "out2" (func $caller2))

  ;; CHECK:      (export "out3" (func $reffer))

  ;; CHECK:      (func $possible-Y1 (type $A) (param $ref anyref) (param $ref2 anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y1)
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible-Y1 (type $A) (param $ref anyref) (param $ref2 anyref)
    (drop
      (ref.cast (ref $Y1)
        (local.get $ref)
      )
    )
  )

  ;; CHECK:      (func $possible-Y2 (type $A) (param $ref anyref) (param $ref2 anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y2)
  ;; CHECK-NEXT:    (local.get $ref2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible-Y2 (type $A) (param $ref anyref) (param $ref2 anyref)
    (drop
      (ref.cast (ref $Y2)
        (local.get $ref2) ;; this changed from ref to ref2.
      )
    )
  )

  ;; CHECK:      (func $caller1 (type $4) (param $func1 funcref) (param $func2 funcref) (param $func3 funcref) (param $func4 funcref)
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (struct.new_default $Y1)
  ;; CHECK-NEXT:   (struct.new_default $Y1)
  ;; CHECK-NEXT:   (ref.func $possible-Y1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (struct.new_default $Y2)
  ;; CHECK-NEXT:   (struct.new_default $Y2)
  ;; CHECK-NEXT:   (ref.func $possible-Y2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (struct.new_default $Y1)
  ;; CHECK-NEXT:   (struct.new_default $Y2)
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $func3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block ;; (replaces unreachable CallRef we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (struct.new_default $Y2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (struct.new_default $Y1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller1 (export "out")
    (param $func1 funcref)
    (param $func2 funcref)
    (param $func3 funcref)
    (param $func4 funcref)

    ;; Both params are Y1. The cast to Y2 would fail, so this must call
    ;; possible-Y1.
    (call_ref $A
      (struct.new $Y1)
      (struct.new $Y1)
      (ref.cast (ref $A)
        (local.get $func1)
      )
    )
    ;; Both params are Y2. The cast to Y1 would fail, so this must call
    ;; possible-Y2.
    (call_ref $A
      (struct.new $Y2)
      (struct.new $Y2)
      (ref.cast (ref $A)
        (local.get $func2)
      )
    )
    ;; Cast the first to Y1 and the second to Y2. Both functions match here, so
    ;; we cannot optimize.
    (call_ref $A
      (struct.new $Y1)
      (struct.new $Y2)
      (ref.cast (ref $A)
        (local.get $func3)
      )
    )
    ;; The inverse, which matches no functions at all, and we trap.
    (call_ref $A
      (struct.new $Y2)
      (struct.new $Y1)
      (ref.cast (ref $A)
        (local.get $func4)
      )
    )
  )

  ;; CHECK:      (func $caller2 (type $5) (param $func1 funcref) (param $func2 funcref) (param $func3 funcref) (param $func4 funcref) (param $struct structref)
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $func3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (struct.new_default $Y1)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $func3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (struct.new_default $Y2)
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (ref.func $possible-Y2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (struct.new_default $Y1)
  ;; CHECK-NEXT:   (ref.func $possible-Y1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:   (struct.new_default $Y2)
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $func3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller2 (export "out2")
    (param $func1 funcref)
    (param $func2 funcref)
    (param $func3 funcref)
    (param $func4 funcref)
    (param $struct structref)

    ;; Both are structref, so both functions are possible, and we cannot
    ;; optimize.
    (call_ref $A
      (local.get $struct)
      (local.get $struct)
      (ref.cast (ref $A)
        (local.get $func3)
      )
    )
    ;; Y1 in the first param works in both functions; no optimization.
    (call_ref $A
      (struct.new $Y1)
      (local.get $struct)
      (ref.cast (ref $A)
        (local.get $func3)
      )
    )
    ;; Y2 in the first param fails in the first, so we optimize to the second.
    (call_ref $A
      (struct.new $Y2)
      (local.get $struct)
      (ref.cast (ref $A)
        (local.get $func3)
      )
    )
    ;; Y1 in the second param fails in the second, so we optimize to the first.
    (call_ref $A
      (local.get $struct)
      (struct.new $Y1)
      (ref.cast (ref $A)
        (local.get $func3)
      )
    )
    ;; Y2 in the second param works in both functions; no optimization.
    (call_ref $A
      (local.get $struct)
      (struct.new $Y2)
      (ref.cast (ref $A)
        (local.get $func3)
      )
    )
  )

  ;; CHECK:      (func $reffer (type $6)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $possible-Y1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $possible-Y2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $reffer (export "out3")
    ;; Take references to the possible functions so that GUFA does not optimize
    ;; calls to them away.
    (drop
      (ref.func $possible-Y1)
    )
    (drop
      (ref.func $possible-Y2)
    )
  )
)

;; Casts in multiple call_ref possible targets.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $X (sub (struct)))
    (type $X (sub (struct)))

    ;; CHECK:       (type $Y1 (sub $X (struct)))
    (type $Y1 (sub $X (struct)))

    ;; CHECK:       (type $Y2 (sub $X (struct)))
    (type $Y2 (sub $X (struct)))

    ;; CHECK:       (type $A (func (param anyref anyref anyref anyref anyref anyref)))
    (type $A (func (param anyref) (param anyref) (param anyref) (param anyref) (param anyref) (param anyref)))
  )

  ;; CHECK:      (type $4 (func (param anyref anyref anyref anyref anyref anyref funcref)))

  ;; CHECK:      (type $5 (func))

  ;; CHECK:      (elem declare func $possible-1 $possible-2)

  ;; CHECK:      (export "out" (func $caller1))

  ;; CHECK:      (export "out2" (func $reffer))

  ;; CHECK:      (func $possible-1 (type $A) (param $ref anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref4)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y1)
  ;; CHECK-NEXT:    (local.get $ref5)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y1)
  ;; CHECK-NEXT:    (local.get $ref6)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible-1 (type $A) (param $ref anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref)
    (drop
      (ref.cast (ref $X)
        (local.get $ref)
      )
    )
    (drop
      (ref.cast (ref $X)
        (local.get $ref2)
      )
    )
    (drop
      (ref.cast (ref $X)
        (local.get $ref4)
      )
    )
    (drop
      (ref.cast (ref $Y1)
        (local.get $ref5)
      )
    )
    (drop
      (ref.cast (ref $Y1)
        (local.get $ref6)
      )
    )
  )

  ;; CHECK:      (func $possible-2 (type $A) (param $ref anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y1)
  ;; CHECK-NEXT:    (local.get $ref4)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref5)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y2)
  ;; CHECK-NEXT:    (local.get $ref6)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible-2 (type $A) (param $ref anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref)
    (drop
      (ref.cast (ref $X)
        (local.get $ref)
      )
    )
    (drop
      (ref.cast (ref $X)
        (local.get $ref3)
      )
    )
    (drop
      (ref.cast (ref $Y1)
        (local.get $ref4)
      )
    )
    (drop
      (ref.cast (ref $X)
        (local.get $ref5)
      )
    )
    (drop
      (ref.cast (ref $Y2)
        (local.get $ref6)
      )
    )
  )

  ;; CHECK:      (func $caller1 (type $4) (param $ref1 anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref) (param $func funcref)
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.cast anyref
  ;; CHECK-NEXT:    (local.get $ref2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.cast anyref
  ;; CHECK-NEXT:    (local.get $ref3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref4)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref5)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref6)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $func)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller1 (export "out")
    (param $ref1 anyref)
    (param $ref2 anyref)
    (param $ref3 anyref)
    (param $ref4 anyref)
    (param $ref5 anyref)
    (param $ref6 anyref)

    (param $func funcref)

    ;; All the params have a cast, so we can potentially refine them depending
    ;; on the call targets.
    (call_ref $A
      ;; The first param is cast to $X in both targets, so we can apply that.
      (ref.cast anyref
        (local.get $ref1)
      )
      ;; The second and third are cast to $X just in one of them, so we do
      ;; nothing.
      (ref.cast anyref
        (local.get $ref2)
      )
      (ref.cast anyref
        (local.get $ref3)
      )
      ;; The fourth and fifth are cast to $X and $Y1, so we can apply the LUB,
      ;; which is $X.
      (ref.cast anyref
        (local.get $ref4)
      )
      (ref.cast anyref
        (local.get $ref5)
      )
      ;; The last is cast to $Y1 and $Y2, and the LUB is $X.
      (ref.cast anyref
        (local.get $ref6)
      )
      (ref.cast (ref $A)
        (local.get $func)
      )
    )
  )

  ;; CHECK:      (func $reffer (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $possible-1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $possible-2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $reffer (export "out2")
    ;; Take references to the possible functions so that GUFA does not optimize
    ;; calls to them away.
    (drop
      (ref.func $possible-1)
    )
    (drop
      (ref.func $possible-2)
    )
  )
)

;; As above, but now the declared type is $X and not anyref, so we need to
;; improve past that.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $X (sub (struct)))
    (type $X (sub (struct)))

    ;; CHECK:       (type $Y1 (sub $X (struct)))
    (type $Y1 (sub $X (struct)))

    ;; CHECK:       (type $Y2 (sub $X (struct)))
    (type $Y2 (sub $X (struct)))

    ;; CHECK:       (type $A (func (param (ref null $X) (ref null $X) (ref null $X))))
    (type $A (func (param (ref null $X)) (param (ref null $X)) (param (ref null $X))))
  )

  ;; CHECK:      (type $4 (func (param anyref anyref anyref anyref anyref anyref funcref)))

  ;; CHECK:      (type $5 (func))

  ;; CHECK:      (elem declare func $possible-1 $possible-2)

  ;; CHECK:      (export "out" (func $caller1))

  ;; CHECK:      (export "out2" (func $reffer))

  ;; CHECK:      (func $possible-1 (type $A) (param $ref (ref null $X)) (param $ref2 (ref null $X)) (param $ref3 (ref null $X))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y1)
  ;; CHECK-NEXT:    (local.get $ref2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y1)
  ;; CHECK-NEXT:    (local.get $ref3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible-1 (type $A) (param $ref (ref null $X)) (param $ref2 (ref null $X)) (param $ref3 (ref null $X))
    (drop
      (ref.cast (ref $Y1)
        (local.get $ref2)
      )
    )
    (drop
      (ref.cast (ref $Y1)
        (local.get $ref3)
      )
    )
  )

  ;; CHECK:      (func $possible-2 (type $A) (param $ref (ref null $X)) (param $ref2 (ref null $X)) (param $ref3 (ref null $X))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y1)
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $Y1)
  ;; CHECK-NEXT:    (local.get $ref3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $possible-2 (type $A) (param $ref (ref null $X)) (param $ref2 (ref null $X)) (param $ref3 (ref null $X))
    (drop
      (ref.cast (ref $Y1)
        (local.get $ref)
      )
    )
    (drop
      (ref.cast (ref $X)
        (local.get $ref2)
      )
    )
    (drop
      (ref.cast (ref $Y1)
        (local.get $ref3)
      )
    )
  )

  ;; CHECK:      (func $caller1 (type $4) (param $ref1 anyref) (param $ref2 anyref) (param $ref3 anyref) (param $ref4 anyref) (param $ref5 anyref) (param $ref6 anyref) (param $func funcref)
  ;; CHECK-NEXT:  (call_ref $A
  ;; CHECK-NEXT:   (ref.cast (ref null $X)
  ;; CHECK-NEXT:    (local.get $ref1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.cast (ref $X)
  ;; CHECK-NEXT:    (local.get $ref2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.cast (ref $Y1)
  ;; CHECK-NEXT:    (local.get $ref3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (local.get $func)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller1 (export "out")
    (param $ref1 anyref)
    (param $ref2 anyref)
    (param $ref3 anyref)
    (param $ref4 anyref)
    (param $ref5 anyref)
    (param $ref6 anyref)

    (param $func funcref)

    ;; All the params have a cast, so we can potentially refine them depending
    ;; on the call targets.
    (call_ref $A
      ;; The first is cast to $Y1 in only one target, so we can do nothing.
      (ref.cast (ref null $X)
        (local.get $ref1)
      )
      ;; The second is cast to $X in one $Y1 in the other, so we can refine the
      ;; nullability at least.
      (ref.cast (ref null $X)
        (local.get $ref2)
      )
      ;; The third parameter is cast to $Y1 in both, so we can optimize to that.
      (ref.cast (ref null $X)
        (local.get $ref3)
      )
      (ref.cast (ref $A)
        (local.get $func)
      )
    )
  )

  ;; CHECK:      (func $reffer (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $possible-1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $possible-2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $reffer (export "out2")
    ;; Take references to the possible functions so that GUFA does not optimize
    ;; calls to them away.
    (drop
      (ref.func $possible-1)
    )
    (drop
      (ref.func $possible-2)
    )
  )
)

;; Test for a cast with incompatible heap types but nullability allows the cast
;; to succeed at runtime.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $X (sub (struct)))
    (type $X (sub (struct)))

    ;; CHECK:       (type $Y1 (sub $X (struct)))
    (type $Y1 (sub $X (struct)))

    ;; CHECK:       (type $Y2 (sub $X (struct)))
    (type $Y2 (sub $X (struct)))

    ;; CHECK:       (type $A (func (param anyref)))
    (type $A (func (param anyref)))
  )

  ;; CHECK:      (type $4 (func (param i32)))

  ;; CHECK:      (export "out" (func $caller))

  ;; CHECK:      (func $called (type $A) (param $ref anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null (exact $Y1))
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $called (type $A) (param $ref anyref)
    (drop
      (ref.cast (ref null $Y1)
        (local.get $ref)
      )
    )
  )

  ;; CHECK:      (func $caller (type $4) (param $i i32)
  ;; CHECK-NEXT:  (call $called
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $called
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $called
  ;; CHECK-NEXT:   (ref.cast (ref (exact $Y1))
  ;; CHECK-NEXT:    (select (result (ref $X))
  ;; CHECK-NEXT:     (struct.new_default $Y1)
  ;; CHECK-NEXT:     (struct.new_default $Y2)
  ;; CHECK-NEXT:     (local.get $i)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller (export "out")
    (param $i i32)

    ;; The param is either a null or a Y2, but only the null can pass the cast
    ;; in the called function, so we can infer a value of null here.
    (call $called
      (select (result (ref null $Y2))
        (ref.null $Y2)
        (struct.new $Y2)
        (local.get $i)
      )
    )

    ;; If no null is possible, this must trap.
    (call $called
      (select (result (ref $Y2))
        (struct.new $Y2)
        (struct.new $Y2)
        (local.get $i)
      )
    )

    ;; Only Y1 would succeed, so we can infer that the cast can be to $Y1.
    (call $called
      (ref.cast (ref $X)
        (select (result (ref $X))
          (struct.new $Y1)
          (struct.new $Y2)
          (local.get $i)
        )
      )
    )
  )
)
