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

;; Closed-world is applied here to avoid treating all ref.funcs as callable
;; from outside (and this is the more important mode to test on).

(module
  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))


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

  ;; CHECK:      (type $2 (func (result (ref $struct))))

  ;; CHECK:      (type $3 (func (result i32)))

  ;; CHECK:      (type $4 (func (result (ref any))))

  ;; CHECK:      (import "a" "b" (func $import (type $3) (result i32)))
  (import "a" "b" (func $import (result i32)))

  ;; CHECK:      (func $no-non-null (type $4) (result (ref any))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $no-non-null (result (ref any))
    ;; The only possible value at the location of this ref.as_non_null is a
    ;; null - but that value does not have a compatible type (null is nullable).
    ;; Therefore we can infer that nothing is possible here, and the code must
    ;; trap, and we'll optimize this to an unreachable.
    (ref.as_non_null
      (ref.null any)
    )
  )

  ;; CHECK:      (func $nested (type $3) (result i32)
  ;; CHECK-NEXT:  (ref.is_null
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (loop $loop
  ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (unreachable)
  ;; CHECK-NEXT:       (unreachable)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested (result i32)
    ;; As above, but add other instructions on the outside, which can also be
    ;; replaced with an unreachable (except for the loop, which as a control
    ;; flow structure with a name we keep it around and just add an unreachable
    ;; after it; and for now we don't optimize ref.is* so that stays).
    (ref.is_null
      (loop $loop (result (ref func))
        (nop)
        (ref.cast (ref func)
          (ref.as_non_null
            (ref.null func)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $yes-non-null (type $4) (result (ref any))
  ;; CHECK-NEXT:  (ref.as_non_null
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $yes-non-null (result (ref any))
    ;; Similar to the above but now there *is* an non-null value here, so there
    ;; is nothing for us to optimize or change here.
    (ref.as_non_null
      (struct.new $struct)
    )
  )

  ;; CHECK:      (func $breaks (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $block (result (ref (exact $struct)))
  ;; CHECK-NEXT:    (br $block
  ;; CHECK-NEXT:     (struct.new_default $struct)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $breaks
    ;; Check that we notice values sent along breaks. We should optimize
    ;; nothing in the first block here.
    (drop
      (block $block (result (ref any))
        (br $block
          (struct.new $struct)
        )
      )
    )
    ;; But here we send a null so we can optimize to an unreachable.
    (drop
      (ref.as_non_null
        (block $block2 (result (ref null any))
          (br $block2
            (ref.null $struct)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $get-nothing (type $2) (result (ref $struct))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $get-nothing (result (ref $struct))
    ;; This function returns a non-nullable struct by type, but does not
    ;; actually return a value in practice, and our whole-program analysis
    ;; should pick that up in optimizing the callers (but nothing changes here).
    (unreachable)
  )

  ;; CHECK:      (func $get-nothing-calls (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (call $get-nothing)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $get-nothing)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.is_null
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $get-nothing)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $get-nothing-calls
    ;; This can be optimized since the call does not actually return any
    ;; possible content (it has an unreachable), which means we can optimize
    ;; away the call's value - we must keep it around in a drop, since it can
    ;; have side effects, but the drop ignores the value which we do not need.
    (drop
      (call $get-nothing)
    )
    ;; As above, add another instruction in the middle. We can optimize it to
    ;; an unreachable, like the call.
    (drop
      (ref.as_non_null
        (call $get-nothing)
      )
    )
    ;; As above, but we do not optimize ref.is_null yet so nothing happens for
    ;; it (but the call still gets optimized as before).
    (drop
      (ref.is_null
        (call $get-nothing)
      )
    )
  )

  ;; CHECK:      (func $two-inputs (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (select
  ;; CHECK-NEXT:    (struct.new_default $struct)
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $get-nothing)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (call $import)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (select
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $get-nothing)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (struct.new_default $struct)
  ;; CHECK-NEXT:    (call $import)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (select (result (ref (exact $struct)))
  ;; CHECK-NEXT:    (struct.new_default $struct)
  ;; CHECK-NEXT:    (struct.new_default $struct)
  ;; CHECK-NEXT:    (call $import)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $get-nothing)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $get-nothing)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (call $import)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $two-inputs
    ;; As above, but now the outer instruction is a select, and some of the arms
    ;; may have a possible type - we check all 4 permutations. Only in the
    ;; case where both inputs are nothing can we optimize away the select (that
    ;; is, drop it and ignore its value), as only then will the select never
    ;; have any contents.
    ;; (Note: we are not fully optimal here since we could notice that the
    ;; select executes both arms unconditionally, so if one traps then it will
    ;; all trap.)
    (drop
      (select (result (ref any))
        (struct.new $struct)
        (call $get-nothing)
        (call $import)
      )
    )
    (drop
      (select (result (ref any))
        (call $get-nothing)
        (struct.new $struct)
        (call $import)
      )
    )
    (drop
      (select (result (ref any))
        (struct.new $struct)
        (struct.new $struct)
        (call $import)
      )
    )
    (drop
      (select (result (ref any))
        (call $get-nothing)
        (call $get-nothing)
        (call $import)
      )
    )
  )

  ;; CHECK:      (func $get-something-flow (type $2) (result (ref $struct))
  ;; CHECK-NEXT:  (struct.new_default $struct)
  ;; CHECK-NEXT: )
  (func $get-something-flow (result (ref $struct))
    ;; Return a value by flowing it out. Helper for later code.
    (struct.new $struct)
  )

  ;; CHECK:      (func $get-something-return (type $2) (result (ref $struct))
  ;; CHECK-NEXT:  (return
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $get-something-return (result (ref $struct))
    ;; Return a value using an explicit return. Helper for later code.
    (return
      (struct.new $struct)
    )
  )

  ;; CHECK:      (func $call-get-something (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (call $get-something-flow)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (call $get-something-return)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $call-get-something
    ;; In both of these cases a value is actually returned and there is nothing
    ;; to optimize, unlike get-nothing from above.
    (drop
      (call $get-something-flow)
    )
    (drop
      (call $get-something-return)
    )
  )

  ;; CHECK:      (func $locals (type $1)
  ;; CHECK-NEXT:  (local $x anyref)
  ;; CHECK-NEXT:  (local $y anyref)
  ;; CHECK-NEXT:  (local $z anyref)
  ;; CHECK-NEXT:  (local.tee $x
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (call $get-nothing)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $z
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $z)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $locals
    (local $x (ref null any))
    (local $y (ref null any))
    (local $z (ref null any))
    ;; Assign to x from a call that actually will not return anything. We will
    ;; be able to optimize away the call's return value (drop it) and append an
    ;; unreachable.
    (local.set $x
      (call $get-nothing)
    )
    ;; Never assign to y.
    ;; Assign to z an actual value.
    (local.set $z
      (struct.new $struct)
    )
    ;; Get the 3 locals, to check that we optimize. We can replace x with an
    ;; unreachable and y with a null constant.
    (drop
      (local.get $x)
    )
    (drop
      (local.get $y)
    )
    (drop
      (local.get $z)
    )
  )
)

(module
  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))

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

  ;; CHECK:      (global $null anyref (ref.null none))
  (global $null (ref null any) (ref.null any))
  ;; CHECK:      (global $something anyref (struct.new_default $struct))
  (global $something (ref null any) (struct.new $struct))

  ;; CHECK:      (global $mut-null (mut anyref) (ref.null none))
  (global $mut-null (mut (ref null any)) (ref.null any))
  ;; CHECK:      (global $mut-something (mut anyref) (ref.null none))
  (global $mut-something (mut (ref null any)) (ref.null any))

  ;; CHECK:      (func $read-globals (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (global.get $something)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (global.get $mut-something)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $read-globals
    ;; This global has no possible contents aside from a null, which we can
    ;; infer and place here.
    (drop
      (global.get $null)
    )
    ;; This global has no possible contents aside from a null, so the
    ;; ref.as_non_null can be optimized to an unreachable (since a null is not
    ;; compatible with its non-nullable type).
    (drop
      (ref.as_non_null
        (global.get $null)
      )
    )
    ;; This global has a possible non-null value (in the initializer), so there
    ;; is nothing to do.
    (drop
      (ref.as_non_null
        (global.get $something)
      )
    )
    ;; This mutable global has a write aside from the initializer, but it is
    ;; also of a null, so we can optimize here.
    (drop
      (ref.as_non_null
        (global.get $mut-null)
      )
    )
    ;; This one also has a later write, of a non-null value, so there is nothing
    ;; to do.
    (drop
      (ref.as_non_null
        (global.get $mut-something)
      )
    )
  )

  ;; CHECK:      (func $write-globals (type $1)
  ;; CHECK-NEXT:  (global.set $mut-null
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $mut-something
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $write-globals
    (global.set $mut-null
      (ref.null $struct)
    )
    (global.set $mut-something
      (struct.new $struct)
    )
  )
)

;; As above, but now with a chain of globals: A starts with a value, which is
;; copied to B, and then C, and then C is read. We will be able to optimize
;; away *-null (which is where A-null starts with null) but not *-something
;; (which is where A-something starts with a value).
(module
  ;; CHECK:      (type $0 (func))

  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))

  ;; CHECK:      (global $A-null anyref (ref.null none))
  (global $A-null (ref null any) (ref.null any))
  ;; CHECK:      (global $A-something anyref (struct.new_default $struct))
  (global $A-something (ref null any) (struct.new $struct))

  ;; CHECK:      (global $B-null (mut anyref) (ref.null none))
  (global $B-null (mut (ref null any)) (ref.null any))
  ;; CHECK:      (global $B-something (mut anyref) (ref.null none))
  (global $B-something (mut (ref null any)) (ref.null any))

  ;; CHECK:      (global $C-null (mut anyref) (ref.null none))
  (global $C-null (mut (ref null any)) (ref.null any))
  ;; CHECK:      (global $C-something (mut anyref) (ref.null none))
  (global $C-something (mut (ref null any)) (ref.null any))

  ;; CHECK:      (func $read-globals (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (global.get $A-something)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (global.get $B-something)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (global.get $C-something)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $read-globals
    (drop
      (ref.as_non_null
        (global.get $A-null)
      )
    )
    (drop
      (ref.as_non_null
        (global.get $A-something)
      )
    )
    (drop
      (ref.as_non_null
        (global.get $B-null)
      )
    )
    (drop
      (ref.as_non_null
        (global.get $B-something)
      )
    )
    (drop
      (ref.as_non_null
        (global.get $C-null)
      )
    )
    (drop
      (ref.as_non_null
        (global.get $C-something)
      )
    )
  )

  ;; CHECK:      (func $write-globals (type $0)
  ;; CHECK-NEXT:  (global.set $B-null
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $C-null
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $B-something
  ;; CHECK-NEXT:   (global.get $A-something)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $C-something
  ;; CHECK-NEXT:   (global.get $B-something)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $write-globals
    (global.set $B-null
      (global.get $A-null)
    )
    (global.set $C-null
      (global.get $B-null)
    )
    (global.set $B-something
      (global.get $A-something)
    )
    (global.set $C-something
      (global.get $B-something)
    )
  )
)

(module
  ;; CHECK:      (type $0 (func (param (ref any)) (result (ref any))))

  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))

  ;; CHECK:      (type $2 (func (param i32) (result i32)))

  ;; CHECK:      (type $3 (func (param (ref any) (ref any) (ref any))))

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

  ;; CHECK:      (func $never-called (type $2) (param $x i32) (result i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $never-called (param $x i32) (result i32)
    ;; This function is never called, so the parameter has no possible contents,
    ;; and we can optimize to an unreachable.
    (local.get $x)
  )

  ;; CHECK:      (func $never-called-ref (type $0) (param $x (ref any)) (result (ref any))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $never-called-ref (param $x (ref any)) (result (ref any))
    ;; As above but with a reference type. Again, we can apply an unreachable.
    (local.get $x)
  )

  ;; CHECK:      (func $recursion (type $0) (param $x (ref any)) (result (ref any))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (call $recursion
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $recursion (param $x (ref any)) (result (ref any))
    ;; This function calls itself recursively. That forms a loop, but still,
    ;; nothing reaches here, so we can optimize to an unreachable (we cannot
    ;; remove the call though, as it has effects, so we drop it).
    (call $recursion
      (local.get $x)
    )
  )

  ;; CHECK:      (func $called (type $3) (param $x (ref any)) (param $y (ref any)) (param $z (ref any))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $z)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $called (param $x (ref any)) (param $y (ref any)) (param $z (ref any))
    ;; This function is called, with possible (non-null) values in the 1st & 3rd
    ;; params, but nothing can arrive in the 2nd, which we can optimize to an
    ;; unreachable.
    (drop
      (local.get $x)
    )
    (drop
      (local.get $y)
    )
    (drop
      (local.get $z)
    )
  )

  ;; CHECK:      (func $call-called (type $4)
  ;; CHECK-NEXT:  (call $called
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $called
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $call-called
    ;; Call the above function as described there: Nothing can arrive in the
    ;; second param (since we cast a null to non-null there), while the others
    ;; have both a null and a non-null (different in the 2 calls here). (With
    ;; more precise analysis we could see that the ref.as must trap, and we
    ;; could optimize even more here.)
    (call $called
      (struct.new $struct)
      (ref.as_non_null
        (ref.null any)
      )
      (ref.as_non_null
        (ref.null any)
      )
    )
    (call $called
      (ref.as_non_null
        (ref.null any)
      )
      (ref.as_non_null
        (ref.null any)
      )
      (struct.new $struct)
    )
  )
)

;; As above, but using indirect calls.
(module
  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))

  ;; CHECK:      (type $two-params (func (param (ref $struct) (ref $struct))))
  (type $two-params (func (param (ref $struct)) (param (ref $struct))))

  ;; CHECK:      (type $three-params (func (param (ref $struct) (ref $struct) (ref $struct))))
  (type $three-params (func (param (ref $struct)) (param (ref $struct)) (param (ref $struct))))

  (table 10 funcref)

  (elem (i32.const 0) funcref
    (ref.func $func-2params-a)
    (ref.func $func-2params-b)
    (ref.func $func-3params)
  )

  ;; CHECK:      (table $0 10 funcref)

  ;; CHECK:      (elem $0 (i32.const 0) $func-2params-a $func-2params-b $func-3params)

  ;; CHECK:      (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_indirect $0 (type $two-params)
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
    ;; Only null is possible for the first, so we can optimize it to an
    ;; unreachable.
    (drop
      (local.get $x)
    )
    (drop
      (local.get $y)
    )
    ;; Send a value only to the second param.
    (call_indirect (type $two-params)
      (ref.as_non_null
        (ref.null $struct)
      )
      (struct.new $struct)
      (i32.const 0)
    )
  )

  ;; CHECK:      (func $func-2params-b (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func-2params-b (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
    ;; Another function with the same signature as before, which we should
    ;; optimize in the same way: the indirect call can go to either.
    (drop
      (local.get $x)
    )
    (drop
      (local.get $y)
    )
  )

  ;; CHECK:      (func $func-3params (type $three-params) (param $x (ref $struct)) (param $y (ref $struct)) (param $z (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $z)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_indirect $0 (type $three-params)
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_indirect $0 (type $three-params)
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func-3params (type $three-params) (param $x (ref $struct)) (param $y (ref $struct)) (param $z (ref $struct))
    (drop
      (local.get $x)
    )
    (drop
      (local.get $y)
    )
    (drop
      (local.get $z)
    )
    ;; Send a non-null value only to the first and third param. Do so in two
    ;; separate calls. The second param, $y, can be optimized.
    (call_indirect (type $three-params)
      (struct.new $struct)
      (ref.as_non_null
        (ref.null $struct)
      )
      (ref.as_non_null
        (ref.null $struct)
      )
      (i32.const 0)
    )
    (call_indirect (type $three-params)
      (ref.as_non_null
        (ref.null $struct)
      )
      (ref.as_non_null
        (ref.null $struct)
      )
      (struct.new $struct)
      (i32.const 0)
    )
  )
)

;; As above, but using call_ref.
(module
  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))

  ;; CHECK:      (type $two-params (func (param (ref $struct) (ref $struct))))
  (type $two-params (func (param (ref $struct)) (param (ref $struct))))

  ;; CHECK:      (elem declare func $func-2params-a)

  ;; CHECK:      (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $two-params
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:   (ref.func $func-2params-a)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func-2params-a (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
    (drop
      (local.get $x)
    )
    (drop
      (local.get $y)
    )
    ;; Send a non-null value only to the second param.
    (call_ref $two-params
      (ref.as_non_null
        (ref.null $struct)
      )
      (struct.new $struct)
      (ref.func $func-2params-a)
    )
  )
)

;; Array creation.
(module
  ;; CHECK:      (type $vector (array (mut f64)))
  (type $vector (array (mut f64)))

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

  ;; CHECK:      (func $arrays (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (array.new $vector
  ;; CHECK-NEXT:     (f64.const 3.14159)
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (array.new_default $vector
  ;; CHECK-NEXT:     (i32.const 100)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (array.new_fixed $vector 2
  ;; CHECK-NEXT:     (f64.const 1.1)
  ;; CHECK-NEXT:     (f64.const 2.2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $arrays
    (drop
      (ref.as_non_null
        (array.new $vector
          (f64.const 3.14159)
          (i32.const 1)
        )
      )
    )
    (drop
      (ref.as_non_null
        (array.new_default $vector
          (i32.const 100)
        )
      )
    )
    (drop
      (ref.as_non_null
        (array.new_fixed $vector 2
          (f64.const 1.1)
          (f64.const 2.2)
        )
      )
    )
    ;; In the last case we have no possible non-null value and can optimize to
    ;; an unreachable.
    (drop
      (ref.as_non_null
        (ref.null $vector)
      )
    )
  )
)

;; Struct fields.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $struct (sub (struct)))
    (type $struct (sub (struct)))

    ;; CHECK:       (type $parent (sub (struct (field (mut (ref null $struct))))))
    (type $parent (sub (struct (field (mut (ref null $struct))))))
    ;; CHECK:       (type $child (sub $parent (struct (field (mut (ref null $struct))) (field (mut (ref null $struct))))))
    (type $child (sub $parent (struct (field (mut (ref null $struct))) (field (mut (ref null $struct))))))

    ;; CHECK:       (type $unrelated (struct))
    (type $unrelated (struct))
  )

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

  ;; CHECK:      (func $func (type $4)
  ;; CHECK-NEXT:  (local $child (ref null $child))
  ;; CHECK-NEXT:  (local $parent (ref null $parent))
  ;; CHECK-NEXT:  (local.set $child
  ;; CHECK-NEXT:   (struct.new $child
  ;; CHECK-NEXT:    (struct.new_default $struct)
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 0
  ;; CHECK-NEXT:    (local.get $child)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get $child 1
  ;; CHECK-NEXT:      (local.get $child)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $parent
  ;; CHECK-NEXT:   (struct.new $parent
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get $parent 0
  ;; CHECK-NEXT:      (local.get $parent)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (block $parent (result (ref none))
  ;; CHECK-NEXT:       (drop
  ;; CHECK-NEXT:        (br_on_cast $parent (ref (exact $unrelated)) (ref none)
  ;; CHECK-NEXT:         (struct.new_default $unrelated)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (unreachable)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func
    (local $child (ref null $child))
    (local $parent (ref null $parent))
    ;; We create a child with a non-null value in field 0 and null in 1.
    (local.set $child
      (struct.new $child
        (struct.new $struct)
        (ref.null $struct)
      )
    )
    ;; Getting field 0 should not be optimized or changed in any way.
    (drop
      (struct.get $child 0
        (local.get $child)
      )
    )
    ;; Field one can be optimized into a null constant (+ a drop of the get).
    (drop
      (struct.get $child 1
        (local.get $child)
      )
    )
    ;; Create a parent with a null. The child wrote to the shared field, but
    ;; using exact type info we can infer that the get's value must be a null,
    ;; so we can optimize.
    (local.set $parent
      (struct.new $parent
        (ref.null $struct)
      )
    )
    (drop
      (struct.get $parent 0
        (local.get $parent)
      )
    )
    ;; An unrelated type is cast to a struct type, and then we read from that.
    ;; The cast will trap at runtime, of course; for here, we should not error
    ;; and also we can optimize these to unreachables. atm we filter out
    ;; trapping contents in ref.cast, but not br_on_cast, so test both.
    (drop
      (struct.get $parent 0
        (ref.cast (ref $parent)
          (struct.new $unrelated)
        )
      )
    )
    (drop
      (struct.get $parent 0
        (block $parent (result (ref $parent))
          (drop
            (br_on_cast $parent anyref (ref $parent)
              (struct.new $unrelated)
            )
          )
          (unreachable)
        )
      )
    )
  )

  ;; CHECK:      (func $nulls (type $4)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block $block (result nullref)
  ;; CHECK-NEXT:      (br $block
  ;; CHECK-NEXT:       (ref.null none)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block $block0 (result nullref)
  ;; CHECK-NEXT:      (br $block0
  ;; CHECK-NEXT:       (ref.null none)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block $block1 (result nullref)
  ;; CHECK-NEXT:      (br $block1
  ;; CHECK-NEXT:       (block (result nullref)
  ;; CHECK-NEXT:        (drop
  ;; CHECK-NEXT:         (ref.cast nullref
  ;; CHECK-NEXT:          (ref.null none)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (ref.null none)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nulls
    ;; Leave null constants alone.
    (drop
      (ref.null $parent)
    )
    ;; Reading from a null reference is easy to optimize - it will trap.
    (drop
      (struct.get $parent 0
        (ref.null $parent)
      )
    )
    ;; Send a null to the block, which is the only value exiting, so we can
    ;; optimize here.
    (drop
      (block $block (result (ref null any))
        (br $block
          (ref.null any)
        )
        (unreachable)
      )
    )
    ;; Send a more specific type. We should emit a valid null constant (but in
    ;; this case, a null of either $parent or $child would be ok).
    (drop
      (block $block (result (ref null $parent))
        (br $block
          (ref.null $child)
        )
        (unreachable)
      )
    )
    ;; Send a less specific type, via a cast. But all nulls are identical and
    ;; ref.cast null passes nulls through, so this is ok, but we must be careful to
    ;; emit a ref.null $child on the outside (to not change the outer type to a
    ;; less refined one).
    (drop
      (block $block (result (ref null $child))
        (br $block
          (ref.cast (ref null $child)
            (ref.null $parent)
          )
        )
        (unreachable)
      )
    )
  )
)

;; Default values in struct fields.
(module
  (rec
    (type $A (sub (struct (field i32))))
    (type $B (sub (struct (field i32))))
    (type $C (sub (struct (field i32))))
  )

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

  ;; CHECK:      (func $func (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func
    ;; Create a struct with default values. We can propagate a 0 to the get.
    (drop
      (struct.get $A 0
        (struct.new_default $A)
      )
    )
    ;; Allocate with a non-default value, that can also be propagated.
    (drop
      (struct.get $B 0
        (struct.new $B
          (i32.const 1)
        )
      )
    )
  )
)

;; Exact types: Writes to the parent class do not confuse us.
(module
  ;; CHECK:      (type $struct (sub (struct)))
  (type $struct (sub (struct)))
  ;; CHECK:      (type $parent (sub (struct (field (mut (ref null $struct))))))
  (type $parent (sub (struct (field (mut (ref null $struct))))))
  ;; CHECK:      (type $child (sub $parent (struct (field (mut (ref null $struct))) (field i32))))
  (type $child (sub $parent (struct (field (mut (ref null $struct))) (field i32))))

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

  ;; CHECK:      (func $func (type $3)
  ;; CHECK-NEXT:  (local $child (ref null $child))
  ;; CHECK-NEXT:  (local $parent (ref null $parent))
  ;; CHECK-NEXT:  (local.set $parent
  ;; CHECK-NEXT:   (struct.new $parent
  ;; CHECK-NEXT:    (struct.new_default $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (struct.get $parent 0
  ;; CHECK-NEXT:     (local.get $parent)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $child
  ;; CHECK-NEXT:   (struct.new $child
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result nullref)
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (struct.get $child 0
  ;; CHECK-NEXT:        (local.get $child)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (ref.null none)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func
    (local $child (ref null $child))
    (local $parent (ref null $parent))
    ;; Allocate when writing to the parent's field.
    (local.set $parent
      (struct.new $parent
        (struct.new $struct)
      )
    )
    ;; This cannot be optimized in any way.
    (drop
      (ref.as_non_null
        (struct.get $parent 0
          (local.get $parent)
        )
      )
    )
    ;; The child writes a null to the first field.
    (local.set $child
      (struct.new $child
        (ref.null $struct)
        (i32.const 0)
      )
    )
    ;; The parent wrote to the shared field, but that does not prevent us from
    ;; seeing that the child must have a null there, and so this will trap.
    (drop
      (ref.as_non_null
        (struct.get $child 0
          (local.get $child)
        )
      )
    )
  )
)

;; Write values to the parent *and* the child and read from the child.
(module
  ;; CHECK:      (type $parent (sub (struct (field (mut i32)))))
  (type $parent (sub (struct (field (mut i32)))))
  ;; CHECK:      (type $child (sub $parent (struct (field (mut i32)) (field i32))))
  (type $child (sub $parent (struct (field (mut i32)) (field i32))))

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

  ;; CHECK:      (func $func (type $2)
  ;; CHECK-NEXT:  (local $child (ref null $child))
  ;; CHECK-NEXT:  (local $parent (ref null $parent))
  ;; CHECK-NEXT:  (local.set $parent
  ;; CHECK-NEXT:   (struct.new $parent
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get $parent 0
  ;; CHECK-NEXT:      (local.get $parent)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $child
  ;; CHECK-NEXT:   (struct.new $child
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:    (i32.const 30)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get $child 0
  ;; CHECK-NEXT:      (local.get $child)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get $child 1
  ;; CHECK-NEXT:      (local.get $child)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 30)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func
    (local $child (ref null $child))
    (local $parent (ref null $parent))
    (local.set $parent
      (struct.new $parent
        (i32.const 10)
      )
    )
    ;; This can be optimized to 10. The child also sets this field, but the
    ;; reference in the local $parent can only be a $parent and nothing else.
    (drop
      (struct.get $parent 0
        (local.get $parent)
      )
    )
    (local.set $child
      (struct.new $child
        ;; The value here conflicts with the parent's for this field, but the
        ;; local $child can only contain a $child and nothing else, so we can
        ;; optimize the get below us.
        (i32.const 20)
        (i32.const 30)
      )
    )
    (drop
      (struct.get $child 0
        (local.get $child)
      )
    )
    ;; This get aliases nothing but 30, so we can optimize.
    (drop
      (struct.get $child 1
        (local.get $child)
      )
    )
  )
)

;; As above, but the $parent local can now contain a child too.
(module
  ;; CHECK:      (type $parent (sub (struct (field (mut i32)))))
  (type $parent (sub (struct (field (mut i32)))))
  ;; CHECK:      (type $child (sub $parent (struct (field (mut i32)) (field i32))))
  (type $child (sub $parent (struct (field (mut i32)) (field i32))))

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

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

  ;; CHECK:      (func $func (type $2) (param $x i32)
  ;; CHECK-NEXT:  (local $child (ref null $child))
  ;; CHECK-NEXT:  (local $parent (ref null $parent))
  ;; CHECK-NEXT:  (local.set $parent
  ;; CHECK-NEXT:   (struct.new $parent
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $parent
  ;; CHECK-NEXT:     (local.tee $child
  ;; CHECK-NEXT:      (struct.new $child
  ;; CHECK-NEXT:       (i32.const 20)
  ;; CHECK-NEXT:       (i32.const 30)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $parent 0
  ;; CHECK-NEXT:    (local.get $parent)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get $child 0
  ;; CHECK-NEXT:      (local.get $child)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (export "func") (param $x i32)
    (local $child (ref null $child))
    (local $parent (ref null $parent))
    (local.set $parent
      (struct.new $parent
        (i32.const 10)
      )
    )
    ;; Another, optional, set to $parent.
    (if
      (local.get $x)
      (then
        (local.set $parent
          (local.tee $child
            (struct.new $child
              (i32.const 20)
              (i32.const 30)
            )
          )
        )
      )
    )
    ;; This get cannot be optimized because before us the local might be set a
    ;; child as well. So the local $parent can refer to either type, and they
    ;; disagree on the aliased value.
    (drop
      (struct.get $parent 0
        (local.get $parent)
      )
    )
    ;; But this one can be optimized as $child can only contain a child.
    (drop
      (struct.get $child 0
        (local.get $child)
      )
    )
  )
)

;; As above, but now the parent and child happen to agree on the aliased value.
(module
  ;; CHECK:      (type $parent (sub (struct (field (mut i32)))))
  (type $parent (sub (struct (field (mut i32)))))
  ;; CHECK:      (type $child (sub $parent (struct (field (mut i32)) (field i32))))
  (type $child (sub $parent (struct (field (mut i32)) (field i32))))

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

  ;; CHECK:      (func $func (type $2)
  ;; CHECK-NEXT:  (local $child (ref null $child))
  ;; CHECK-NEXT:  (local $parent (ref null $parent))
  ;; CHECK-NEXT:  (local.set $parent
  ;; CHECK-NEXT:   (struct.new $parent
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get $parent 0
  ;; CHECK-NEXT:      (local.get $parent)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $parent
  ;; CHECK-NEXT:   (local.tee $child
  ;; CHECK-NEXT:    (struct.new $child
  ;; CHECK-NEXT:     (i32.const 10)
  ;; CHECK-NEXT:     (i32.const 30)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get $child 0
  ;; CHECK-NEXT:      (local.get $child)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func
    (local $child (ref null $child))
    (local $parent (ref null $parent))
    (local.set $parent
      (struct.new $parent
        (i32.const 10)
      )
    )
    (drop
      (struct.get $parent 0
        (local.get $parent)
      )
    )
    (local.set $parent
      (local.tee $child
        (struct.new $child
          (i32.const 10) ;; This is 10, like above, so we can optimize the get
                         ;; before us.
          (i32.const 30)
        )
      )
    )
    (drop
      (struct.get $child 0
        (local.get $child)
      )
    )
  )
)

;; Arrays get/set
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $nothing (sub (array (mut anyref))))
    (type $nothing (sub (array (mut (ref null any)))))

    ;; CHECK:       (type $null (sub (array (mut anyref))))
    (type $null (sub (array (mut (ref null any)))))

    ;; CHECK:       (type $something (sub (array (mut anyref))))
    (type $something (sub (array (mut (ref null any)))))

    ;; CHECK:       (type $something-child (sub $something (array (mut anyref))))
    (type $something-child (sub $something (array (mut (ref null any)))))
  )

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

  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))

  ;; CHECK:      (func $func (type $4)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (array.set $null
  ;; CHECK-NEXT:   (array.new_default $null
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result nullref)
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (array.get $null
  ;; CHECK-NEXT:        (array.new_default $null
  ;; CHECK-NEXT:         (i32.const 10)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (i32.const 0)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (ref.null none)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (array.set $something
  ;; CHECK-NEXT:   (array.new_default $something
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (array.get $something
  ;; CHECK-NEXT:     (array.new_default $something
  ;; CHECK-NEXT:      (i32.const 10)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func
    ;; Reading from a null will trap, and we can optimize to an unreachable.
    (drop
      (array.get $nothing
        (ref.null $nothing)
        (i32.const 0)
      )
    )
    ;; Write a null to this array.
    (array.set $null
      (array.new_default $null
        (i32.const 10)
      )
      (i32.const 0)
      (ref.null any)
    )
    ;; We can only read a null here, so this will trap and can be optimized.
    (drop
      (ref.as_non_null
        (array.get $null
          (array.new_default $null
            (i32.const 10)
          )
          (i32.const 0)
        )
      )
    )
    ;; In $something we do actually write a non-null value, so we cannot add
    ;; unreachables here.
    (array.set $something
      (array.new_default $something
        (i32.const 10)
      )
      (i32.const 0)
      (struct.new $struct)
    )
    (drop
      (ref.as_non_null
        (array.get $something
          (array.new_default $something
            (i32.const 10)
          )
          (i32.const 0)
        )
      )
    )
    ;; $something-child has nothing written to it, but its parent does. Still,
    ;; with exact type info that does not confuse us, and we can optimize to an
    ;; unreachable.
    (drop
      (ref.as_non_null
        (array.get $something-child
          (ref.cast (ref $something-child)
            (array.new_default $something
              (i32.const 10)
            )
          )
          (i32.const 0)
        )
      )
    )
  )
)

;; A big chain, from an allocation that passes through many locations along the
;; way before it is used. Nothing here can be optimized.
(module
  ;; CHECK:      (type $storage (struct (field (mut anyref))))

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

  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))

  (type $storage (struct (field (mut (ref null any)))))

  ;; CHECK:      (type $3 (func (param anyref) (result anyref)))

  ;; CHECK:      (global $x (mut anyref) (ref.null none))
  (global $x (mut (ref null any)) (ref.null any))

  ;; CHECK:      (func $foo (type $1)
  ;; CHECK-NEXT:  (local $x anyref)
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $x
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (struct.get $storage 0
  ;; CHECK-NEXT:     (struct.new $storage
  ;; CHECK-NEXT:      (call $pass-through
  ;; CHECK-NEXT:       (global.get $x)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo
    (local $x (ref null any))
    ;; Allocate a non-null value and pass it through a local.
    (local.set $x
      (struct.new $struct)
    )
    ;; Pass it through a global.
    (global.set $x
      (local.get $x)
    )
    ;; Pass it through a call, then write it to a struct, then read it from
    ;; there, and coerce to non-null which we would optimize if the value were
    ;; only a null. But it is not a null, and no optimizations happen here.
    (drop
      (ref.as_non_null
        (struct.get $storage 0
          (struct.new $storage
            (call $pass-through
              (global.get $x)
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $pass-through (type $3) (param $x anyref) (result anyref)
  ;; CHECK-NEXT:  (local.get $x)
  ;; CHECK-NEXT: )
  (func $pass-through (param $x (ref null any)) (result (ref null any))
    (local.get $x)
  )
)

;; As above, but the chain is turned into a loop, replacing the initial
;; allocation with a get from the end. We can optimize such cycles.
(module
  (type $struct (struct))

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

  ;; CHECK:      (type $storage (struct (field (mut anyref))))
  (type $storage (struct (field (mut (ref null any)))))

  ;; CHECK:      (type $2 (func (param anyref) (result anyref)))

  ;; CHECK:      (global $x (mut anyref) (ref.null none))
  (global $x (mut (ref null any)) (ref.null any))

  ;; CHECK:      (func $foo (type $0)
  ;; CHECK-NEXT:  (local $x anyref)
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $x
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.new $storage
  ;; CHECK-NEXT:      (block (result nullref)
  ;; CHECK-NEXT:       (drop
  ;; CHECK-NEXT:        (call $pass-through
  ;; CHECK-NEXT:         (ref.null none)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (ref.null none)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo
    (local $x (ref null any))
    ;; Replace the initial allocation with a read from the global. That is
    ;; written to lower down, forming a loop - a loop with no actual allocation
    ;; anywhere, so we can infer the possible values are only a null.
    (local.set $x
      (global.get $x)
    )
    (global.set $x
      (struct.get $storage 0
        (struct.new $storage
          (call $pass-through
            (local.get $x)
          )
        )
      )
    )
    (drop
      (ref.as_non_null
        (global.get $x)
      )
    )
  )

  ;; CHECK:      (func $pass-through (type $2) (param $x anyref) (result anyref)
  ;; CHECK-NEXT:  (ref.null none)
  ;; CHECK-NEXT: )
  (func $pass-through (param $x (ref null any)) (result (ref null any))
    (local.get $x)
  )
)

;; A single long chain as above, but now we break the chain in the middle by
;; adding a non-null value.
(module
  ;; CHECK:      (type $storage (struct (field (mut anyref))))

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

  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))

  (type $storage (struct (field (mut (ref null any)))))

  ;; CHECK:      (type $3 (func (param anyref) (result anyref)))

  ;; CHECK:      (global $x (mut anyref) (ref.null none))
  (global $x (mut (ref null any)) (ref.null any))

  ;; CHECK:      (func $foo (type $1)
  ;; CHECK-NEXT:  (local $x anyref)
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (global.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $x
  ;; CHECK-NEXT:   (struct.get $storage 0
  ;; CHECK-NEXT:    (struct.new $storage
  ;; CHECK-NEXT:     (call $pass-through
  ;; CHECK-NEXT:      (struct.new_default $struct)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (global.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo
    (local $x (ref null any))
    (local.set $x
      (global.get $x)
    )
    (global.set $x
      (struct.get $storage 0
        (struct.new $storage
          (call $pass-through
            ;; The only change is to allocate here instead of reading the local
            ;; $x. This causes us to not optimize anything in this function.
            (struct.new $struct)
          )
        )
      )
    )
    (drop
      (ref.as_non_null
        (global.get $x)
      )
    )
  )

  ;; CHECK:      (func $pass-through (type $3) (param $x anyref) (result anyref)
  ;; CHECK-NEXT:  (local.get $x)
  ;; CHECK-NEXT: )
  (func $pass-through (param $x (ref null any)) (result (ref null any))
    (local.get $x)
  )
)

;; Exceptions.
(module
  ;; CHECK:      (type $0 (func))

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

  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))

  ;; CHECK:      (tag $nothing (type $1) (param anyref))
  (tag $nothing (param (ref null any)))

  ;; CHECK:      (tag $something (type $1) (param anyref))
  (tag $something (param (ref null any)))

  ;; CHECK:      (tag $empty (type $0))
  (tag $empty (param))

  ;; CHECK:      (func $func (type $0)
  ;; CHECK-NEXT:  (local $0 anyref)
  ;; CHECK-NEXT:  (throw $nothing
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (try
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch $nothing
  ;; CHECK-NEXT:    (local.set $0
  ;; CHECK-NEXT:     (pop anyref)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (block (result nullref)
  ;; CHECK-NEXT:        (drop
  ;; CHECK-NEXT:         (local.get $0)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (ref.null none)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (throw $something
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (try
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch $something
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (pop anyref)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func
    ;; This tag receives no non-null value, so we can optimize the pop of it,
    ;; in the next try-catch, to an unreachable.
    (throw $nothing
      (ref.null $struct)
    )
    (try
      (do)
      (catch $nothing
        (drop
          (ref.as_non_null
            (pop (ref null any))
          )
        )
      )
    )
    ;; This tag cannot be optimized as we send it something.
    (throw $something
      (struct.new $struct)
    )
    (try
      (do)
      (catch $something
        (drop
          (ref.as_non_null
            (pop (ref null any))
          )
        )
      )
    )
  )

  ;; CHECK:      (func $empty-tag (type $0)
  ;; CHECK-NEXT:  (try $label$3
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:    (nop)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch $empty
  ;; CHECK-NEXT:    (nop)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $empty-tag
    ;; Check we do not error on catching an empty tag.
    (try $label$3
      (do
        (nop)
      )
      (catch $empty
        (nop)
      )
    )
  )

  ;; CHECK:      (func $try-results (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (try (result i32)
  ;; CHECK-NEXT:      (do
  ;; CHECK-NEXT:       (i32.const 0)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (catch $empty
  ;; CHECK-NEXT:       (i32.const 0)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (catch_all
  ;; CHECK-NEXT:       (i32.const 0)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (try (result i32)
  ;; CHECK-NEXT:    (do
  ;; CHECK-NEXT:     (i32.const 42)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (catch $empty
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (catch_all
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (try (result i32)
  ;; CHECK-NEXT:    (do
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (catch $empty
  ;; CHECK-NEXT:     (i32.const 42)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (catch_all
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (try (result i32)
  ;; CHECK-NEXT:    (do
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (catch $empty
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (catch_all
  ;; CHECK-NEXT:     (i32.const 42)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $try-results
    ;; If all values flowing out are identical, we can optimize. That is only
    ;; the case in the very first try.
    (drop
      (try (result i32)
        (do
          (i32.const 0)
        )
        (catch $empty
          (i32.const 0)
        )
        (catch_all
          (i32.const 0)
        )
      )
    )
    ;; If any of the values is changed, we cannot.
    (drop
      (try (result i32)
        (do
          (i32.const 42)
        )
        (catch $empty
          (i32.const 0)
        )
        (catch_all
          (i32.const 0)
        )
      )
    )
    (drop
      (try (result i32)
        (do
          (i32.const 0)
        )
        (catch $empty
          (i32.const 42)
        )
        (catch_all
          (i32.const 0)
        )
      )
    )
    (drop
      (try (result i32)
        (do
          (i32.const 0)
        )
        (catch $empty
          (i32.const 0)
        )
        (catch_all
          (i32.const 42)
        )
      )
    )
  )
)

;; Exceptions with a tuple
(module
  ;; CHECK:      (type $0 (func (param anyref anyref)))

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

  ;; CHECK:      (type $struct (struct))
  (type $struct (struct))

  ;; CHECK:      (tag $tag (type $0) (param anyref anyref))
  (tag $tag (param (ref null any)) (param (ref null any)))

  ;; CHECK:      (func $func (type $1)
  ;; CHECK-NEXT:  (local $0 (tuple anyref anyref))
  ;; CHECK-NEXT:  (throw $tag
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (try
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch $tag
  ;; CHECK-NEXT:    (local.set $0
  ;; CHECK-NEXT:     (pop (tuple anyref anyref))
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result nullref)
  ;; CHECK-NEXT:      (tuple.drop 2
  ;; CHECK-NEXT:       (local.get $0)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (ref.null none)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (try
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch $tag
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (tuple.extract 2 1
  ;; CHECK-NEXT:      (pop (tuple anyref anyref))
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func
    ;; This tag receives a null in the first parameter.
    (throw $tag
      (ref.null $struct)
      (struct.new $struct)
    )
    ;; Catch the first, which we can optimize to a null.
    (try
      (do)
      (catch $tag
        (drop
          (tuple.extract 2 0
            (pop (tuple (ref null any) (ref null any)))
          )
        )
      )
    )
    ;; Catch the second, which we cannot optimize.
    (try
      (do)
      (catch $tag
        (drop
          (tuple.extract 2 1
            (pop (tuple (ref null any) (ref null any)))
          )
        )
      )
    )
  )
)

(module
  ;; CHECK:      (type $"{}" (sub (struct)))
  (type $"{}" (sub (struct)))

  ;; CHECK:      (type $1 (func (result (ref $"{}"))))

  ;; CHECK:      (func $func (type $1) (result (ref $"{}"))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $block (result (ref none))
  ;; CHECK-NEXT:    (br_on_non_null $block
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $func (result (ref $"{}"))
    ;; This block can only return a null in theory (in practice, not even that -
    ;; the br will not be taken, but this pass is not smart enough to see that).
    ;; We can optimize to an unreachable here, but must be careful - we cannot
    ;; remove the block as the wasm would not validate (not unless we also
    ;; removed the br, which we don't do atm). All we will do is add an
    ;; unreachable after the block, on the outside of it (which would help other
    ;; passes do more work).
    (block $block (result (ref $"{}"))
      (br_on_non_null $block
        (ref.null $"{}")
      )
      (unreachable)
    )
  )
)

(module
  ;; CHECK:      (type $0 (func))

  ;; CHECK:      (type $A (sub (struct (field i32))))
  (type $A (sub (struct (field i32))))
  ;; CHECK:      (type $B (sub (struct (field i64))))
  (type $B (sub (struct (field i64))))
  ;; CHECK:      (type $C (sub (struct (field f32))))
  (type $C (sub (struct (field f32))))
  ;; CHECK:      (type $D (sub (struct (field f64))))
  (type $D (sub (struct (field f64))))

  ;; CHECK:      (func $many-types (type $0)
  ;; CHECK-NEXT:  (local $x anyref)
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (i64.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (struct.new $C
  ;; CHECK-NEXT:    (f32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (struct.new $D
  ;; CHECK-NEXT:    (f64.const 3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $many-types
    (local $x (ref null any))
    ;; Write 4 different types into $x. That should not confuse us, and we
    ;; should not make any changes in this function.
    (local.set $x
      (struct.new $A
        (i32.const 0)
      )
    )
    (local.set $x
      (struct.new $B
        (i64.const 1)
      )
    )
    (local.set $x
      (struct.new $C
        (f32.const 2)
      )
    )
    (local.set $x
      (struct.new $D
        (f64.const 3)
      )
    )
    (drop
      (ref.as_non_null
        (local.get $x)
      )
    )
  )
)

;; Test a vtable-like pattern. This tests ref.func values flowing into struct
;; locations being properly noticed, both from global locations (the global's
;; init) and a function ($create).
(module
  ;; CHECK:      (type $vtable-A (sub (struct (field funcref) (field funcref) (field funcref))))
  (type $vtable-A (sub (struct (field (ref null func)) (field (ref null func)) (field (ref null func)))))

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

  ;; CHECK:      (global $global-A (ref $vtable-A) (struct.new $vtable-A
  ;; CHECK-NEXT:  (ref.func $foo)
  ;; CHECK-NEXT:  (ref.null nofunc)
  ;; CHECK-NEXT:  (ref.func $foo)
  ;; CHECK-NEXT: ))
  (global $global-A (ref $vtable-A)
    (struct.new $vtable-A
      (ref.func $foo)
      (ref.null func)
      (ref.func $foo)
    )
  )

  ;; CHECK:      (elem declare func $foo $test)

  ;; CHECK:      (func $test (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $foo)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.null nofunc)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $vtable-A 2
  ;; CHECK-NEXT:    (global.get $global-A)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    ;; The first item here contains a fixed value (ref.func $foo) in both the
    ;; global init and in the function $create, which we can apply.
    (drop
      (struct.get $vtable-A 0
        (global.get $global-A)
      )
    )
    ;; The second item here contains a null in all cases, which we can also
    ;; apply.
    (drop
      (struct.get $vtable-A 1
        (global.get $global-A)
      )
    )
    ;; The third item has more than one possible value, due to the function
    ;; $create later down, so we cannot optimize.
    (drop
      (struct.get $vtable-A 2
        (global.get $global-A)
      )
    )
  )

  ;; CHECK:      (func $create (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $vtable-A
  ;; CHECK-NEXT:    (ref.func $foo)
  ;; CHECK-NEXT:    (ref.null nofunc)
  ;; CHECK-NEXT:    (ref.func $test)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $create
    (drop
      (struct.new $vtable-A
        (ref.func $foo)
        (ref.null func)
        (ref.func $test)
      )
    )
  )

  ;; CHECK:      (func $foo (type $1)
  ;; CHECK-NEXT: )
  (func $foo)
)

(module
  ;; CHECK:      (type $struct (sub (struct (field i32))))
  (type $struct (sub (struct (field i32))))

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

  ;; CHECK:      (func $test (type $1)
  ;; CHECK-NEXT:  (local $ref (ref null $struct))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (block (result (ref (exact $struct)))
  ;; CHECK-NEXT:    (block (result (ref (exact $struct)))
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 42)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get $struct 0
  ;; CHECK-NEXT:      (local.get $ref)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 42)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    (local $ref (ref null $struct))
    ;; Regression test for an assertion firing in this case. We should properly
    ;; handle the multiple intermediate blocks here, allowing us to optimize the
    ;; get below to a 42.
    (local.set $ref
      (block (result (ref $struct))
        (block (result (ref $struct))
          (struct.new $struct
            (i32.const 42)
          )
        )
      )
    )
    (drop
      (struct.get $struct 0
        (local.get $ref)
      )
    )
  )
)

;; Casts.
(module
  ;; CHECK:      (type $struct (sub (struct (field i32))))
  (type $struct (sub (struct (field i32))))
  ;; CHECK:      (type $substruct (sub $struct (struct (field i32) (field i32))))
  (type $substruct (sub $struct (struct (field i32) (field i32))))
  ;; CHECK:      (type $2 (func))

  ;; CHECK:      (type $subsubstruct (sub $substruct (struct (field i32) (field i32) (field i32))))
  (type $subsubstruct (sub $substruct (struct (field i32) (field i32) (field i32))))

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

  ;; CHECK:      (type $other (sub (struct)))
  (type $other (sub (struct)))

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

  ;; CHECK:      (type $7 (func (param i32 (ref null $struct) (ref null $struct) (ref null $other) (ref $struct) (ref $struct) (ref $other))))

  ;; CHECK:      (type $8 (func (result (ref eq))))

  ;; CHECK:      (import "a" "b" (func $import (type $6) (result i32)))
  (import "a" "b" (func $import (result i32)))

  ;; CHECK:      (export "test-cones" (func $test-cones))

  ;; CHECK:      (export "ref.test-inexact" (func $ref.test-inexact))

  ;; CHECK:      (export "ref.eq-zero" (func $ref.eq-zero))

  ;; CHECK:      (export "ref.eq-unknown" (func $ref.eq-unknown))

  ;; CHECK:      (export "ref.eq-cone" (func $ref.eq-cone))

  ;; CHECK:      (export "local-no" (func $ref.eq-local-no))

  ;; CHECK:      (func $test (type $2)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref (exact $substruct))
  ;; CHECK-NEXT:    (struct.new $substruct
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref (exact $subsubstruct))
  ;; CHECK-NEXT:    (struct.new $subsubstruct
  ;; CHECK-NEXT:     (i32.const 3)
  ;; CHECK-NEXT:     (i32.const 4)
  ;; CHECK-NEXT:     (i32.const 5)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    ;; The cast here will fail, and the ref.cast null allows nothing through, so we
    ;; can emit an unreachable here.
    (drop
      (ref.cast (ref $substruct)
        (struct.new $struct
          (i32.const 0)
        )
      )
    )
    ;; This cast of a type to itself can succeed (in fact, it will), so we make
    ;; no changes here.
    (drop
      (ref.cast (ref $substruct)
        (struct.new $substruct
          (i32.const 1)
          (i32.const 2)
        )
      )
    )
    ;; This cast of a subtype will also succeed. As above, we make no changes.
    (drop
      (ref.cast (ref $substruct)
        (struct.new $subsubstruct
          (i32.const 3)
          (i32.const 4)
          (i32.const 5)
        )
      )
    )
  )

  ;; CHECK:      (func $test-nulls (type $2)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast nullref
  ;; CHECK-NEXT:      (block (result nullref)
  ;; CHECK-NEXT:       (drop
  ;; CHECK-NEXT:        (call $import)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (ref.null none)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast nullref
  ;; CHECK-NEXT:      (select (result i31ref)
  ;; CHECK-NEXT:       (ref.null none)
  ;; CHECK-NEXT:       (ref.i31
  ;; CHECK-NEXT:        (i32.const 0)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (call $import)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null (exact $struct))
  ;; CHECK-NEXT:    (select (result (ref null (exact $struct)))
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 6)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (call $import)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test-nulls
    ;; Only a null can flow through the cast, which we can infer for the value
    ;; of the cast.
    (drop
      (ref.cast (ref null $struct)
        (select
          (ref.null $struct)
          (ref.null $struct)
          (call $import)
        )
      )
    )
    ;; A null or an i31 will reach the cast; only the null can actually pass
    ;; through (an i31 would fail the cast). Given that, we can infer a null for
    ;; the value of the cast. (The cast itself will also be turned into a cast
    ;; to null, but it is dropped right before we return a null, so that has no
    ;; benefit in this case.)
    (drop
      (ref.cast (ref null $struct)
        (select
          (ref.null $struct)
          (ref.i31 (i32.const 0))
          (call $import)
        )
      )
    )
    ;; A null or a $struct may arrive, and so we cannot do anything here.
    (drop
      (ref.cast (ref null $struct)
        (select
          (ref.null $struct)
          (struct.new $struct
            (i32.const 6)
          )
          (call $import)
        )
      )
    )
  )

  ;; CHECK:      (func $test-cones (type $4) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null (exact $struct))
  ;; CHECK-NEXT:    (select (result (ref null (exact $struct)))
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $struct)
  ;; CHECK-NEXT:    (select (result (ref $struct))
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (struct.new $substruct
  ;; CHECK-NEXT:      (i32.const 2)
  ;; CHECK-NEXT:      (i32.const 3)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $substruct)
  ;; CHECK-NEXT:    (select (result (ref $struct))
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 4)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (struct.new $substruct
  ;; CHECK-NEXT:      (i32.const 5)
  ;; CHECK-NEXT:      (i32.const 6)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test-cones (export "test-cones") (param $x i32)
    ;; The input to the ref.cast null is potentially null, so we cannot infer here.
    (drop
      (ref.cast (ref null $struct)
        (select
          (struct.new $struct
            (i32.const 0)
          )
          (ref.null any)
          (local.get $x)
        )
      )
    )
    ;; The input to the ref.cast is either $struct or $substruct, both of which
    ;; work, so we cannot optimize anything here away.
    (drop
      (ref.cast (ref $struct)
        (select
          (struct.new $struct
            (i32.const 1)
          )
          (struct.new $substruct
            (i32.const 2)
            (i32.const 3)
          )
          (local.get $x)
        )
      )
    )
    ;; As above, but now we test with $substruct, so one possibility fails and
    ;; one succeeds. We cannot infer here either.
    (drop
      (ref.cast (ref $substruct)
        (select
          (struct.new $struct
            (i32.const 4)
          )
          (struct.new $substruct
            (i32.const 5)
            (i32.const 6)
          )
          (local.get $x)
        )
      )
    )
    ;; Two possible types, both are supertypes, so neither is a subtype, and we
    ;; can infer an unreachable. The combination of these two is a cone from
    ;; $struct of depth 1, which does not overlap with $subsubstruct.
    (drop
      (ref.cast (ref $subsubstruct)
        (select
          (struct.new $struct
            (i32.const 7)
          )
          (struct.new $substruct
            (i32.const 8)
            (i32.const 9)
          )
          (local.get $x)
        )
      )
    )
  )

  ;; CHECK:      (func $ref.test-exact (type $2)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref.test-exact
    ;; This cast will fail: we know the exact type of the reference, and it is
    ;; not a subtype.
    (drop
      (ref.test (ref $substruct)
        (struct.new $struct
          (i32.const 0)
        )
      )
    )
    ;; Casting a thing to itself must succeed.
    (drop
      (ref.test (ref $substruct)
        (struct.new $substruct
          (i32.const 1)
          (i32.const 2)
        )
      )
    )
    ;; Casting a thing to a supertype must succeed.
    (drop
      (ref.test (ref $substruct)
        (struct.new $subsubstruct
          (i32.const 3)
          (i32.const 4)
          (i32.const 5)
        )
      )
    )
  )

  ;; CHECK:      (func $ref.test-inexact (type $4) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref (exact $struct))
  ;; CHECK-NEXT:    (select (result (ref null (exact $struct)))
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $substruct)
  ;; CHECK-NEXT:    (select (result (ref $struct))
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 4)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (struct.new $substruct
  ;; CHECK-NEXT:      (i32.const 5)
  ;; CHECK-NEXT:      (i32.const 6)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref.test-inexact (export "ref.test-inexact") (param $x i32)
    ;; The input to the ref.test is potentially null, so we cannot infer here.
    (drop
      (ref.test (ref $struct)
        (select
          (struct.new $struct
            (i32.const 0)
          )
          (ref.null any)
          (local.get $x)
        )
      )
    )
    ;; The input to the ref.test is either $struct or $substruct, both of which
    ;; work, so here we can infer a 1. We do so using a cone type: the
    ;; combination of those two types is a cone on $struct of depth 1, and that
    ;; cone is 100% a subtype of $struct, so the test will succeed.
    (drop
      (ref.test (ref $struct)
        (select
          (struct.new $struct
            (i32.const 1)
          )
          (struct.new $substruct
            (i32.const 2)
            (i32.const 3)
          )
          (local.get $x)
        )
      )
    )
    ;; As above, but now we test with $substruct, so one possibility fails and
    ;; one succeeds. We cannot infer here.
    (drop
      (ref.test (ref $substruct)
        (select
          (struct.new $struct
            (i32.const 4)
          )
          (struct.new $substruct
            (i32.const 5)
            (i32.const 6)
          )
          (local.get $x)
        )
      )
    )
    ;; Two possible types, both are supertypes, so neither is a subtype, and we
    ;; can infer a 0. The combination of these two is a cone from $struct of
    ;; depth 1, which does not overlap with $subsubstruct.
    (drop
      (ref.test (ref $subsubstruct)
        (select
          (struct.new $struct
            (i32.const 7)
          )
          (struct.new $substruct
            (i32.const 8)
            (i32.const 9)
          )
          (local.get $x)
        )
      )
    )
  )

  ;; CHECK:      (func $ref.eq-zero (type $2)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref.eq-zero (export "ref.eq-zero")
    ;; We do not track specific references, so only the types can be used here.
    ;; Using the types, we can infer that two different ExactTypes cannot
    ;; contain the same reference, so we infer a 0.
    (drop
      (ref.eq
        (struct.new $struct
          (i32.const 1)
        )
        (struct.new $substruct
          (i32.const 2)
          (i32.const 3)
        )
      )
    )
    ;; A null and a non-null reference cannot be identical, so we infer 0.
    (drop
      (ref.eq
        (ref.null $struct)
        (struct.new $struct
          (i32.const 5)
        )
      )
    )
    (drop
      (ref.eq
        (struct.new $struct
          (i32.const 5)
        )
        (ref.null $struct)
      )
    )
  )

  ;; CHECK:      (func $ref.eq-unknown (type $7) (param $x i32) (param $struct (ref null $struct)) (param $struct2 (ref null $struct)) (param $other (ref null $other)) (param $nn-struct (ref $struct)) (param $nn-struct2 (ref $struct)) (param $nn-other (ref $other))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (struct.new $struct
  ;; CHECK-NEXT:     (i32.const 4)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (struct.new $struct
  ;; CHECK-NEXT:     (i32.const 5)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:    (local.get $other)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:    (local.get $struct2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:    (local.get $nn-struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.get $nn-struct)
  ;; CHECK-NEXT:    (local.get $nn-struct2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref.eq-unknown (export "ref.eq-unknown") (param $x i32) (param $struct (ref null $struct)) (param $struct2 (ref null $struct)) (param $other (ref null $other)) (param $nn-struct (ref $struct)) (param $nn-struct2 (ref $struct)) (param $nn-other (ref $other))
    ;; Here we cannot infer as the type is identical. (Though, if we used more
    ;; than the type, we could see they cannot be identical.)
    (drop
      (ref.eq
        (struct.new $struct
          (i32.const 4)
        )
        (struct.new $struct
          (i32.const 5)
        )
      )
    )
    ;; These nulls are identical, so we could infer 1, but we leave that for
    ;; other passes, and do not infer here.
    (drop
      (ref.eq
        (ref.null $struct)
        (ref.null $struct)
      )
    )
    ;; When nulls are possible, we cannot infer anything (with or without the
    ;; same type on both sides).
    (drop
      (ref.eq
        (local.get $struct)
        (local.get $other)
      )
    )
    (drop
      (ref.eq
        (local.get $struct)
        (local.get $struct2)
      )
    )
    ;; A null is only possible on one side, but the same non-null value could be
    ;; on both.
    (drop
      (ref.eq
        (local.get $struct)
        (local.get $nn-struct)
      )
    )
    ;; The type is identical, and non-null, but we don't know if the value is
    ;; the same or not.
    (drop
      (ref.eq
        (local.get $nn-struct)
        (local.get $nn-struct2)
      )
    )
    ;; Non-null on both sides, and incompatible types. We can infer 0 here.
    (drop
      (ref.eq
        (local.get $nn-struct)
        (local.get $nn-other)
      )
    )
    ;; We can ignore unreachable code.
    (drop
      (ref.eq
        (ref.null $struct)
        (unreachable)
      )
    )
    ;; The called function here traps and never returns an actual value, which
    ;; will lead to an unreachable emitted right after the call. We should not
    ;; prevent that from happening: an unreachable must be emitted (we will also
    ;; emit an i32.const 0, which will never be reached, and not cause issues).
    (drop
      (ref.eq
        (ref.null $struct)
        (call $unreachable)
      )
    )
  )

  ;; CHECK:      (func $ref.eq-cone (type $4) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (struct.new $substruct
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (select (result (ref $struct))
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 3)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (struct.new $subsubstruct
  ;; CHECK-NEXT:      (i32.const 4)
  ;; CHECK-NEXT:      (i32.const 5)
  ;; CHECK-NEXT:      (i32.const 6)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (struct.new $substruct
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (select (result (ref $struct))
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 3)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (struct.new $substruct
  ;; CHECK-NEXT:      (i32.const 4)
  ;; CHECK-NEXT:      (i32.const 5)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (struct.new $substruct
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (select (result (ref $substruct))
  ;; CHECK-NEXT:     (struct.new $substruct
  ;; CHECK-NEXT:      (i32.const 3)
  ;; CHECK-NEXT:      (i32.const 4)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (struct.new $subsubstruct
  ;; CHECK-NEXT:      (i32.const 5)
  ;; CHECK-NEXT:      (i32.const 6)
  ;; CHECK-NEXT:      (i32.const 7)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (struct.new $substruct
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (select (result (ref (exact $substruct)))
  ;; CHECK-NEXT:     (struct.new $substruct
  ;; CHECK-NEXT:      (i32.const 3)
  ;; CHECK-NEXT:      (i32.const 4)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (struct.new $substruct
  ;; CHECK-NEXT:      (i32.const 5)
  ;; CHECK-NEXT:      (i32.const 6)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref.eq-cone (export "ref.eq-cone") (param $x i32)
    ;; One side has two possible types, so we have a cone there. This cone is
    ;; of subtypes of the other type, which is exact, so we cannot intersect
    ;; here and we infer a 0.
    (drop
      (ref.eq
        (struct.new $struct
          (i32.const 1)
        )
        (select
          (struct.new $substruct
            (i32.const 2)
            (i32.const 3)
          )
          (struct.new $subsubstruct
            (i32.const 4)
            (i32.const 5)
            (i32.const 6)
          )
          (local.get $x)
        )
      )
    )
    ;; Now the cone is large enough, so there might be an intersection, and we
    ;; do not optimize (the cone of $struct and $subsubstruct contains
    ;; $substruct which is in the middle).
    (drop
      (ref.eq
        (struct.new $substruct
          (i32.const 1)
          (i32.const 2)
        )
        (select
          (struct.new $struct
            (i32.const 3)
          )
          (struct.new $subsubstruct
            (i32.const 4)
            (i32.const 5)
            (i32.const 6)
          )
          (local.get $x)
        )
      )
    )
    (drop
      (ref.eq
        (struct.new $substruct
          (i32.const 1)
          (i32.const 2)
        )
        (select
          (struct.new $struct
            (i32.const 3)
          )
          (struct.new $substruct ;; As above, but with this changed. We still
            (i32.const 4)        ;; cannot optimize.
            (i32.const 5)
          )
          (local.get $x)
        )
      )
    )
    (drop
      (ref.eq
        (struct.new $substruct
          (i32.const 1)
          (i32.const 2)
        )
        (select
          (struct.new $substruct ;; As above, but with this changed. We still
            (i32.const 3)        ;; cannot optimize.
            (i32.const 4)
          )
          (struct.new $subsubstruct
            (i32.const 5)
            (i32.const 6)
            (i32.const 7)
          )
          (local.get $x)
        )
      )
    )
    (drop
      (ref.eq
        (struct.new $substruct
          (i32.const 1)
          (i32.const 2)
        )
        (select
          (struct.new $substruct
            (i32.const 3)
            (i32.const 4)
          )
          (struct.new $substruct ;; As above, but with this changed. We still
            (i32.const 5)        ;; cannot optimize (here the type is actually
            (i32.const 6)        ;; exact, despite the select).
          )
          (local.get $x)
        )
      )
    )
  )

  ;; CHECK:      (func $unreachable (type $8) (result (ref eq))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $unreachable (result (ref eq))
    (unreachable)
  )

  ;; CHECK:      (func $ref.eq-updates (type $2)
  ;; CHECK-NEXT:  (local $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (block (result nullref)
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $import)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref.eq-updates
    (local $x (ref null eq))
    ;; The local.get will be optimized to a ref.null. After that we will leave
    ;; the ref.eq as it is. This guards against a possible bug of us not
    ;; setting the contents of the new ref.null expression just created: the
    ;; parent ref.eq will query the contents right after adding that expression,
    ;; and the contents must be set or else we'll think nothing is possible
    ;; there.
    ;;
    ;; (We could optimize ref.eq of two nulls to 1, but we leave that for other
    ;; passes.)
    (drop
      (ref.eq
        (ref.null eq)
        (local.get $x)
      )
    )
    ;; Another situation we need to be careful with effects of updates. Here
    ;; we have a block whose result we can infer to a null, but that does not
    ;; let us optimize the ref.eq, and we also must be careful to not drop side
    ;; effects - the call must remain.
    (drop
      (ref.eq
        (block (result eqref)
          (drop
            (call $import)
          )
          (ref.null $struct)
        )
        (ref.null $struct)
      )
    )
  )

  ;; CHECK:      (func $ref.eq-local-no (type $4) (param $x i32)
  ;; CHECK-NEXT:  (local $ref (ref $struct))
  ;; CHECK-NEXT:  (local $ref-null (ref null $struct))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $ref-null
  ;; CHECK-NEXT:     (local.get $ref)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:    (local.get $ref-null)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref.eq-local-no (export "local-no") (param $x i32)
    (local $ref (ref $struct))
    (local $ref-null (ref null $struct))
    ;; Always set the non-nullable ref, but only sometimes set the nullable.
    (local.set $ref
      (struct.new $struct
        (i32.const 0)
      )
    )
    (if
      (local.get $x)
      (then
        (local.set $ref-null
          (local.get $ref)
        )
      )
    )
    ;; If the |if| executed they are equal, but otherwise not, so we can't
    ;; optimize.
    (drop
      (ref.eq
        (local.get $ref)
        (local.get $ref-null)
      )
    )
  )
)

;; Test ref.eq on globals.
(module
  ;; CHECK:      (type $A (sub (struct (field i32))))
  (type $A (sub (struct (field i32))))
  (type $B (sub $A (struct (field i32))))

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

  ;; CHECK:      (global $a (ref $A) (struct.new $A
  ;; CHECK-NEXT:  (i32.const 0)
  ;; CHECK-NEXT: ))
  (global $a (ref $A) (struct.new $A
    (i32.const 0)
  ))

  ;; CHECK:      (global $a-other (ref $A) (struct.new $A
  ;; CHECK-NEXT:  (i32.const 1)
  ;; CHECK-NEXT: ))
  (global $a-other (ref $A) (struct.new $A
    (i32.const 1)
  ))

  ;; CHECK:      (global $a-copy (ref $A) (global.get $a))
  (global $a-copy (ref $A) (global.get $a))

  ;; CHECK:      (global $a-mut (mut (ref $A)) (struct.new $A
  ;; CHECK-NEXT:  (i32.const 2)
  ;; CHECK-NEXT: ))
  (global $a-mut (mut (ref $A)) (struct.new $A
    (i32.const 2)
  ))

  ;; CHECK:      (global $a-mut-copy (mut (ref $A)) (global.get $a))
  (global $a-mut-copy (mut (ref $A)) (global.get $a))

  ;; CHECK:      (global $a-mut-copy-written (mut (ref $A)) (global.get $a))
  (global $a-mut-copy-written (mut (ref $A)) (global.get $a))

  ;; CHECK:      (func $compare-a (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (global.get $a)
  ;; CHECK-NEXT:    (global.get $a)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (global.get $a)
  ;; CHECK-NEXT:    (global.get $a)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (global.get $a)
  ;; CHECK-NEXT:    (global.get $a)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (global.get $a)
  ;; CHECK-NEXT:    (global.get $a-other)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (global.get $a)
  ;; CHECK-NEXT:    (global.get $a-mut)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $a-mut-copy-written
  ;; CHECK-NEXT:   (global.get $a-other)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (global.get $a)
  ;; CHECK-NEXT:    (global.get $a-mut-copy-written)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $compare-a
    ;; Comparisons of $a to everything else.
    ;;
    ;; GUFA does not compute the results of these yet, as it leaves it to other
    ;; passes. This test guards against us doing anything unexpected here.
    ;;
    ;; What we do change here is update a copied global to the original,
    ;; so $a-copy will turn into $a (because that is the only value it can
    ;; contain). That should happen for the first three only. (For the 3rd, it
    ;; works even though it is mutable, since there is only a single write
    ;; anywhere.)
    (drop
      (ref.eq
        (global.get $a)
        (global.get $a)
      )
    )
    (drop
      (ref.eq
        (global.get $a)
        (global.get $a-copy)
      )
    )
    (drop
      (ref.eq
        (global.get $a)
        (global.get $a-mut-copy)
      )
    )
    (drop
      (ref.eq
        (global.get $a)
        (global.get $a-other)
      )
    )
    (drop
      (ref.eq
        (global.get $a)
        (global.get $a-mut)
      )
    )
    (global.set $a-mut-copy-written
      (global.get $a-other)
    )
    (drop
      (ref.eq
        (global.get $a)
        (global.get $a-mut-copy-written)
      )
    )
  )
)

(module
  (type $A (sub (struct (field i32))))
  (type $B (sub (struct (ref $A))))
  (type $C (sub (struct (ref $B))))

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

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 42)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    ;; Test nested struct.get operations. We can optimize all this into the
    ;; constant 42.
    (drop
      (struct.get $A 0
        (struct.get $B 0
          (struct.get $C 0
            (struct.new $C
              (struct.new $B
                (struct.new $A
                  (i32.const 42)
                )
              )
            )
          )
        )
      )
    )
  )
)

(module
  ;; CHECK:      (type $A (sub (struct (field i32))))
  (type $A (sub (struct (field i32))))
  ;; CHECK:      (type $B (sub (struct (field (ref $A)))))
  (type $B (sub (struct (ref $A))))
  ;; CHECK:      (type $C (sub (struct (field (ref $B)))))
  (type $C (sub (struct (ref $B))))

  ;; CHECK:      (type $3 (func (result i32)))

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

  ;; CHECK:      (import "a" "b" (func $import (type $3) (result i32)))
  (import "a" "b" (func $import (result i32)))

  ;; CHECK:      (func $test (type $4)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 0
  ;; CHECK-NEXT:    (struct.get $B 0
  ;; CHECK-NEXT:     (struct.get $C 0
  ;; CHECK-NEXT:      (struct.new $C
  ;; CHECK-NEXT:       (struct.new $B
  ;; CHECK-NEXT:        (struct.new $A
  ;; CHECK-NEXT:         (call $import)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    ;; As above, but now call an import for the i32; we cannot optimize.
    (drop
      (struct.get $A 0
        (struct.get $B 0
          (struct.get $C 0
            (struct.new $C
              (struct.new $B
                (struct.new $A
                  (call $import)
                )
              )
            )
          )
        )
      )
    )
  )
)

;; ref.as* test.
(module
  ;; CHECK:      (type $A (sub (struct (field i32))))
  (type $A (sub (struct (field i32))))
  ;; CHECK:      (type $B (sub $A (struct (field i32) (field f64))))
  (type $B (sub $A (struct (field i32) (field f64))))

  ;; CHECK:      (type $2 (func (result i32)))

  ;; CHECK:      (type $3 (func (result (ref $B))))

  ;; CHECK:      (import "a" "b" (func $import (type $2) (result i32)))
  (import "a" "b" (func $import (result i32)))

  ;; CHECK:      (func $foo (type $3) (result (ref $B))
  ;; CHECK-NEXT:  (local $A (ref null $A))
  ;; CHECK-NEXT:  (ref.cast (ref (exact $B))
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (local.tee $A
  ;; CHECK-NEXT:     (struct.new $B
  ;; CHECK-NEXT:      (i32.const 42)
  ;; CHECK-NEXT:      (f64.const 13.37)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo (result (ref $B))
    (local $A (ref null $A))

    ;; Read the following from the most nested comment first.

    (ref.cast (ref $B) ;; if we mistakenly think this contains content of
                 ;; type $A, it would trap, but it should not, and we
                 ;; have nothing to optimize here
      (ref.as_non_null ;; also $B, based on the child's *contents* (not type!)
        (local.tee $A ;; flows out a $B, but has type $A
          (struct.new $B ;; returns a $B
            (i32.const 42)
            (f64.const 13.37)
          )
        )
      )
    )
  )
)

(module
  ;; CHECK:      (type $A (sub (struct (field i32))))
  (type $A (sub (struct (field i32))))
  ;; CHECK:      (type $1 (func (result i32)))

  ;; CHECK:      (type $B (sub $A (struct (field i32) (field i32))))
  (type $B (sub $A (struct (field i32) (field i32))))
  ;; CHECK:      (func $0 (type $1) (result i32)
  ;; CHECK-NEXT:  (local $ref (ref null $A))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get $A 0
  ;; CHECK-NEXT:      (local.get $ref)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (i32.const 0)
  ;; CHECK-NEXT: )
  (func $0 (result i32)
    (local $ref (ref null $A))
    (local.set $ref
      (struct.new $B
        (i32.const 0)
        (i32.const 1)
      )
    )
    ;; This struct.get has a reference of type $A, but we can infer the type
    ;; present in the reference must actually be a $B, and $B precisely - no
    ;; sub or supertypes. So we can infer a value of 0.
    ;;
    ;; A possible bug that this is a regression test for is a confusion between
    ;; the type of the content and the declared type. If we mixed them up and
    ;; thought this must be precisely an $A and not a $B then we'd emit an
    ;; unreachable here (since no $A is ever allocated).
    (struct.get $A 0
      (local.get $ref)
    )
  )
)

;; array.copy between types.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $bytes (array (mut anyref)))
    (type $bytes (array (mut anyref)))
    ;; CHECK:       (type $chars (array (mut anyref)))
    (type $chars (array (mut anyref)))
  )

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

  ;; CHECK:      (func $test (type $2)
  ;; CHECK-NEXT:  (local $bytes (ref null $bytes))
  ;; CHECK-NEXT:  (local $chars (ref null $chars))
  ;; CHECK-NEXT:  (local.set $bytes
  ;; CHECK-NEXT:   (array.new_fixed $bytes 1
  ;; CHECK-NEXT:    (ref.i31
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $chars
  ;; CHECK-NEXT:   (array.new_fixed $chars 1
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (array.copy $chars $bytes
  ;; CHECK-NEXT:   (local.get $chars)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (local.get $bytes)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.get $bytes
  ;; CHECK-NEXT:    (local.get $bytes)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.get $chars
  ;; CHECK-NEXT:    (local.get $chars)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    (local $bytes (ref null $bytes))
    (local $chars (ref null $chars))

    ;; Write something to $bytes, but just a null to $chars. But then do a copy
    ;; which means two things are possible in $chars, and we can't optimize
    ;; there.
    (local.set $bytes
      (array.new_fixed $bytes 1
        (ref.i31 (i32.const 0))
      )
    )
    (local.set $chars
      (array.new_fixed $chars 1
        (ref.null any)
      )
    )
    (array.copy $chars $bytes
      (local.get $chars)
      (i32.const 0)
      (local.get $bytes)
      (i32.const 0)
      (i32.const 1)
    )
    (drop
      (array.get $bytes
        (local.get $bytes)
        (i32.const 0)
      )
    )
    (drop
      (array.get $chars
        (local.get $chars)
        (i32.const 0)
      )
    )
  )
)

;; As above, but with a copy in the opposite direction. Now $chars has a single
;; value (a null) which we can optimize, but $bytes has two values and we
;; cannot optimize there.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $bytes (array (mut anyref)))
    (type $bytes (array (mut anyref)))
    ;; CHECK:       (type $chars (array (mut anyref)))
    (type $chars (array (mut anyref)))
  )

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

  ;; CHECK:      (func $test (type $2)
  ;; CHECK-NEXT:  (local $bytes (ref null $bytes))
  ;; CHECK-NEXT:  (local $chars (ref null $chars))
  ;; CHECK-NEXT:  (local.set $bytes
  ;; CHECK-NEXT:   (array.new_fixed $bytes 1
  ;; CHECK-NEXT:    (ref.i31
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $chars
  ;; CHECK-NEXT:   (array.new_fixed $chars 1
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (array.copy $bytes $chars
  ;; CHECK-NEXT:   (local.get $bytes)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (local.get $chars)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.get $bytes
  ;; CHECK-NEXT:    (local.get $bytes)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (array.get $chars
  ;; CHECK-NEXT:      (local.get $chars)
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    (local $bytes (ref null $bytes))
    (local $chars (ref null $chars))
    (local.set $bytes
      (array.new_fixed $bytes 1
        (ref.i31 (i32.const 0))
      )
    )
    (local.set $chars
      (array.new_fixed $chars 1
        (ref.null any)
      )
    )
    (array.copy $bytes $chars
      (local.get $bytes)
      (i32.const 0)
      (local.get $chars)
      (i32.const 0)
      (i32.const 1)
    )
    (drop
      (array.get $bytes
        (local.get $bytes)
        (i32.const 0)
      )
    )
    (drop
      (array.get $chars
        (local.get $chars)
        (i32.const 0)
      )
    )
  )
)

;; Basic tests for all instructions appearing in possible-contents.cpp but not
;; already shown above. If we forgot to add the proper links to any of them,
;; they might appear as if no content were possible there, and we'd emit an
;; unreachable. That should not happen anywhere here.
(module
  (type $A (sub (struct)))

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

  ;; CHECK:      (type $B (array (mut anyref)))
  (type $B (array (mut anyref)))

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

  ;; CHECK:      (type $3 (func (param (ref $B))))

  ;; CHECK:      (memory $0 10)

  ;; CHECK:      (table $t 0 externref)

  ;; CHECK:      (tag $e-i32 (type $2) (param i32))
  (tag $e-i32 (param i32))

  (memory $0 10)

  (table $t 0 externref)

  ;; CHECK:      (func $br_table (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block $A (result i32)
  ;; CHECK-NEXT:      (br_table $A $A
  ;; CHECK-NEXT:       (i32.const 1)
  ;; CHECK-NEXT:       (i32.const 2)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $br_table
    (drop
      ;; The value 1 can be inferred here.
      (block $A (result i32)
        (br_table $A $A
          (i32.const 1)
          (i32.const 2)
        )
      )
    )
  )

  ;; CHECK:      (func $memory (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.load
  ;; CHECK-NEXT:    (i32.const 5)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.atomic.rmw.add
  ;; CHECK-NEXT:    (i32.const 5)
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.atomic.rmw.cmpxchg
  ;; CHECK-NEXT:    (i32.const 5)
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:    (i32.const 15)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (memory.atomic.wait32
  ;; CHECK-NEXT:    (i32.const 5)
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:    (i64.const 15)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (memory.atomic.notify
  ;; CHECK-NEXT:    (i32.const 5)
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (memory.size)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (memory.grow
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $memory
    (drop
      (i32.load
        (i32.const 5)
      )
    )
    (drop
      (i32.atomic.rmw.add
        (i32.const 5)
        (i32.const 10)
      )
    )
    (drop
      (i32.atomic.rmw.cmpxchg
        (i32.const 5)
        (i32.const 10)
        (i32.const 15)
      )
    )
    (drop
      (memory.atomic.wait32
        (i32.const 5)
        (i32.const 10)
        (i64.const 15)
      )
    )
    (drop
      (memory.atomic.notify
        (i32.const 5)
        (i32.const 10)
      )
    )
    (drop
      (memory.size)
    )
    (drop
      (memory.grow
        (i32.const 1)
      )
    )
  )

  ;; CHECK:      (func $simd (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i8x16.extract_lane_s 0
  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i8x16.replace_lane 0
  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
  ;; CHECK-NEXT:    (i32.const 3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i8x16.shuffle 0 17 2 19 4 21 6 23 8 25 10 27 12 29 14 31
  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
  ;; CHECK-NEXT:    (v128.const i32x4 0x00000003 0x00000000 0x00000004 0x00000000)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (v128.bitselect
  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
  ;; CHECK-NEXT:    (v128.const i32x4 0x00000003 0x00000000 0x00000004 0x00000000)
  ;; CHECK-NEXT:    (v128.const i32x4 0x00000005 0x00000000 0x00000006 0x00000000)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i8x16.shr_s
  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
  ;; CHECK-NEXT:    (i32.const 3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (v128.load8_splat
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (v128.load8_lane 0
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (v128.const i32x4 0x00000001 0x00000000 0x00000002 0x00000000)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $simd
    (drop
      (i8x16.extract_lane_s 0
        (v128.const i64x2 1 2)
      )
    )
    (drop
      (i8x16.replace_lane 0
        (v128.const i64x2 1 2)
        (i32.const 3)
      )
    )
    (drop
      (i8x16.shuffle 0 17 2 19 4 21 6 23 8 25 10 27 12 29 14 31
        (v128.const i64x2 1 2)
        (v128.const i64x2 3 4)
      )
    )
    (drop
      (v128.bitselect
        (v128.const i64x2 1 2)
        (v128.const i64x2 3 4)
        (v128.const i64x2 5 6)
      )
    )
    (drop
      (i8x16.shr_s
        (v128.const i64x2 1 2)
        (i32.const 3)
      )
    )
    (drop
      (v128.load8_splat
        (i32.const 0)
      )
    )
    (drop
      (v128.load8_lane 0
        (i32.const 0)
        (v128.const i64x2 1 2)
      )
    )
  )

  ;; CHECK:      (func $unary (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.eqz
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unary
    (drop
      (i32.eqz
        (i32.const 1)
      )
    )
  )

  ;; CHECK:      (func $binary (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.add
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $binary
    (drop
      (i32.add
        (i32.const 1)
        (i32.const 2)
      )
    )
  )

  ;; CHECK:      (func $table (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (table.get $t
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (table.size $t)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (table.grow $t
  ;; CHECK-NEXT:    (ref.null noextern)
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $table
    (drop
      (table.get $t
        (i32.const 1)
      )
    )
    (drop
      (table.size $t)
    )
    (drop
      (table.grow $t
        (ref.null extern)
        (i32.const 1)
      )
    )
  )

  ;; CHECK:      (func $i31 (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i31.get_s
  ;; CHECK-NEXT:    (ref.i31
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $i31
    (drop
      (i31.get_s
        (ref.i31
          (i32.const 0)
        )
      )
    )
  )

  ;; CHECK:      (func $arrays (type $3) (param $B (ref $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.len
  ;; CHECK-NEXT:    (array.new_fixed $B 2
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $arrays (param $B (ref $B))
    (drop
      (array.len
        (array.new_fixed $B 2
          (ref.null none)
          (ref.null none)
        )
      )
    )
  )

  ;; CHECK:      (func $rethrow (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (try $l0
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:    (throw $e-i32
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch $e-i32
  ;; CHECK-NEXT:    (local.set $0
  ;; CHECK-NEXT:     (pop i32)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (block (result i32)
  ;; CHECK-NEXT:       (drop
  ;; CHECK-NEXT:        (local.get $0)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (i32.const 0)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (rethrow $l0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $rethrow
    (try $l0
      (do
        (throw $e-i32
          (i32.const 0)
        )
      )
      (catch $e-i32
        (drop
          (pop i32)
        )
        (rethrow $l0)
      )
    )
  )

  ;; CHECK:      (func $tuples (type $0)
  ;; CHECK-NEXT:  (tuple.drop 2
  ;; CHECK-NEXT:   (tuple.make 2
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $tuples
    (tuple.drop 2
      (tuple.make 2
        (i32.const 1)
        (i32.const 2)
      )
    )
  )
)

(module
  ;; CHECK:      (type $struct (sub (struct (field (mut i32)))))
  (type $struct (sub (struct (mut i32))))

  ;; CHECK:      (type $substruct (sub $struct (struct (field (mut i32)) (field f64))))
  (type $substruct (sub $struct (struct (mut i32) f64)))

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

  ;; CHECK:      (global $something (mut (ref $struct)) (struct.new $struct
  ;; CHECK-NEXT:  (i32.const 10)
  ;; CHECK-NEXT: ))
  (global $something (mut (ref $struct)) (struct.new $struct
    (i32.const 10)
  ))

  ;; CHECK:      (global $subsomething (mut (ref $substruct)) (struct.new $substruct
  ;; CHECK-NEXT:  (i32.const 22)
  ;; CHECK-NEXT:  (f64.const 3.14159)
  ;; CHECK-NEXT: ))
  (global $subsomething (mut (ref $substruct)) (struct.new $substruct
    (i32.const 22)
    (f64.const 3.14159)
  ))

  ;; CHECK:      (func $foo (type $2)
  ;; CHECK-NEXT:  (global.set $something
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (global.get $something)
  ;; CHECK-NEXT:   (i32.const 12)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $struct 0
  ;; CHECK-NEXT:    (global.get $something)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 22)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo
    ;; The global $something has an initial value and this later value, and they
    ;; are both of type $struct, so we can infer an exact type for the global.
    (global.set $something
      (struct.new $struct
        (i32.const 10)
      )
    )
    ;; Write to that global here. This can only affect $struct, and *not*
    ;; $substruct, thanks to the exact type.
    (struct.set $struct 0
      (global.get $something)
      (i32.const 12)
    )
    ;; We cannot optimize the first get here, as it might be 10 or 11.
    (drop
      (struct.get $struct 0
        (global.get $something)
      )
    )
    ;; We can optimize this get, however, as nothing aliased it and 22 is the
    ;; only possibility.
    (drop
      (struct.get $substruct 0
        (global.get $subsomething)
      )
    )
  )
)

;; As above, but we can no longer infer an exact type for the struct.set on the
;; global $something.
(module
  ;; CHECK:      (type $struct (sub (struct (field (mut i32)))))
  (type $struct (sub (struct (mut i32))))

  ;; CHECK:      (type $substruct (sub $struct (struct (field (mut i32)) (field f64))))
  (type $substruct (sub $struct (struct (mut i32) f64)))

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

  ;; CHECK:      (global $something (mut (ref $struct)) (struct.new $struct
  ;; CHECK-NEXT:  (i32.const 10)
  ;; CHECK-NEXT: ))
  (global $something (mut (ref $struct)) (struct.new $struct
    (i32.const 10)
  ))

  ;; CHECK:      (global $subsomething (mut (ref $substruct)) (struct.new $substruct
  ;; CHECK-NEXT:  (i32.const 22)
  ;; CHECK-NEXT:  (f64.const 3.14159)
  ;; CHECK-NEXT: ))
  (global $subsomething (mut (ref $substruct)) (struct.new $substruct
    (i32.const 22)
    (f64.const 3.14159)
  ))

  ;; CHECK:      (func $foo (type $2)
  ;; CHECK-NEXT:  (global.set $something
  ;; CHECK-NEXT:   (struct.new $substruct
  ;; CHECK-NEXT:    (i32.const 22)
  ;; CHECK-NEXT:    (f64.const 3.14159)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (global.get $something)
  ;; CHECK-NEXT:   (i32.const 12)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $struct 0
  ;; CHECK-NEXT:    (global.get $something)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $substruct 0
  ;; CHECK-NEXT:    (global.get $subsomething)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo
    ;; Write a $substruct to $something, so that the global might contain either
    ;; of the two types.
    (global.set $something
      (struct.new $substruct
        (i32.const 22)
        (f64.const 3.14159)
      )
    )
    ;; This write might alias both types now.
    (struct.set $struct 0
      (global.get $something)
      (i32.const 12)
    )
    ;; As a result, we can optimize neither of these gets.
    (drop
      (struct.get $struct 0
        (global.get $something)
      )
    )
    (drop
      (struct.get $substruct 0
        (global.get $subsomething)
      )
    )
  )
)

;; As above, but change the constants in the first field in all cases to 10. Now
;; we can optimize.
(module
  ;; CHECK:      (type $struct (sub (struct (field (mut i32)))))
  (type $struct (sub (struct (mut i32))))

  ;; CHECK:      (type $substruct (sub $struct (struct (field (mut i32)) (field f64))))
  (type $substruct (sub $struct (struct (mut i32) f64)))

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

  ;; CHECK:      (global $something (mut (ref $struct)) (struct.new $struct
  ;; CHECK-NEXT:  (i32.const 10)
  ;; CHECK-NEXT: ))
  (global $something (mut (ref $struct)) (struct.new $struct
    (i32.const 10)
  ))

  ;; CHECK:      (global $subsomething (mut (ref $substruct)) (struct.new $substruct
  ;; CHECK-NEXT:  (i32.const 10)
  ;; CHECK-NEXT:  (f64.const 3.14159)
  ;; CHECK-NEXT: ))
  (global $subsomething (mut (ref $substruct)) (struct.new $substruct
    (i32.const 10)
    (f64.const 3.14159)
  ))

  ;; CHECK:      (func $foo (type $2)
  ;; CHECK-NEXT:  (global.set $something
  ;; CHECK-NEXT:   (struct.new $substruct
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:    (f64.const 3.14159)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (global.get $something)
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo
    (global.set $something
      (struct.new $substruct
        (i32.const 10)
        (f64.const 3.14159)
      )
    )
    (struct.set $struct 0
      (global.get $something)
      (i32.const 10)
    )
    (drop
      (struct.get $struct 0
        (global.get $something)
      )
    )
    (drop
      (struct.get $substruct 0
        (global.get $subsomething)
      )
    )
  )
)

;; call_ref types
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $i1 (func (param i32)))
    (type $i1 (func (param i32)))
    ;; CHECK:       (type $i2 (func (param i32)))
    (type $i2 (func (param i32)))
  )

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

  ;; CHECK:      (type $3 (func (result i32)))

  ;; CHECK:      (import "a" "b" (func $import (type $3) (result i32)))
  (import "a" "b" (func $import (result i32)))

  ;; CHECK:      (global $func (ref func) (ref.func $reffed-in-global-code))
  (global $func (ref func) (ref.func $reffed-in-global-code))

  ;; CHECK:      (elem declare func $reffed1 $reffed2)

  ;; CHECK:      (func $reffed1 (type $i1) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 42)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $reffed1 (type $i1) (param $x i32)
    ;; This is called with one possible value, 42, which we can optimize the
    ;; param to.
    (drop
      (local.get $x)
    )
  )

  ;; CHECK:      (func $not-reffed (type $i1) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $not-reffed (type $i1) (param $x i32)
    ;; This function has the same type as the previous one, but it is never
    ;; taken by reference, which means the call_refs below do not affect it. As
    ;; there are no other calls, this local.get can be turned into an
    ;; unreachable.
    (drop
      (local.get $x)
    )
  )

  ;; CHECK:      (func $reffed-in-global-code (type $i1) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 42)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $reffed-in-global-code (type $i1) (param $x i32)
    ;; The only ref to this function is in global code, so this tests that we
    ;; scan that properly. This can be optimized like $reffed, that is, we can
    ;; infer 42 here.
    (drop
      (local.get $x)
    )
  )

  ;; CHECK:      (func $reffed2 (type $i2) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $reffed2 (type $i2) (param $x i32)
    ;; This is called with two possible values, so we cannot optimize.
    (drop
      (local.get $x)
    )
  )

  ;; CHECK:      (func $do-calls (type $2)
  ;; CHECK-NEXT:  (call_ref $i1
  ;; CHECK-NEXT:   (i32.const 42)
  ;; CHECK-NEXT:   (ref.func $reffed1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $i1
  ;; CHECK-NEXT:   (i32.const 42)
  ;; CHECK-NEXT:   (ref.func $reffed1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $i2
  ;; CHECK-NEXT:   (i32.const 1337)
  ;; CHECK-NEXT:   (ref.func $reffed2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $i2
  ;; CHECK-NEXT:   (i32.const 99999)
  ;; CHECK-NEXT:   (ref.func $reffed2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $do-calls
    ;; Call $i1 twice with the same value, and $i2 twice with different values.
    ;; Note that structurally the types are identical, but we still
    ;; differentiate them, allowing us to optimize.
    (call_ref $i1
      (i32.const 42)
      (ref.func $reffed1)
    )
    (call_ref $i1
      (i32.const 42)
      (ref.func $reffed1)
    )
    (call_ref $i2
      (i32.const 1337)
      (ref.func $reffed2)
    )
    (call_ref $i2
      (i32.const 99999)
      (ref.func $reffed2)
    )
  )

  ;; CHECK:      (func $call_ref-nofunc (type $2)
  ;; CHECK-NEXT:  (block ;; (replaces unreachable CallRef we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (ref.null nofunc)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $call_ref-nofunc
    ;; Test a call_ref of something of type nofunc. That has a heap type, but it
    ;; is not a signature type. We should not crash on that.
    (call_ref $i1
      (i32.const 1)
      (ref.null nofunc)
    )
  )
)

;; Limited cone reads.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (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 i32)))

  ;; CHECK:      (export "reads" (func $reads))

  ;; CHECK:      (func $reads (type $3) (param $x i32)
  ;; CHECK-NEXT:  (local $A (ref $A))
  ;; CHECK-NEXT:  (local $B (ref $B))
  ;; CHECK-NEXT:  (local $C (ref $C))
  ;; CHECK-NEXT:  (local.set $A
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $B
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $C
  ;; CHECK-NEXT:   (struct.new $C
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 0
  ;; CHECK-NEXT:    (select (result (ref $A))
  ;; CHECK-NEXT:     (local.get $A)
  ;; CHECK-NEXT:     (local.get $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 0
  ;; CHECK-NEXT:    (select (result (ref $A))
  ;; CHECK-NEXT:     (local.get $A)
  ;; CHECK-NEXT:     (local.get $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 20)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $reads (export "reads") (param $x i32)
    (local $A (ref $A))
    (local $B (ref $B))
    (local $C (ref $C))
    ;; B and C agree on their value.
    (local.set $A
      (struct.new $A
        (i32.const 10)
      )
    )
    (local.set $B
      (struct.new $B
        (i32.const 20)
      )
    )
    (local.set $C
      (struct.new $C
        (i32.const 20)
      )
    )
    ;; We can optimize the last of these, which mixes B and C, into 20.
    (drop
      (struct.get $A 0
        (select
          (local.get $A)
          (local.get $B)
          (local.get $x)
        )
      )
    )
    (drop
      (struct.get $A 0
        (select
          (local.get $A)
          (local.get $C)
          (local.get $x)
        )
      )
    )
    (drop
      (struct.get $A 0
        (select
          (local.get $B)
          (local.get $C)
          (local.get $x)
        )
      )
    )
  )
)

;; As above, but now A and B agree on the value and not B and C.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (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 i32)))

  ;; CHECK:      (export "reads" (func $reads))

  ;; CHECK:      (func $reads (type $3) (param $x i32)
  ;; CHECK-NEXT:  (local $A (ref $A))
  ;; CHECK-NEXT:  (local $B (ref $B))
  ;; CHECK-NEXT:  (local $C (ref $C))
  ;; CHECK-NEXT:  (local.set $A
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $B
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $C
  ;; CHECK-NEXT:   (struct.new $C
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 0
  ;; CHECK-NEXT:    (select (result (ref $A))
  ;; CHECK-NEXT:     (local.get $A)
  ;; CHECK-NEXT:     (local.get $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 0
  ;; CHECK-NEXT:    (select (result (ref $B))
  ;; CHECK-NEXT:     (local.get $B)
  ;; CHECK-NEXT:     (local.get $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $reads (export "reads") (param $x i32)
    (local $A (ref $A))
    (local $B (ref $B))
    (local $C (ref $C))
    ;; A and B agree on their value.
    (local.set $A
      (struct.new $A
        (i32.const 10)
      )
    )
    (local.set $B
      (struct.new $B
        (i32.const 10)
      )
    )
    (local.set $C
      (struct.new $C
        (i32.const 20)
      )
    )
    ;; We can optimize the first of these, which mixes A and B, into 10.
    (drop
      (struct.get $A 0
        (select
          (local.get $A)
          (local.get $B)
          (local.get $x)
        )
      )
    )
    (drop
      (struct.get $A 0
        (select
          (local.get $A)
          (local.get $C)
          (local.get $x)
        )
      )
    )
    (drop
      (struct.get $A 0
        (select
          (local.get $B)
          (local.get $C)
          (local.get $x)
        )
      )
    )
  )
)

;; As above but now A has two subtypes, instead of a chain A->B->C
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (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 $A (struct (field (mut i32)))))
    (type $C (sub $A (struct (field (mut i32))))) ;; This line changed.
  )

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

  ;; CHECK:      (export "reads" (func $reads))

  ;; CHECK:      (func $reads (type $3) (param $x i32)
  ;; CHECK-NEXT:  (local $A (ref $A))
  ;; CHECK-NEXT:  (local $B (ref $B))
  ;; CHECK-NEXT:  (local $C (ref $C))
  ;; CHECK-NEXT:  (local.set $A
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $B
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $C
  ;; CHECK-NEXT:   (struct.new $C
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 0
  ;; CHECK-NEXT:    (select (result (ref $A))
  ;; CHECK-NEXT:     (local.get $A)
  ;; CHECK-NEXT:     (local.get $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 0
  ;; CHECK-NEXT:    (select (result (ref $A))
  ;; CHECK-NEXT:     (local.get $A)
  ;; CHECK-NEXT:     (local.get $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 0
  ;; CHECK-NEXT:    (select (result (ref $A))
  ;; CHECK-NEXT:     (local.get $B)
  ;; CHECK-NEXT:     (local.get $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $reads (export "reads") (param $x i32)
    (local $A (ref $A))
    (local $B (ref $B))
    (local $C (ref $C))
    ;; A and B agree on their value.
    (local.set $A
      (struct.new $A
        (i32.const 10)
      )
    )
    (local.set $B
      (struct.new $B
        (i32.const 10)
      )
    )
    (local.set $C
      (struct.new $C
        (i32.const 20)
      )
    )
    ;; We cannot optimize any of these. The first is optimizable in theory,
    ;; since A and B agree on the value, but we end up with a cone on A of depth
    ;; 1, and that includes B and C. To optimize this we'd need a sum type.
    (drop
      (struct.get $A 0
        (select
          (local.get $A)
          (local.get $B)
          (local.get $x)
        )
      )
    )
    (drop
      (struct.get $A 0
        (select
          (local.get $A)
          (local.get $C)
          (local.get $x)
        )
      )
    )
    (drop
      (struct.get $A 0
        (select
          (local.get $B)
          (local.get $C)
          (local.get $x)
        )
      )
    )
  )
)

;; Cone writes.
(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 i32)))

  ;; CHECK:      (export "write" (func $write))

  ;; CHECK:      (func $write (type $3) (param $x i32)
  ;; CHECK-NEXT:  (local $A (ref $A))
  ;; CHECK-NEXT:  (local $B (ref $B))
  ;; CHECK-NEXT:  (local $C (ref $C))
  ;; CHECK-NEXT:  (local.set $A
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $B
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $C
  ;; CHECK-NEXT:   (struct.new $C
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $A 0
  ;; CHECK-NEXT:   (select (result (ref $A))
  ;; CHECK-NEXT:    (local.get $A)
  ;; CHECK-NEXT:    (local.get $B)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 20)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $write (export "write") (param $x i32)
    (local $A (ref $A))
    (local $B (ref $B))
    (local $C (ref $C))
    ;; A and B agree on their value.
    (local.set $A
      (struct.new $A
        (i32.const 10)
      )
    )
    (local.set $B
      (struct.new $B
        (i32.const 10)
      )
    )
    (local.set $C
      (struct.new $C
        (i32.const 20)
      )
    )
    ;; Do a cone write. This writes the same value as they already have.
    (struct.set $A 0
      (select
        (local.get $A)
        (local.get $B)
        (local.get $x)
      )
      (i32.const 10)
    )
    ;; Read from all the locals. We can optimize them all, to 10, 10, 20.
    (drop
      (struct.get $A 0
        (local.get $A)
      )
    )
    (drop
      (struct.get $B 0
        (local.get $B)
      )
    )
    (drop
      (struct.get $C 0
        (local.get $C)
      )
    )
  )
)

;; As above, but write a different value.
(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 i32)))

  ;; CHECK:      (export "write" (func $write))

  ;; CHECK:      (func $write (type $3) (param $x i32)
  ;; CHECK-NEXT:  (local $A (ref $A))
  ;; CHECK-NEXT:  (local $B (ref $B))
  ;; CHECK-NEXT:  (local $C (ref $C))
  ;; CHECK-NEXT:  (local.set $A
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $B
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $C
  ;; CHECK-NEXT:   (struct.new $C
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $B 0
  ;; CHECK-NEXT:   (select (result (ref $B))
  ;; CHECK-NEXT:    (local.get $B)
  ;; CHECK-NEXT:    (local.get $C)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $C 0
  ;; CHECK-NEXT:    (local.get $C)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $write (export "write") (param $x i32)
    (local $A (ref $A))
    (local $B (ref $B))
    (local $C (ref $C))
    (local.set $A
      (struct.new $A
        (i32.const 10)
      )
    )
    (local.set $B
      (struct.new $B
        (i32.const 10)
      )
    )
    (local.set $C
      (struct.new $C
        (i32.const 20)
      )
    )
    ;; Do a different cone write from before: now we write to B and C. This
    ;; means C can have 10 or 20, and so we don't optimize it down below.
    (struct.set $A 0
      (select
        (local.get $B)
        (local.get $C)
        (local.get $x)
      )
      (i32.const 10)
    )
    (drop
      (struct.get $A 0
        (local.get $A)
      )
    )
    (drop
      (struct.get $B 0
        (local.get $B)
      )
    )
    (drop
      (struct.get $C 0
        (local.get $C)
      )
    )
  )
)

;; As above, but write a different cone.
(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 i32)))

  ;; CHECK:      (export "write" (func $write))

  ;; CHECK:      (func $write (type $3) (param $x i32)
  ;; CHECK-NEXT:  (local $A (ref $A))
  ;; CHECK-NEXT:  (local $B (ref $B))
  ;; CHECK-NEXT:  (local $C (ref $C))
  ;; CHECK-NEXT:  (local.set $A
  ;; CHECK-NEXT:   (struct.new $A
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $B
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $C
  ;; CHECK-NEXT:   (struct.new $C
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $B 0
  ;; CHECK-NEXT:   (select (result (ref $B))
  ;; CHECK-NEXT:    (local.get $B)
  ;; CHECK-NEXT:    (local.get $C)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (i32.const 20)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 0
  ;; CHECK-NEXT:    (local.get $B)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 20)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $write (export "write") (param $x i32)
    (local $A (ref $A))
    (local $B (ref $B))
    (local $C (ref $C))
    (local.set $A
      (struct.new $A
        (i32.const 10)
      )
    )
    (local.set $B
      (struct.new $B
        (i32.const 10)
      )
    )
    (local.set $C
      (struct.new $C
        (i32.const 20)
      )
    )
    ;; Write a different value now: 20. This prevents us from optimizing B, but
    ;; we can still optimize A and C.
    (struct.set $A 0
      (select
        (local.get $B)
        (local.get $C)
        (local.get $x)
      )
      (i32.const 20)
    )
    (drop
      (struct.get $A 0
        (local.get $A)
      )
    )
    (drop
      (struct.get $B 0
        (local.get $B)
      )
    )
    (drop
      (struct.get $C 0
        (local.get $C)
      )
    )
  )
)

;; Tests for proper inference of imported etc. values - we do know their type,
;; at least.
(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 $A))))

  ;; CHECK:      (type $3 (func (result (ref $A))))

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

  ;; CHECK:      (import "a" "b" (global $A (ref $A)))
  (import "a" "b" (global $A (ref $A)))

  ;; CHECK:      (import "a" "c" (func $A (type $3) (result (ref $A))))
  (import "a" "c" (func $A (result (ref $A))))

  ;; CHECK:      (global $mut_A (ref $A) (struct.new $A
  ;; CHECK-NEXT:  (i32.const 42)
  ;; CHECK-NEXT: ))
  (global $mut_A (ref $A) (struct.new $A
    (i32.const 42)
  ))

  ;; CHECK:      (export "yes" (func $yes))

  ;; CHECK:      (export "no" (func $no))

  ;; CHECK:      (export "mut_A" (global $mut_A))
  (export "mut_A" (global $mut_A))

  ;; CHECK:      (func $yes (type $2) (param $A (ref $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (call $A)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $yes (export "yes") (param $A (ref $A))
    ;; An imported global has a known type, at least, which in this case is
    ;; enough for us to infer a result of 1.
    (drop
      (ref.test (ref $A)
        (global.get $A)
      )
    )
    ;; Likewise, a function result.
    (drop
      (ref.test (ref $A)
        (call $A)
      )
    )
    ;; Likewise, a parameter to this function, which is exported, but we do
    ;; still know the type it will be called with, and can optimize to 1.
    (drop
      (ref.test (ref $A)
        (local.get $A)
      )
    )
    ;; Likewise, an exported mutable global can be modified by the outside, but
    ;; the type remains known, and we can optimize to 1.
    (drop
      (ref.test (ref $A)
        (global.get $A)
      )
    )
  )

  ;; CHECK:      (func $no (type $2) (param $A (ref $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $B)
  ;; CHECK-NEXT:    (global.get $A)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $B)
  ;; CHECK-NEXT:    (call $A)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $B)
  ;; CHECK-NEXT:    (local.get $A)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $B)
  ;; CHECK-NEXT:    (global.get $A)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $no (export "no") (param $A (ref $A))
    ;; Identical to the above function, but now all tests are vs type $B. We
    ;; cannot optimize any of these, as all we know is the type is $A.
    (drop
      (ref.test (ref $B)
        (global.get $A)
      )
    )
    (drop
      (ref.test (ref $B)
        (call $A)
      )
    )
    (drop
      (ref.test (ref $B)
        (local.get $A)
      )
    )
    (drop
      (ref.test (ref $B)
        (global.get $A)
      )
    )
  )

  ;; CHECK:      (func $filtering (type $4)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block $B (result (ref none))
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (br_on_cast $B (ref (exact $A)) (ref none)
  ;; CHECK-NEXT:        (struct.new $A
  ;; CHECK-NEXT:         (i32.const 100)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block $A (result (ref (exact $A)))
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (br_on_cast $A (ref (exact $A)) (ref (exact $A))
  ;; CHECK-NEXT:        (struct.new $A
  ;; CHECK-NEXT:         (i32.const 200)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $filtering
    ;; Check for filtering of values by the declared type in the wasm. We do not
    ;; have specific filtering or flowing for br_on_* yet, so it will always
    ;; send the value to the branch target. But the target has a declared type
    ;; of $B, which means the exact $A gets filtered out, and nothing remains,
    ;; so we can append an unreachable.
    ;;
    ;; When we add filtering/flowing for br_on_* this test should continue to
    ;; pass and only the comment will need to be updated, so if you are reading
    ;; this and it is stale, please fix that :)
    (drop
      (block $B (result (ref $B))
        (drop
          (br_on_cast $B anyref (ref $B)
            (struct.new $A
              (i32.const 100)
            )
          )
        )
        (unreachable)
      )
    )
    ;; But casting to $A will succeed, so the block is reachable, and also the
    ;; cast will return 1.
    (drop
      (ref.test (ref $A)
        (block $A (result (ref $A))
          (drop
            (br_on_cast $A anyref (ref $A)
              (struct.new $A
                (i32.const 200)
              )
            )
          )
          (unreachable)
        )
      )
    )
  )
)


;; Check that array.new_data and array.new_seg are handled properly.
(module
  ;; CHECK:      (type $array-i8 (array i8))
  (type $array-i8 (array i8))
  ;; CHECK:      (type $array-funcref (array funcref))
  (type $array-funcref (array funcref))
  (data "hello")
  (elem func $test)

  ;; CHECK:      (type $2 (func (param (ref $array-i8) (ref $array-funcref))))

  ;; CHECK:      (data $0 "hello")

  ;; CHECK:      (elem $0 func $test)

  ;; CHECK:      (export "test" (func $test))

  ;; CHECK:      (func $test (type $2) (param $array-i8 (ref $array-i8)) (param $array-funcref (ref $array-funcref))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.new_data $array-i8 $0
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (i32.const 5)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.new_elem $array-funcref $0
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.get_u $array-i8
  ;; CHECK-NEXT:    (local.get $array-i8)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.get $array-funcref
  ;; CHECK-NEXT:    (local.get $array-funcref)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (export "test") (param $array-i8 (ref $array-i8)) (param $array-funcref (ref $array-funcref))
    (drop
      (array.new_data $array-i8 0
        (i32.const 0)
        (i32.const 5)
      )
    )
    (drop
      (array.new_elem $array-funcref 0
        (i32.const 0)
        (i32.const 1)
      )
    )
    (drop
      (array.get $array-i8
        (local.get $array-i8)
        (i32.const 0)
      )
    )
    (drop
      (array.get $array-funcref
        (local.get $array-funcref)
        (i32.const 0)
      )
    )
  )
)

;; Verify we do not error or misoptimize with array.init_elem.
(module
  ;; CHECK:      (type $vector (array (mut funcref)))
  (type $vector (array (mut funcref)))

  (elem func)

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

  ;; CHECK:      (elem $0 func)

  ;; CHECK:      (elem declare func $test)

  ;; CHECK:      (func $test (type $1)
  ;; CHECK-NEXT:  (local $ref (ref $vector))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (array.new $vector
  ;; CHECK-NEXT:    (ref.func $test)
  ;; CHECK-NEXT:    (i32.const 100)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (array.init_elem $vector $0
  ;; CHECK-NEXT:   (local.get $ref)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.get $vector
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    (local $ref (ref $vector))
    (local.set $ref
      (array.new $vector
        (ref.func $test)
        (i32.const 100)
      )
    )
    (array.init_elem $vector 0
      (local.get $ref)
      (i32.const 1)
      (i32.const 1)
      (i32.const 1)
    )
    ;; We wrote a specific ref.func earlier, but also we did an init_elem whose
    ;; values we consider unknown, so we will not optimize this get.
    (drop
      (array.get $vector
        (local.get $ref)
        (i32.const 1)
      )
    )
  )
)

;; Packed field combination.
(module
  (rec
    ;; CHECK:      (type $0 (func))

    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (struct (field i8)))
    (type $A (struct (field i8)))
    ;; CHECK:       (type $B (struct (field i8)))
    (type $B (struct (field i8)))
  )

  ;; CHECK:      (func $A (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get_u $A 0
  ;; CHECK-NEXT:    (struct.new $A
  ;; CHECK-NEXT:     (i32.const 305419896)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get_u $A 0
  ;; CHECK-NEXT:    (struct.new_default $A)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $A
    ;; We write two values to $A, which are different, so we cannot infer.
    (drop
      (struct.get_u $A 0
        (struct.new $A
          (i32.const 0x12345678)
        )
      )
    )
    (drop
      (struct.get_u $A 0
        (struct.new_default $A)
      )
    )
  )

  ;; CHECK:      (func $B (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $B
    ;; We write two values to $B, which *seem* different, but given the field is
    ;; packed they are both actually 0, so we can optimize here.
    (drop
      (struct.get_u $B 0
        (struct.new $B
          (i32.const 0x12345600) ;; only this changed compared to func $A
        )
      )
    )
    (drop
      (struct.get_u $B 0
        (struct.new_default $B)
      )
    )
  )
)

;; Packed fields with signed gets.
(module
  ;; CHECK:      (type $array (array (mut i8)))

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

  ;; CHECK:      (type $struct (struct (field i16)))
  (type $struct (struct (field i16)))

  (type $array (array (mut i8)))

  ;; CHECK:      (func $test-struct (type $1)
  ;; CHECK-NEXT:  (local $x (ref $struct))
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (i32.const -1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const -1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 65535)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test-struct
    (local $x (ref $struct))
    (local.set $x
      (struct.new $struct
        (i32.const -1)
      )
    )
    ;; This reads -1.
    (drop
      (struct.get_s $struct 0
        (local.get $x)
      )
    )
    ;; This reads 65535, as the other bits were truncated.
    (drop
      (struct.get_u $struct 0
        (local.get $x)
      )
    )
  )

  ;; CHECK:      (func $test-array (type $1)
  ;; CHECK-NEXT:  (local $x (ref $array))
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (array.new_fixed $array 1
  ;; CHECK-NEXT:    (i32.const -1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (array.get_s $array
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const -1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (array.get_u $array
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 255)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test-array
    (local $x (ref $array))
    (local.set $x
      (array.new_fixed $array 1
        (i32.const -1)
      )
    )
    ;; This reads -1.
    (drop
      (array.get_s $array
        (local.get $x)
        (i32.const 0)
      )
    )
    ;; This reads 255, as the other bits were truncated.
    (drop
      (array.get_u $array
        (local.get $x)
        (i32.const 0)
      )
    )
  )
)

;; Packed fields with conflicting sets.
(module

  ;; CHECK:      (type $struct (struct (field i16)))

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

  ;; CHECK:      (import "a" "b" (global $import i32))
  (import "a" "b" (global $import i32))

  (type $struct (struct (field i16)))

  ;; CHECK:      (func $test-struct (type $1)
  ;; CHECK-NEXT:  (local $x (ref null $struct))
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (global.get $import)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const -1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (else
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 42)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get_s $struct 0
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get_u $struct 0
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test-struct
    (local $x (ref null $struct))
    (if
      (global.get $import)
      (then
        (local.set $x
          (struct.new $struct
            (i32.const -1)
          )
        )
      )
      (else
        (local.set $x
          (struct.new $struct
            (i32.const 42)
          )
        )
      )
    )
    ;; We cannot infer anything for these reads.
    (drop
      (struct.get_s $struct 0
        (local.get $x)
      )
    )
    (drop
      (struct.get_u $struct 0
        (local.get $x)
      )
    )
  )
)

;; Packed fields with different sets that actually do not conflict.
(module

  ;; CHECK:      (type $struct (struct (field i16)))

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

  ;; CHECK:      (import "a" "b" (global $import i32))
  (import "a" "b" (global $import i32))

  (type $struct (struct (field i16)))

  ;; CHECK:      (func $test-struct (type $1)
  ;; CHECK-NEXT:  (local $x (ref null $struct))
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (global.get $import)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const -1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (else
  ;; CHECK-NEXT:    (local.set $x
  ;; CHECK-NEXT:     (struct.new $struct
  ;; CHECK-NEXT:      (i32.const 65535)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get_s $struct 0
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const -1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (struct.get_u $struct 0
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 65535)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test-struct
    (local $x (ref null $struct))
    (if
      (global.get $import)
      (then
        (local.set $x
          (struct.new $struct
            (i32.const -1)
          )
        )
      )
      (else
        (local.set $x
          (struct.new $struct
            (i32.const 65535)
          )
        )
      )
    )
    ;; We can infer here because -1 and 65535 are actually the same, after
    ;; truncation.
    (drop
      (struct.get_s $struct 0
        (local.get $x)
      )
    )
    (drop
      (struct.get_u $struct 0
        (local.get $x)
      )
    )
  )
)

;; Test that we do not error on array.init of a bottom type.
(module
  (type $"[mut:i32]" (array (mut i32)))

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

  ;; CHECK:      (data $0 "")
  (data $0 "")

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (block ;; (replaces unreachable ArrayInitData we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    (array.init_data $"[mut:i32]" $0
      (ref.as_non_null
        (ref.null none)
      )
      (i32.const 0)
      (i32.const 0)
      (i32.const 1)
    )
  )
)

(module
  ;; CHECK:      (type $A (sub (struct)))
  (type $A (sub (struct)))

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

  ;; CHECK:      (type $2 (func (result (ref $A))))

  ;; CHECK:      (type $3 (func (result anyref)))

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

  ;; CHECK:      (func $func (type $2) (result (ref $A))
  ;; CHECK-NEXT:  (ref.cast (ref (exact $B))
  ;; CHECK-NEXT:   (call $get-B-def-any)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (export "func") (result (ref $A))
    ;; Call a function that actually returns a B, though it is defined as
    ;; returning an anyref. Then cast it to A. We can infer that it will be a B,
    ;; so we can cast to B here instead.
    (ref.cast (ref $A)
      (call $get-B-def-any)
    )
  )

  ;; CHECK:      (func $get-B-def-any (type $3) (result anyref)
  ;; CHECK-NEXT:  (struct.new_default $B)
  ;; CHECK-NEXT: )
  (func $get-B-def-any (result anyref)
    (struct.new $B)
  )
)

;; A situation that we need traps-never-happens to optimize. Here we do nothing,
;; while in gufa-tnh we test with that flag.
(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:  (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))
    ;; The param is cast.
    (drop
      (ref.cast (ref $B)
        (local.get $x)
      )
    )
  )

  ;; CHECK:      (func $caller (type $3) (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
      (ref.cast (ref $A)
                   ;; This cast will could be refined with TNH, since we call a
                   ;; function that casts (so if we do not trap as TNH assumes,
                   ;; we must be sending in a $B). But without that flag we do
                   ;; nothing.
        (local.get $any)
      )
    )
  )
)

(module
  ;; CHECK:      (type $A (struct))
  (type $A (struct))

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

  ;; CHECK:      (func $func (type $1)
  ;; CHECK-NEXT:  (local $temp anyref)
  ;; CHECK-NEXT:  (local.set $temp
  ;; CHECK-NEXT:   (struct.new_default $A)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $label (result (ref null (exact $A)))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (br_on_cast $label (ref (exact $A)) (ref (exact $A))
  ;; CHECK-NEXT:      (ref.cast (ref (exact $A))
  ;; CHECK-NEXT:       (local.get $temp)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func
    (local $temp anyref)
    ;; Write an $A into the anyref local.
    (local.set $temp
      (struct.new $A)
    )
    (drop
      (block $label (result anyref)
        (drop
          (br_on_cast $label anyref (ref struct)
            ;; This cast can be refined since we know the input is $A. After we
            ;; do that, we must refinalize, as the br_on_cast's types must be
            ;; valid - specifically, we can't end up with the input type being
            ;; $A and the output type still being (ref struct), as the output
            ;; type must be a subtype. After refinalizing, both will become $A.
            (ref.cast anyref
              (local.get $temp)
            )
          )
        )
        (ref.null none)
      )
    )
  )
)

(module
  ;; CHECK:      (type $array (sub (array (mut i8))))
  (type $array (sub (array (mut i8))))

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

  ;; CHECK:      (type $2 (func (result i64 nullref i32)))

  ;; CHECK:      (global $global (ref null $array) (array.new_fixed $array 0))
  (global $global (ref null $array) (array.new_fixed $array 0))

  ;; CHECK:      (func $test-set-bottom (type $1)
  ;; CHECK-NEXT:  (block ;; (replaces unreachable ArraySet we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block (result nullref)
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (ref.cast nullref
  ;; CHECK-NEXT:       (global.get $global)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test-set-bottom
    ;; We should not error on sets to bottom types, even if they are cast from
    ;; valid values.
    (array.set $array
      (ref.cast nullref
        (global.get $global)
      )
      (i32.const 0)
      (i32.const 0)
    )
  )

  ;; CHECK:      (func $loop-tuple-br_on (type $1)
  ;; CHECK-NEXT:  (tuple.drop 3
  ;; CHECK-NEXT:   (loop $loop (type $2) (result i64 nullref i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (br_on_null $loop
  ;; CHECK-NEXT:        (ref.null none)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (tuple.make 3
  ;; CHECK-NEXT:     (i64.const 1)
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $loop-tuple-br_on
    ;; The br_on here does not send any values when it branches. This is a test
    ;; for a bug where it did send the null along, which then caused an
    ;; assertion when we tried to combine the null with the tuple that flows
    ;; out.
    (tuple.drop 3
      (loop $loop (result i64 anyref i32)
        (drop
          ;; As this br always happens, we can add an unreachable after it.
          (br_on_null $loop
            (ref.null any)
          )
        )
        (tuple.make 3
          (i64.const 1)
          (ref.null any)
          (i32.const 2)
        )
      )
    )
  )
)

;; Atomic accesses require special handling
(module
  ;; CHECK:      (type $A (shared (struct (field i32))))
  (type $A (shared (struct (field i32))))

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

  ;; CHECK:      (func $gets (type $1)
  ;; CHECK-NEXT:  (local $0 (ref $A))
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (struct.new_default $A)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.atomic.get acqrel $A 0
  ;; CHECK-NEXT:    (local.get $0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.atomic.get $A 0
  ;; CHECK-NEXT:    (local.get $0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $gets
    (local (ref $A))
    (local.set 0
      (struct.new_default $A)
    )
    (drop
      ;; This is optimizable. It reads from shared memory, but there is only one
      ;; possible value that can be read and it is not atomic, so does not form
      ;; a synchronization edge.
      (struct.get $A 0
        (local.get 0)
      )
    )
    (drop
      ;; We do not optimize atomic gets, since they might synchronize with a
      ;; write.
      (struct.atomic.get acqrel $A 0
        (local.get 0)
      )
    )
    (drop
      ;; We do not optimize atomic gets, since they might synchronize with a
      ;; write.
      (struct.atomic.get $A 0
        (local.get 0)
      )
    )
  )
)
