;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.

;; RUN: wasm-opt %s --remove-unused-names --optimize-instructions --enable-reference-types --enable-gc -S -o - \
;; RUN:   | filecheck %s

(module
  ;; CHECK:      (type $struct (struct (field $i8 (mut i8)) (field $i16 (mut i16)) (field $i32 (mut i32)) (field $i64 (mut i64))))
  (type $struct (struct
    (field $i8  (mut i8))
    (field $i16 (mut i16))
    (field $i32 (mut i32))
    (field $i64 (mut i64))
  ))

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

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

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

  ;; CHECK:      (type $B (sub $A (struct (field i32) (field i32) (field f32))))
  (type $B (sub $A (struct (field i32) (field i32) (field f32))))

  ;; CHECK:      (type $B-child (sub $B (struct (field i32) (field i32) (field f32) (field i64))))
  (type $B-child (sub $B (struct (field i32) (field i32) (field f32) (field i64))))

  (type $empty (struct))

  ;; CHECK:      (type $void (sub (func)))

  ;; CHECK:      (type $void2 (sub $void (func)))

  ;; CHECK:      (type $C (sub $A (struct (field i32) (field i32) (field f64))))

  ;; CHECK:      (type $struct.ref (struct (field funcref)))
  (type $struct.ref (struct (field funcref)))

  (type $C (sub $A (struct (field i32) (field i32) (field f64))))

  (type $void (sub (func)))

  (type $void2 (sub $void (func)))

  ;; CHECK:      (type $struct_i64 (func (param structref) (result i64)))
  (type $struct_i64 (func (param (ref null struct)) (result i64)))

  ;; CHECK:      (import "env" "get-i32" (func $get-i32 (type $8) (result i32)))
  (import "env" "get-i32" (func $get-i32 (result i32)))

  ;; These functions test if an `if` with subtyped arms is correctly folded
  ;; 1. if its `ifTrue` and `ifFalse` arms are identical (can fold)
  ;; CHECK:      (func $if-arms-subtype-fold (type $28) (result anyref)
  ;; CHECK-NEXT:  (ref.null none)
  ;; CHECK-NEXT: )
  (func $if-arms-subtype-fold (result anyref)
    (if (result anyref)
      (i32.const 0)
      (then
        (ref.null eq)
      )
      (else
        (ref.null eq)
      )
    )
  )
  ;; 2. if its `ifTrue` and `ifFalse` arms are not identical (cannot fold)
  ;; CHECK:      (func $if-arms-subtype-nofold (type $29) (param $i31ref i31ref) (result anyref)
  ;; CHECK-NEXT:  (if (result anyref)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (else
  ;; CHECK-NEXT:    (local.get $i31ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $if-arms-subtype-nofold (param $i31ref i31ref) (result anyref)
    (if (result anyref)
      (i32.const 0)
      (then
        (ref.null none)
      )
      (else
        (local.get $i31ref)
      )
    )
  )

  ;; Stored values automatically truncate unneeded bytes.
  ;; CHECK:      (func $store-trunc (type $11) (param $x (ref null $struct))
  ;; CHECK-NEXT:  (struct.set $struct $i8
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (i32.const 35)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct $i16
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (i32.const 9029)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct $i8
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (call $get-i32)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $store-trunc (param $x (ref null $struct))
    (struct.set $struct $i8
      (local.get $x)
      (i32.const 0x123) ;; data over 0xff is unnecessary
    )
    (struct.set $struct $i16
      (local.get $x)
      (i32.const 0x12345) ;; data over 0xffff is unnecessary
    )
    (struct.set $struct $i8
      (local.get $x)
      (i32.and ;; truncating bits using an and is unnecessary
       (call $get-i32)
       (i32.const 0xff)
      )
    )
  )

  ;; Similar, but for arrays.
  ;; CHECK:      (func $store-trunc2 (type $16) (param $x (ref null $array))
  ;; CHECK-NEXT:  (array.set $array
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (i32.const 35)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $store-trunc2 (param $x (ref null $array))
    (array.set $array
      (local.get $x)
      (i32.const 0)
      (i32.const 0x123) ;; data over 0xff is unnecessary
    )
  )

  ;; ref.is_null is not needed on a non-nullable value, and if something is
  ;; cast to its own type, we don't need that either, etc.
  ;; CHECK:      (func $unneeded_test (type $17) (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $struct)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $func)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $i31)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unneeded_test
    (param $struct (ref $struct))
    (param $func (ref func))
    (param $i31 (ref i31))
    (drop
      (ref.is_null (local.get $struct))
    )
    (drop
       (ref.test (ref func) (local.get $func))
     )
    (drop
      (ref.test (ref i31) (local.get $i31))
    )
  )

  ;; similar to $unneeded_is, but the values are nullable. we can at least
  ;; leave just the null check.
  ;; CHECK:      (func $unneeded_test_null (type $18) (param $struct (ref null $struct)) (param $func funcref) (param $i31 i31ref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.is_null
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.eqz
  ;; CHECK-NEXT:    (ref.is_null
  ;; CHECK-NEXT:     (local.get $func)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.eqz
  ;; CHECK-NEXT:    (ref.is_null
  ;; CHECK-NEXT:     (local.get $i31)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unneeded_test_null
   (param $struct (ref null $struct))
    (param $func (ref null func))
    (param $i31 (ref null i31))
    (drop
      (ref.is_null (local.get $struct))
    )
    ;; This can be optimized to !is_null rather than ref.test func, since we
    ;; know the heap type is what we want, so the only possible issue is a null.
    (drop
      (ref.test (ref func) (local.get $func))
    )
    ;; This can be optimized similarly.
    (drop
      (ref.test (ref i31) (local.get $i31))
    )
  )

  ;; ref.as_non_null is not needed on a non-nullable value, and if something is
  ;; a func we don't need that either etc., and can just return the value.
  ;; CHECK:      (func $unneeded_cast (type $17) (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $func)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $i31)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unneeded_cast
    (param $struct (ref $struct))
    (param $func (ref func))
    (param $i31 (ref i31))
    (drop
      (ref.as_non_null (local.get $struct))
    )
    (drop
      (ref.cast (ref func) (local.get $func))
    )
    (drop
      (ref.cast (ref i31) (local.get $i31))
    )
  )

  ;; similar to $unneeded_cast, but the values are nullable. we can turn the
  ;; more specific things into ref.as_non_null.
  ;; CHECK:      (func $unneeded_cast_null (type $18) (param $struct (ref null $struct)) (param $func funcref) (param $i31 i31ref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (local.get $func)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (local.get $i31)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unneeded_cast_null
    (param $struct (ref null $struct))
    (param $func (ref null func))
    (param $i31 (ref null i31))
    (drop
      (ref.as_non_null (local.get $struct))
    )
    (drop
      (ref.cast (ref func) (local.get $func))
    )
    (drop
      (ref.cast (ref i31) (local.get $i31))
    )
  )

  ;; CHECK:      (func $unneeded_unreachability (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref func)
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block ;; (replaces unreachable RefCast we can't emit)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unneeded_unreachability
    ;; unreachable instructions can simply be ignored
    (drop
      (ref.test (ref func) (unreachable))
    )
    (drop
      (ref.cast (ref func) (unreachable))
    )
  )

  ;; CHECK:      (func $redundant-non-null-casts (type $30) (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct $i8
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get_u $struct $i8
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (array.set $array
  ;; CHECK-NEXT:   (local.get $y)
  ;; CHECK-NEXT:   (i32.const 2)
  ;; CHECK-NEXT:   (i32.const 3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.get_u $array
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:    (i32.const 4)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.len
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $void
  ;; CHECK-NEXT:   (local.get $f)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $redundant-non-null-casts (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void))
    (drop
      (ref.as_non_null
        (ref.as_non_null
          (ref.as_non_null
            (local.get $x)
          )
        )
      )
    )
    (struct.set $struct 0
      (ref.as_non_null
        (local.get $x)
      )
      (i32.const 1)
    )
    (drop
      (struct.get_u $struct 0
        (ref.as_non_null
          (local.get $x)
        )
      )
    )
    (array.set $array
      (ref.as_non_null
        (local.get $y)
      )
      (i32.const 2)
      (i32.const 3)
    )
    (drop
      (array.get $array
        (ref.as_non_null
          (local.get $y)
        )
        (i32.const 4)
      )
    )
    (drop
      (array.len
        (ref.as_non_null
          (local.get $y)
        )
      )
    )
    (call_ref $void
      (ref.as_non_null
        (local.get $f)
      )
    )
  )

  ;; CHECK:      (func $get-eqref (type $31) (result eqref)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $get-eqref (result eqref)
    (unreachable)
  )

  ;; CHECK:      (func $ref-eq (type $10) (param $x eqref) (param $y eqref)
  ;; CHECK-NEXT:  (local $lx eqref)
  ;; CHECK-NEXT:  (local $ly eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $lx
  ;; CHECK-NEXT:   (call $get-eqref)
  ;; 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-eq (param $x eqref) (param $y eqref)
    (local $lx eqref)
    (local $ly eqref)
    ;; identical parameters are equal
    (drop
      (ref.eq
        (local.get $x)
        (local.get $x)
      )
    )
    ;; different ones might not be
    (drop
      (ref.eq
        (local.get $x)
        (local.get $y)
      )
    )
    ;; identical locals are
    (local.set $lx
      (call $get-eqref)
    )
    (drop
      (ref.eq
        (local.get $lx)
        (local.get $lx)
      )
    )
    ;; fallthroughs work ok (but we need --remove-unused-names so that we can
    ;; trivially tell that there are no breaks)
    (drop
      (ref.eq
        (block (result eqref)
          (nop)
          (local.get $x)
        )
        (block (result eqref)
          (nop)
          (drop
            (i32.const 10)
          )
          (nop)
          (local.get $x)
        )
      )
    )
  )

  ;; CHECK:      (func $nothing (type $5)
  ;; CHECK-NEXT: )
  (func $nothing)

  ;; CHECK:      (func $ref-eq-corner-cases (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (block (result eqref)
  ;; CHECK-NEXT:     (call $nothing)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (block (result eqref)
  ;; CHECK-NEXT:     (call $nothing)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (struct.new_default $struct)
  ;; CHECK-NEXT:    (struct.new_default $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.tee $x
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-eq-corner-cases (param $x eqref)
    ;; side effects prevent optimization
    (drop
      (ref.eq
        (block (result eqref)
          (call $nothing)
          (local.get $x)
        )
        (block (result eqref)
          (call $nothing)
          (local.get $x)
        )
      )
    )
    ;; allocation prevents optimization
    (drop
      (ref.eq
        (struct.new_default $struct)
        (struct.new_default $struct)
      )
    )
    ;; but irrelevant allocations do not prevent optimization
    (drop
      (ref.eq
        (block (result eqref)
          ;; an allocation that does not trouble us
          (drop
            (struct.new_default $struct)
          )
          (local.get $x)
        )
        (block (result eqref)
          (drop
            (struct.new_default $struct)
          )
          ;; add a nop to make the two inputs to ref.eq not structurally equal,
          ;; but in a way that does not matter (since only the value falling
          ;; out does)
          (nop)
          (local.get $x)
        )
      )
    )
    ;; a tee does not prevent optimization, as we can fold the tee and the get.
    (drop
      (ref.eq
        (local.tee $x
          (local.get $x)
        )
        (local.get $x)
      )
    )
  )

  ;; CHECK:      (func $ref-eq-ref-cast (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:    (ref.cast (ref null $struct)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-eq-ref-cast (param $x eqref)
    ;; it is almost valid to look through a cast, except that it might trap so
    ;; there is a side effect
    (drop
      (ref.eq
        (local.get $x)
        (ref.cast (ref null $struct)
          (local.get $x)
        )
      )
    )
  )

  ;; CHECK:      (func $flip-cast-of-as-non-null (type $19) (param $x anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $struct)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get_u $struct $i8
  ;; CHECK-NEXT:    (ref.cast (ref $struct)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref none))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref i31)
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $flip-cast-of-as-non-null (param $x anyref)
    (drop
      (ref.cast (ref $struct)
        ;; this can be folded into the outer cast, which checks for null too
        (ref.as_non_null
          (local.get $x)
        )
      )
    )
    (drop
      ;; an example of how this helps: the struct.get will trap on null anyhow
      (struct.get_u $struct 0
        (ref.cast (ref $struct)
          ;; this can be moved through the ref.cast null outward.
          (ref.as_non_null
            (local.get $x)
          )
        )
      )
    )
    ;; This will trap, so we can emit an unreachable.
    (drop
      (ref.cast (ref $struct)
        (ref.cast (ref i31)
          (local.get $x)
        )
      )
    )
  )
  ;; CHECK:      (func $flip-tee-of-as-non-null (type $19) (param $x anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (local.tee $x
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $flip-tee-of-as-non-null (param $x anyref)
    (drop
      (local.tee $x
        ;; this can be moved through the tee outward.
        (ref.as_non_null
          (local.get $x)
        )
      )
    )
  )

  ;; CHECK:      (func $flip-tee-of-as-non-null-non-nullable (type $32) (param $x (ref any)) (param $y anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $x
  ;; CHECK-NEXT:    (ref.as_non_null
  ;; CHECK-NEXT:     (local.get $y)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $flip-tee-of-as-non-null-non-nullable (param $x (ref any)) (param $y (ref null any))
    (drop
      (local.tee $x
        ;; this *cannnot* be moved through the tee outward, as the param is in
        ;; fact non-nullable, and we depend on the ref.as_non_null in order to
        ;; get a valid type to assign to it
        (ref.as_non_null
          (local.get $y)
        )
      )
    )
  )
  ;; CHECK:      (func $ternary-identical-arms (type $33) (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.is_null
  ;; CHECK-NEXT:    (if (result (ref null $struct))
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:     (then
  ;; CHECK-NEXT:      (local.get $y)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (else
  ;; CHECK-NEXT:      (local.get $z)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ternary-identical-arms (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct))
    (drop
      (if (result i32)
        (local.get $x)
        (then
          (ref.is_null (local.get $y))
        )
        (else
          (ref.is_null (local.get $z))
        )
      )
    )
  )
  ;; CHECK:      (func $select-identical-arms-but-side-effect (type $20) (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (select
  ;; CHECK-NEXT:    (struct.get_u $struct $i8
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (struct.get_u $struct $i8
  ;; CHECK-NEXT:     (local.get $y)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $z)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $select-identical-arms-but-side-effect (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32)
    (drop
      (select
        ;; the arms are equal but have side effects
        (struct.get_u $struct 0
          (local.get $x)
        )
        (struct.get_u $struct 0
          (local.get $y)
        )
        (local.get $z)
      )
    )
  )
  ;; CHECK:      (func $ternary-identical-arms-no-side-effect (type $34) (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get_u $struct $i8
  ;; CHECK-NEXT:    (select (result (ref $struct))
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:     (local.get $y)
  ;; CHECK-NEXT:     (local.get $z)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ternary-identical-arms-no-side-effect (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32)
    (drop
      (select
        ;; the arms are equal and as the params are non-null, there are no possible side effects
        (struct.get_u $struct 0
          (local.get $x)
        )
        (struct.get_u $struct 0
          (local.get $y)
        )
        (local.get $z)
      )
    )
  )
  ;; CHECK:      (func $if-identical-arms-with-side-effect (type $20) (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get_u $struct $i8
  ;; CHECK-NEXT:    (if (result (ref null $struct))
  ;; CHECK-NEXT:     (local.get $z)
  ;; CHECK-NEXT:     (then
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (else
  ;; CHECK-NEXT:      (local.get $y)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $if-identical-arms-with-side-effect (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32)
    (drop
      (if (result i32)
        (local.get $z)
        ;; the arms are equal and have side effects, but that is ok with an if
        ;; which only executes one side anyhow
        (then
          (struct.get_u $struct 0
            (local.get $x)
          )
        )
        (else
          (struct.get_u $struct 0
            (local.get $y)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $ref-cast-squared (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $struct)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-squared (param $x eqref)
    ;; Identical ref.casts can be folded together.
    (drop
      (ref.cast (ref null $struct)
        (ref.cast (ref null $struct)
          (local.get $x)
        )
      )
    )
  )
  ;; CHECK:      (func $ref-cast-squared-fallthrough (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (local $1 (ref null $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref null $struct))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.tee $x
  ;; CHECK-NEXT:      (local.tee $1
  ;; CHECK-NEXT:       (ref.cast (ref null $struct)
  ;; CHECK-NEXT:        (local.get $x)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-squared-fallthrough (param $x eqref)
    ;; A fallthrough in the middle does not prevent this optimization.
    (drop
      (ref.cast (ref null $struct)
        (local.tee $x
          (ref.cast (ref null $struct)
            (local.get $x)
          )
        )
      )
    )
  )
  ;; CHECK:      (func $ref-cast-cubed (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $struct)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-cubed (param $x eqref)
    ;; Three and more also work.
    (drop
      (ref.cast (ref null $struct)
        (ref.cast (ref null $struct)
          (ref.cast (ref null $struct)
            (local.get $x)
          )
        )
      )
    )
  )
  ;; CHECK:      (func $ref-cast-squared-different (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast nullref
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-squared-different (param $x eqref)
    ;; Different casts cannot be folded. We can emit a cast to null here, which
    ;; is the only possible thing that can pass through.
    (drop
      (ref.cast (ref null $struct)
        (ref.cast (ref null $empty)
          (local.get $x)
        )
      )
    )
  )

  ;; CHECK:      (func $ref-eq-null (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.is_null
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.is_null
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-eq-null (param $x eqref)
    ;; Equality to null can be done with ref.is_null.
    (drop
      (ref.eq
        (local.get $x)
        (ref.null eq)
      )
    )
    (drop
      (ref.eq
        (ref.null eq)
        (local.get $x)
      )
    )
    ;; Also check that we turn a comparison of two nulls into 1, using the rule
    ;; for comparing the same thing to itself (i.e., that we run that rule first
    ;; and not the check for one of them being null, which would require more
    ;; work afterwards).
    (drop
      (ref.eq
        (ref.null eq)
        (ref.null eq)
      )
    )
  )

  ;; CHECK:      (func $ref-eq-possible (type $10) (param $x eqref) (param $y eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (ref.cast (ref null $struct)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.cast (ref null $array)
  ;; CHECK-NEXT:     (local.get $y)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-eq-possible (param $x eqref) (param $y eqref)
    ;; These casts are to types that are incompatible. However, it is possible
    ;; they are both null, so we cannot optimize here.
    (drop
      (ref.eq
        (ref.cast (ref null $struct)
          (local.get $x)
        )
        (ref.cast (ref null $array)
          (local.get $y)
        )
      )
    )
  )

  ;; CHECK:      (func $ref-eq-impossible (type $10) (param $x eqref) (param $y eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref $struct)
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref null $array)
  ;; CHECK-NEXT:      (local.get $y)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref null $struct)
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref $array)
  ;; CHECK-NEXT:      (local.get $y)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref $struct)
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref $array)
  ;; CHECK-NEXT:      (local.get $y)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-eq-impossible (param $x eqref) (param $y eqref)
    ;; As above but cast one of them to non-null, which proves they cannot be
    ;; equal, and the result must be 0.
    (drop
      (ref.eq
        (ref.cast (ref $struct)
          (ref.as_non_null
            (local.get $x)
          )
        )
        (ref.cast (ref null $array)
          (local.get $y)
        )
      )
    )
    ;; As above but the cast is on the other one.
    (drop
      (ref.eq
        (ref.cast (ref null $struct)
          (local.get $x)
        )
        (ref.cast (ref $array)
          (ref.as_non_null
            (local.get $y)
          )
        )
      )
    )
    ;; As above but the cast is both.
    (drop
      (ref.eq
        (ref.cast (ref $struct)
          (ref.as_non_null
            (local.get $x)
          )
        )
        (ref.cast (ref $array)
          (ref.as_non_null
            (local.get $y)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $ref-eq-possible-b (type $10) (param $x eqref) (param $y eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (ref.cast (ref $A)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.cast (ref $B)
  ;; CHECK-NEXT:     (local.get $y)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (ref.cast (ref $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.cast (ref $A)
  ;; CHECK-NEXT:     (local.get $y)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-eq-possible-b (param $x eqref) (param $y eqref)
    ;; As above but the casts are to things that are compatible, since B is a
    ;; subtype of A, so we cannot optimize.
    (drop
      (ref.eq
        (ref.cast (ref $A)
          (local.get $x)
        )
        (ref.cast (ref $B)
          (local.get $y)
        )
      )
    )
    ;; As above but flipped.
    (drop
      (ref.eq
        (ref.cast (ref $B)
          (local.get $x)
        )
        (ref.cast (ref $A)
          (local.get $y)
        )
      )
    )
  )

  ;; CHECK:      (func $hoist-LUB-danger (type $35) (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32)
  ;; CHECK-NEXT:  (if (result i32)
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (struct.get $B 1
  ;; CHECK-NEXT:     (local.get $b)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (else
  ;; CHECK-NEXT:    (struct.get $C 1
  ;; CHECK-NEXT:     (local.get $c)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $hoist-LUB-danger (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32)
    ;; In nominal typing, if we hoist the struct.get out of the if, then the if
    ;; will have a new type, $A, but $A does not have field "1" which would be an
    ;; error. We disallow subtyping for this reason.
    ;;
    ;; We also disallow subtyping in structural typing, even though atm there
    ;; might not be a concrete risk there: future instructions might introduce
    ;; such things, and it reduces the complexity of having differences with
    ;; nominal typing.
    (if (result i32)
      (local.get $x)
      (then
        (struct.get $B 1
          (local.get $b)
        )
      )
      (else
        (struct.get $C 1
          (local.get $c)
        )
      )
    )
  )

  ;; CHECK:      (func $incompatible-cast-of-non-null (type $36) (param $struct (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref none))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $struct)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $incompatible-cast-of-non-null (param $struct (ref $struct))
    (drop
      (ref.cast (ref $array)
        (local.get $struct)
      )
    )
  )

  ;; CHECK:      (func $incompatible-cast-of-null (type $11) (param $x (ref null $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $incompatible-cast-of-null (param $x (ref null $struct))
    (drop
      (ref.cast (ref $array)
        ;; The child is null, so the cast will trap. Replace it with an
        ;; unreachable.
        (ref.null none)
      )
    )
    (drop
      (ref.cast (ref $array)
        ;; Even though the child type is non-null, it is still valid to do this
        ;; transformation. In practice this code will trap before getting to our
        ;; new unreachable.
        (ref.as_non_null
          (local.get $x)
        )
      )
    )
  )

  ;; CHECK:      (func $incompatible-cast-of-unknown (type $11) (param $struct (ref null $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast nullref
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $incompatible-cast-of-unknown (param $struct (ref null $struct))
    (drop
      (ref.cast (ref null $array)
        (local.get $struct)
      )
    )
  )

  ;; CHECK:      (func $incompatible-test (type $11) (param $struct (ref null $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $struct)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.is_null
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (local.get $struct)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $incompatible-test (param $struct (ref null $struct))
    (drop
      ;; This test will definitely fail, so we can turn it into 0.
      (ref.test (ref $array)
        (local.get $struct)
      )
    )
    (drop
      ;; But this one might succeed due to a null, so don't optimize it away.
      ;; We can however change it from ref.test to ref.is_null, as a null is the
      ;; only possible way this will succeed.
      (ref.test (ref null $array)
        (local.get $struct)
      )
    )
    (drop
      ;; This one cannot succeed due to a null, so optimize it.
      (ref.test (ref null $array)
        (ref.as_non_null
          (local.get $struct)
        )
      )
    )
  )

  ;; CHECK:      (func $subtype-compatible (type $21) (param $A (ref null $A)) (param $B (ref null $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $B)
  ;; CHECK-NEXT:    (local.get $A)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.eqz
  ;; CHECK-NEXT:    (ref.is_null
  ;; CHECK-NEXT:     (local.get $B)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $B)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (local.get $B)
  ;; 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:     (ref.as_non_null
  ;; CHECK-NEXT:      (local.get $B)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $subtype-compatible (param $A (ref null $A)) (param $B (ref null $B))
    (drop
      ;; B is a subtype of A, so this can work.
      (ref.test (ref $B)
        (local.get $A)
      )
    )
    (drop
      ;; The other direction can work too. It will only fail if the input is a
      ;; null, so we can switch to checking that.
      (ref.test (ref $A)
        (local.get $B)
      )
    )
    (drop
      ;; If the test is nullable, this will succeed.
      (ref.test (ref null $A)
        (local.get $B)
      )
    )
    (drop
      ;; We will also succeed if the input is non-nullable.
      (ref.test (ref $A)
        (ref.as_non_null
          (local.get $B)
        )
      )
    )
    (drop
      ;; Or if the test is nullable and the input is non-nullable.
      (ref.test (ref null $A)
        (ref.as_non_null
          (local.get $B)
        )
      )
    )
  )

  ;; CHECK:      (func $compatible-test-separate-fallthrough (type $12) (param $eqref eqref) (result i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $eqref
  ;; CHECK-NEXT:    (block (result eqref)
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (block (result eqref)
  ;; CHECK-NEXT:       (ref.cast i31ref
  ;; CHECK-NEXT:        (local.get $eqref)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (i32.const 1)
  ;; CHECK-NEXT: )
  (func $compatible-test-separate-fallthrough (param $eqref eqref) (result i32)
    (ref.test (ref i31)
      (local.tee $eqref
        (block (result eqref)
          ;; Prove that the value is non-nullable
          (ref.as_non_null
            (block (result eqref)
              ;; Prove that the value is an i31
              (ref.cast i31ref
                (local.get $eqref)
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $improvable-test-separate-fallthrough (type $12) (param $eqref eqref) (result i32)
  ;; CHECK-NEXT:  (ref.test (ref i31)
  ;; CHECK-NEXT:   (block (result eqref)
  ;; CHECK-NEXT:    (ref.as_non_null
  ;; CHECK-NEXT:     (local.get $eqref)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $improvable-test-separate-fallthrough (param $eqref eqref) (result i32)
    ;; There is no need to admit null here, but we don't know whether we have an i31.
    (ref.test i31ref
      (block (result eqref)
        ;; Prove that the value is non-nullable
        (ref.as_non_null
          (local.get $eqref)
        )
      )
    )
  )

  ;; CHECK:      (func $incompatible-test-separate-fallthrough (type $12) (param $eqref eqref) (result i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $eqref
  ;; CHECK-NEXT:    (block (result eqref)
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (block (result eqref)
  ;; CHECK-NEXT:       (ref.cast i31ref
  ;; CHECK-NEXT:        (local.get $eqref)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (i32.const 0)
  ;; CHECK-NEXT: )
  (func $incompatible-test-separate-fallthrough (param $eqref eqref) (result i32)
    (ref.test structref
      (local.tee $eqref
        (block (result eqref)
          ;; Prove that the value is non-nullable
          (ref.as_non_null
            (block (result eqref)
              ;; Prove that the value is an i31
              (ref.cast i31ref
                (local.get $eqref)
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $incompatible-test-heap-types-nonnullable (type $7) (param $anyref anyref) (result anyref)
  ;; CHECK-NEXT:  (block $outer (result anyref)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block (result i32)
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (block (result anyref)
  ;; CHECK-NEXT:       (br_on_cast_fail $outer anyref i31ref
  ;; CHECK-NEXT:        (block (result anyref)
  ;; CHECK-NEXT:         (br_on_cast_fail $outer anyref structref
  ;; CHECK-NEXT:          (local.get $anyref)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.get $anyref)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $incompatible-test-heap-types-nonnullable (param $anyref anyref) (result anyref)
    (block $outer (result anyref)
      (drop
        ;; The value cannot be both i31 and struct, so it must be null and we
        ;; can optimize to 0.
        (ref.test (ref any)
          (block (result anyref)
            (br_on_cast_fail $outer anyref i31ref
              (block (result anyref)
                (br_on_cast_fail $outer anyref structref
                  (local.get $anyref)
                )
              )
            )
          )
        )
      )
      (local.get $anyref)
    )
  )

  ;; CHECK:      (func $incompatible-test-heap-types-nullable (type $7) (param $anyref anyref) (result anyref)
  ;; CHECK-NEXT:  (block $outer (result anyref)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block (result i32)
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (block (result anyref)
  ;; CHECK-NEXT:       (br_on_cast_fail $outer anyref i31ref
  ;; CHECK-NEXT:        (block (result anyref)
  ;; CHECK-NEXT:         (br_on_cast_fail $outer anyref structref
  ;; CHECK-NEXT:          (local.get $anyref)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.get $anyref)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $incompatible-test-heap-types-nullable (param $anyref anyref) (result anyref)
    (block $outer (result anyref)
      (drop
        ;; Same as above, but now we allow null, so we optimize to 1.
        (ref.test anyref
          (block (result anyref)
            (br_on_cast_fail $outer anyref i31ref
              (block (result anyref)
                (br_on_cast_fail $outer anyref structref
                  (local.get $anyref)
                )
              )
            )
          )
        )
      )
      (local.get $anyref)
    )
  )

  ;; CHECK:      (func $incompatible-test-heap-types-unreachable (type $7) (param $anyref anyref) (result anyref)
  ;; CHECK-NEXT:  (block $outer (result anyref)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block (result i32)
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (block (result anyref)
  ;; CHECK-NEXT:       (br_on_cast_fail $outer anyref (ref i31)
  ;; CHECK-NEXT:        (block (result anyref)
  ;; CHECK-NEXT:         (br_on_cast_fail $outer anyref structref
  ;; CHECK-NEXT:          (local.get $anyref)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.get $anyref)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $incompatible-test-heap-types-unreachable (param $anyref anyref) (result anyref)
    (block $outer (result anyref)
      (drop
        ;; Same as above, but now we know the value must be non-null and bottom,
        ;; so it cannot exist at all.
        (ref.test anyref
          (block (result anyref)
            (br_on_cast_fail $outer anyref (ref i31)
              (block (result anyref)
                (br_on_cast_fail $outer anyref structref
                  (local.get $anyref)
                )
              )
            )
          )
        )
      )
      (local.get $anyref)
    )
  )

  ;; CHECK:      (func $ref.test-unreachable (type $37) (param $A (ref null $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $A)
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref null $A)
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref.test-unreachable (param $A (ref null $A))
    (drop
      ;; We should ignore unreachable ref.tests and not try to compare their
      ;; HeapTypes.
      (ref.test (ref $A)
        (unreachable)
      )
    )
    (drop
      (ref.test (ref null $A)
        (unreachable)
      )
    )
  )

  ;; CHECK:      (func $ref-cast-static-null (type $5)
  ;; CHECK-NEXT:  (local $a (ref null $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.null none)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.tee $a
  ;; CHECK-NEXT:      (ref.null none)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.tee $a
  ;; CHECK-NEXT:      (ref.null none)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result nullref)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result nullref)
  ;; CHECK-NEXT:      (ref.cast nullref
  ;; CHECK-NEXT:       (local.get $a)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result nullref)
  ;; CHECK-NEXT:      (ref.cast nullref
  ;; CHECK-NEXT:       (local.get $a)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-static-null
    (local $a (ref null $A))
    ;; Casting nulls results in a null.
    (drop
      (ref.cast (ref null $A)
        (ref.null none)
      )
    )
    ;; A fallthrough works too.
    (drop
      (ref.cast (ref null $B)
        (local.tee $a
          (ref.null none)
        )
      )
    )
    ;; A non-null cast of a falling-though null will trap.
    (drop
      (ref.cast (ref $A)
        (local.tee $a
          (ref.null none)
        )
      )
    )
    ;; The prior two examples work even if the fallthrough is only later proven
    ;; to be null.
    (drop
      (ref.cast (ref null $B)
        (block (result (ref null $A))
          (ref.cast nullref
            (local.get $a)
          )
        )
      )
    )
    (drop
      (ref.cast (ref $B)
        (block (result (ref null $A))
          (ref.cast nullref
            (local.get $a)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $ref-cast-static-general (type $21) (param $a (ref null $A)) (param $b (ref null $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $a)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $b)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B)
  ;; CHECK-NEXT:    (local.get $a)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $a
  ;; CHECK-NEXT:    (local.get $a)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-static-general (param $a (ref null $A)) (param $b (ref null $B))
    ;; In the general case, a static cast of something simply succeeds if the
    ;; type is a subtype.
    (drop
      (ref.cast (ref null $A)
        (local.get $a)
      )
    )
    (drop
      (ref.cast (ref null $A)
        (local.get $b)
      )
    )
    ;; This is the only one that we cannot know for sure will succeed.
    (drop
      (ref.cast (ref null $B)
        (local.get $a)
      )
    )
    ;; A fallthrough works too.
    (drop
      (ref.cast (ref null $A)
        (local.tee $a
          (local.get $a)
        )
      )
    )
  )

  ;; CHECK:      (func $ref-cast-static-squared (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $A)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-static-squared (param $x eqref)
    ;; Identical ref.casts can be folded together.
    (drop
      (ref.cast (ref null $A)
        (ref.cast (ref null $A)
          (local.get $x)
        )
      )
    )
    ;; When subtypes exist, we only need the stricter one.
    (drop
      (ref.cast (ref null $A)
        (ref.cast (ref null $B)
          (local.get $x)
        )
      )
    )
    (drop
      (ref.cast (ref null $B)
        (ref.cast (ref null $A)
          (local.get $x)
        )
      )
    )
  )

  ;; CHECK:      (func $ref-cast-static-many (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B-child)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B-child)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B-child)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B-child)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B-child)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B-child)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-static-many (param $x eqref)
    ;; We should optimize a long sequence of static casts when we can. All six
    ;; orderings of these casts should collapse into the strictest one.
    (drop
      (ref.cast (ref null $A)
        (ref.cast (ref null $B)
          (ref.cast (ref null $B-child)
            (local.get $x)
          )
        )
      )
    )
    (drop
      (ref.cast (ref null $A)
        (ref.cast (ref null $B-child)
          (ref.cast (ref null $B)
            (local.get $x)
          )
        )
      )
    )
    (drop
      (ref.cast (ref null $B)
        (ref.cast (ref null $A)
          (ref.cast (ref null $B-child)
            (local.get $x)
          )
        )
      )
    )
    (drop
      (ref.cast (ref null $B)
        (ref.cast (ref null $B-child)
          (ref.cast (ref null $A)
            (local.get $x)
          )
        )
      )
    )
    (drop
      (ref.cast (ref null $B-child)
        (ref.cast (ref null $A)
          (ref.cast (ref null $B)
            (local.get $x)
          )
        )
      )
    )
    (drop
      (ref.cast (ref null $B-child)
        (ref.cast (ref null $B)
          (ref.cast (ref null $A)
            (local.get $x)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $ref-cast-static-very-many (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B-child)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-static-very-many (param $x eqref)
    ;; We should optimize an arbitrarily-long long sequence of static casts.
    (drop
      (ref.cast (ref null $A)
        (ref.cast (ref null $B)
          (ref.cast (ref null $B-child)
            (ref.cast (ref null $A)
              (ref.cast (ref null $A)
                (ref.cast (ref null $B-child)
                  (ref.cast (ref null $B-child)
                    (ref.cast (ref null $B)
                      (ref.cast (ref null $B)
                        (ref.cast (ref null $B)
                          (ref.cast (ref null $B-child)
                            (ref.cast (ref null $A)
                              (local.get $x)
                            )
                          )
                        )
                      )
                    )
                  )
                )
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $ref-cast-static-fallthrough-remaining (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref null $B))
  ;; CHECK-NEXT:    (call $ref-cast-static-fallthrough-remaining
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.cast (ref null $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-static-fallthrough-remaining (param $x eqref)
    (drop
      (ref.cast (ref null $A)
        (block (result (ref null $B))
          ;; Additional contents in between redundant casts must be preserved.
          ;; That is, when we see that the casts are redundant, by seeing that
          ;; the fallthrough value reaching the outer cast is already cast, we
          ;; can avoid a duplicate cast, but we do still need to keep any code
          ;; in the middle, as it may have side effects.
          ;;
          ;; In this first testcase, the outer cast is not needed as the inside
          ;; is already a more specific type.
          (call $ref-cast-static-fallthrough-remaining
            (local.get $x)
          )
          (ref.cast (ref null $B)
            (local.get $x)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $ref-cast-static-fallthrough-remaining-child (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref null $B)
  ;; CHECK-NEXT:    (block (result eqref)
  ;; CHECK-NEXT:     (call $ref-cast-static-fallthrough-remaining-child
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (ref.cast (ref null $A)
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-static-fallthrough-remaining-child (param $x eqref)
    (drop
      ;; As above, but with $A and $B flipped. Now the inner cast is not needed.
      ;; However, we do not remove it, as it may be necessary for validation,
      ;; and we hope other opts help out here.
      (ref.cast (ref null $B)
        (block (result eqref)
          (call $ref-cast-static-fallthrough-remaining-child
            (local.get $x)
          )
          (ref.cast (ref null $A)
            (local.get $x)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $ref-cast-static-fallthrough-remaining-impossible (type $22) (param $x (ref eq))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result (ref $struct))
  ;; CHECK-NEXT:      (call $ref-cast-static-fallthrough-remaining-impossible
  ;; CHECK-NEXT:       (local.get $x)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (ref.cast (ref $struct)
  ;; CHECK-NEXT:       (local.get $x)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-static-fallthrough-remaining-impossible (param $x (ref eq))
    (drop
      ;; As above, but with an impossible cast of an array to a struct. The
      ;; block with the side effects and the inner cast must be kept around and
      ;; dropped, and then we replace the outer cast with an unreachable.
      (ref.cast (ref $array)
        (block (result (ref eq))
          (call $ref-cast-static-fallthrough-remaining-impossible
            (local.get $x)
          )
          (ref.cast (ref $struct)
            (local.get $x)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $ref-cast-static-fallthrough-remaining-nonnull (type $22) (param $x (ref eq))
  ;; CHECK-NEXT:  (local $1 (ref $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $B))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result (ref $B))
  ;; CHECK-NEXT:      (call $ref-cast-static-fallthrough-remaining
  ;; CHECK-NEXT:       (local.get $x)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (local.tee $1
  ;; CHECK-NEXT:       (ref.cast (ref $B)
  ;; CHECK-NEXT:        (local.get $x)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-static-fallthrough-remaining-nonnull (param $x (ref eq))
    ;; The input is non-nullable here, and the middle block is of a simpler type
    ;; than either the parent or the child. This checks that we do not
    ;; mis-optimize this case: The outer cast is not needed, so we can optimize
    ;; it out, but we have to be careful not to remove any side effects.
    (drop
      (ref.cast (ref $A)
        (block (result (ref eq))
          (call $ref-cast-static-fallthrough-remaining
            (local.get $x)
          )
          (ref.cast (ref $B)
            (local.get $x)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $ref-cast-static-squared-impossible (type $4) (param $x eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast nullref
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref none))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref $array)
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref none))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref $array)
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref none))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.cast (ref $array)
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-static-squared-impossible (param $x eqref)
    ;; Impossible casts will trap unless the input is null. Only the first one
    ;; here, which lets a null get through, will not trap.
    (drop
      (ref.cast (ref null $struct)
        (ref.cast (ref null $array)
          (local.get $x)
        )
      )
    )
    (drop
      (ref.cast (ref $struct)
        (ref.cast (ref null $array)
          (local.get $x)
        )
      )
    )
    (drop
      (ref.cast (ref null $struct)
        (ref.cast (ref $array)
          (local.get $x)
        )
      )
    )
    (drop
      (ref.cast (ref $struct)
        (ref.cast (ref $array)
          (ref.as_non_null (local.get $x))
        )
      )
    )
  )

  ;; CHECK:      (func $ref-test-static-same-type (type $23) (param $nullable (ref null $A)) (param $non-nullable (ref $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.eqz
  ;; CHECK-NEXT:    (ref.is_null
  ;; CHECK-NEXT:     (local.get $nullable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $non-nullable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-test-static-same-type (param $nullable (ref null $A)) (param $non-nullable (ref $A))
    ;; A nullable value cannot be optimized here even though it is the same
    ;; type. But we can at least use !ref.is_null rather than ref.test.
    (drop
      (ref.test (ref $A)
        (local.get $nullable)
      )
    )
    ;; But if it is non-nullable, it must succeed.
    (drop
      (ref.test (ref $A)
        (local.get $non-nullable)
      )
    )
  )

  ;; CHECK:      (func $ref-test-static-subtype (type $13) (param $nullable (ref null $B)) (param $non-nullable (ref $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.eqz
  ;; CHECK-NEXT:    (ref.is_null
  ;; CHECK-NEXT:     (local.get $nullable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $non-nullable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-test-static-subtype (param $nullable (ref null $B)) (param $non-nullable (ref $B))
    ;; As above, but the input is a subtype, so the same things happen.
    (drop
      (ref.test (ref $A)
        (local.get $nullable)
      )
    )
    (drop
      (ref.test (ref $A)
        (local.get $non-nullable)
      )
    )
  )

  ;; CHECK:      (func $ref-test-static-supertype (type $23) (param $nullable (ref null $A)) (param $non-nullable (ref $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $B)
  ;; CHECK-NEXT:    (local.get $nullable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $B)
  ;; CHECK-NEXT:    (local.get $non-nullable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-test-static-supertype (param $nullable (ref null $A)) (param $non-nullable (ref $A))
    ;; As above, but the input is a supertype. We can't know at compile time
    ;; what to do here.
    (drop
      (ref.test (ref $B)
        (local.get $nullable)
      )
    )
    (drop
      (ref.test (ref $B)
        (local.get $non-nullable)
      )
    )
  )

  ;; CHECK:      (func $ref-test-static-impossible (type $38) (param $nullable (ref null $array)) (param $non-nullable (ref $array))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $nullable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $non-nullable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-test-static-impossible (param $nullable (ref null $array)) (param $non-nullable (ref $array))
    ;; Testing an impossible cast will definitely fail.
    (drop
      (ref.test (ref $struct)
        (local.get $nullable)
      )
    )
    (drop
      (ref.test (ref $struct)
        (local.get $non-nullable)
      )
    )
  )

  ;; CHECK:      (func $ref-boolean (type $10) (param $x eqref) (param $y eqref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.eq
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.is_null
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $A)
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-boolean (param $x eqref) (param $y eqref)
    ;; ref.eq returns a boolean, so &1 on it is not needed.
    (drop
      (i32.and
        (ref.eq
          (local.get $x)
          (local.get $y)
        )
        (i32.const 1)
      )
    )
    ;; likewise ref.is and ref.test
    (drop
      (i32.and
        (ref.is_null
          (local.get $x)
        )
        (i32.const 1)
      )
    )
    (drop
      (i32.and
        (ref.test (ref $A)
          (local.get $x)
        )
        (i32.const 1)
      )
    )
  )

  ;; CHECK:      (func $impossible (type $39) (result (ref none))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $impossible (result (ref none))
    (unreachable)
  )

  ;; CHECK:      (func $bottom-type-accessors (type $40) (param $bot (ref none)) (param $null nullref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (call $impossible)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $bottom-type-accessors (param $bot (ref none)) (param $null nullref)
    (drop
      (struct.get $A 0
        (local.get $bot)
      )
    )
    (drop
      (array.get $array
        (local.get $null)
        (i32.const 0)
      )
    )
    (struct.set $A 0
      (ref.null none)
      (i32.const 42)
    )
    (array.set $array
      (call $impossible)
      (i32.const 1)
      (i32.const 2)
    )
    (call_ref $void
      (ref.null nofunc)
    )
  )

  ;; CHECK:      (func $ref-cast-heap-type (type $13) (param $null-b (ref null $B)) (param $b (ref $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $b)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (local.get $null-b)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $b)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $null-b)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-heap-type (param $null-b (ref null $B)) (param $b (ref $B))
    ;; We are casting a heap type to a supertype, which always succeeds. However
    ;; we need to check for nullability.

    ;; Non-nullable casts. When the input is non-nullable we must succeed.
    (drop
      (ref.cast (ref $A)
        (local.get $b)
      )
    )
    ;; When the input can be null, we might fail if it is a null. But we can
    ;; switch to checking only that.
    (drop
      (ref.cast (ref $A)
        (local.get $null-b)
      )
    )

    ;; Null casts. Both of these must succeed.
    (drop
      (ref.cast (ref null $A)
        (local.get $b)
      )
    )
    (drop
      (ref.cast (ref null $A)
        (local.get $null-b)
      )
    )
  )

  ;; CHECK:      (func $ref-cast-heap-type-incompatible (type $13) (param $null-b (ref null $B)) (param $b (ref $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref none))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $b)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref none))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $null-b)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref none))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $b)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast nullref
  ;; CHECK-NEXT:    (local.get $null-b)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref-cast-heap-type-incompatible (param $null-b (ref null $B)) (param $b (ref $B))
    ;; As above, but replacing $A with $struct. Now we have two incompatible
    ;; types, $B and $struct, so the only possible way the cast succeeds is if
    ;; the cast allows null and the input is a null.
    (drop
      (ref.cast (ref $struct)
        (local.get $b)
      )
    )
    (drop
      (ref.cast (ref $struct)
        (local.get $null-b)
      )
    )
    (drop
      (ref.cast (ref null $struct)
        (local.get $b)
      )
    )
    ;; This last case is the only one that can succeed. We turn it into a cast
    ;; to a null.
    (drop
      (ref.cast (ref null $struct)
        (local.get $null-b)
      )
    )
  )

  ;; CHECK:      (func $compatible-cast-separate-fallthrough (type $24) (param $eqref eqref) (result (ref i31))
  ;; CHECK-NEXT:  (local $1 i31ref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $eqref
  ;; CHECK-NEXT:    (block (result eqref)
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (block (result eqref)
  ;; CHECK-NEXT:       (local.tee $1
  ;; CHECK-NEXT:        (ref.cast i31ref
  ;; CHECK-NEXT:         (local.get $eqref)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (ref.as_non_null
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $compatible-cast-separate-fallthrough (param $eqref eqref) (result (ref i31))
    ;; This cast will succeed even though no individual fallthrough value is sufficiently refined.
    (ref.cast (ref i31)
      (local.tee $eqref
        (block (result eqref)
          ;; Prove that the value is non-nullable
          (ref.as_non_null
            (block (result eqref)
              ;; Prove that the value is an i31
              (ref.cast i31ref
                (local.get $eqref)
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $compatible-cast-fallthrough-null-check (type $24) (param $eqref eqref) (result (ref i31))
  ;; CHECK-NEXT:  (local $1 i31ref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $eqref
  ;; CHECK-NEXT:    (block (result eqref)
  ;; CHECK-NEXT:     (local.tee $1
  ;; CHECK-NEXT:      (ref.cast i31ref
  ;; CHECK-NEXT:       (local.get $eqref)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (ref.as_non_null
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $compatible-cast-fallthrough-null-check (param $eqref eqref) (result (ref i31))
    ;; Similar to above, but now we no longer know whether the value going into
    ;; the cast is null or not.
    (ref.cast (ref i31)
      (local.tee $eqref
        (block (result eqref)
          ;; Prove that the value is an i31
          (ref.cast i31ref
            (local.get $eqref)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $compatible-cast-separate-fallthrough-multiple-options-1 (type $25) (param $eqref eqref) (result (ref eq))
  ;; CHECK-NEXT:  (local $1 i31ref)
  ;; CHECK-NEXT:  (block $outer (result (ref eq))
  ;; CHECK-NEXT:   (block (result (ref i31))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.tee $eqref
  ;; CHECK-NEXT:      (block (result eqref)
  ;; CHECK-NEXT:       (local.tee $1
  ;; CHECK-NEXT:        (br_on_cast_fail $outer eqref i31ref
  ;; CHECK-NEXT:         (block (result eqref)
  ;; CHECK-NEXT:          (ref.as_non_null
  ;; CHECK-NEXT:           (block (result eqref)
  ;; CHECK-NEXT:            (ref.cast i31ref
  ;; CHECK-NEXT:             (local.get $eqref)
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (ref.as_non_null
  ;; CHECK-NEXT:     (local.get $1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $compatible-cast-separate-fallthrough-multiple-options-1
      (param $eqref eqref) (result (ref eq))
    ;; There are multiple "best" values we could tee and propagate. Choose the
    ;; shallowest.
    (block $outer (result (ref eq))
      (ref.cast (ref i31)
        (local.tee $eqref
          (block (result eqref)
            ;; Prove that the value is an i31 a second time. This one will be
            ;; propagated.
            (br_on_cast_fail $outer eqref i31ref
              (block (result eqref)
                ;; Prove that the value is non-nullable
                (ref.as_non_null
                  (block (result eqref)
                    ;; Prove that the value is an i31
                    (ref.cast i31ref
                      (local.get $eqref)
                    )
                  )
                )
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $compatible-cast-separate-fallthrough-multiple-options-2 (type $25) (param $eqref eqref) (result (ref eq))
  ;; CHECK-NEXT:  (local $1 (ref i31))
  ;; CHECK-NEXT:  (block $outer (result (ref eq))
  ;; CHECK-NEXT:   (block (result (ref i31))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.tee $eqref
  ;; CHECK-NEXT:      (block (result eqref)
  ;; CHECK-NEXT:       (br_on_cast_fail $outer eqref i31ref
  ;; CHECK-NEXT:        (block (result eqref)
  ;; CHECK-NEXT:         (ref.as_non_null
  ;; CHECK-NEXT:          (block (result eqref)
  ;; CHECK-NEXT:           (local.tee $1
  ;; CHECK-NEXT:            (ref.cast (ref i31)
  ;; CHECK-NEXT:             (local.get $eqref)
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $compatible-cast-separate-fallthrough-multiple-options-2
      (param $eqref eqref) (result (ref eq))
    (block $outer (result (ref eq))
      (ref.cast (ref i31)
        (local.tee $eqref
          (block (result eqref)
            ;; Prove that the value is an i31 a second time, but not that it is
            ;; non-null at the same time.
            (br_on_cast_fail $outer eqref i31ref
              (block (result eqref)
                ;; Prove that the value is non-nullable but not i31.
                (ref.as_non_null
                  (block (result eqref)
                    ;; Now this is non-nullable and an exact match, so we
                    ;; propagate this one.
                    (ref.cast (ref i31)
                      (local.get $eqref)
                    )
                  )
                )
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $incompatible-cast-separate-fallthrough (type $41) (param $eqref eqref) (result structref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $eqref
  ;; CHECK-NEXT:    (block (result (ref i31))
  ;; CHECK-NEXT:     (ref.as_non_null
  ;; CHECK-NEXT:      (block (result i31ref)
  ;; CHECK-NEXT:       (ref.cast i31ref
  ;; CHECK-NEXT:        (local.get $eqref)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $incompatible-cast-separate-fallthrough (param $eqref eqref) (result structref)
    (ref.cast structref
      (local.tee $eqref
        (block (result eqref)
          ;; Prove that the value is non-nullable
          (ref.as_non_null
            (block (result eqref)
              ;; Prove that the value is an i31
              (ref.cast i31ref
                (local.get $eqref)
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $incompatible-cast-heap-types-nonnullable (type $7) (param $anyref anyref) (result anyref)
  ;; CHECK-NEXT:  (block $outer (result (ref any))
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result nullref)
  ;; CHECK-NEXT:      (br_on_cast_fail $outer structref nullref
  ;; CHECK-NEXT:       (block (result structref)
  ;; CHECK-NEXT:        (br_on_cast_fail $outer anyref structref
  ;; CHECK-NEXT:         (local.get $anyref)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $incompatible-cast-heap-types-nonnullable (param $anyref anyref) (result anyref)
    (block $outer (result anyref)
      ;; The value cannot be both an i31 and a struct, so it must be null, so
      ;; the cast will fail.
      (ref.cast (ref struct)
        (block (result anyref)
          (br_on_cast_fail $outer anyref i31ref
            (block (result anyref)
              (br_on_cast_fail $outer anyref structref
                (local.get $anyref)
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $incompatible-cast-heap-types-nullable (type $7) (param $anyref anyref) (result anyref)
  ;; CHECK-NEXT:  (block $outer (result anyref)
  ;; CHECK-NEXT:   (ref.cast nullref
  ;; CHECK-NEXT:    (block (result nullref)
  ;; CHECK-NEXT:     (br_on_cast_fail $outer structref nullref
  ;; CHECK-NEXT:      (block (result structref)
  ;; CHECK-NEXT:       (br_on_cast_fail $outer anyref structref
  ;; CHECK-NEXT:        (local.get $anyref)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $incompatible-cast-heap-types-nullable (param $anyref anyref) (result anyref)
    (block $outer (result anyref)
      ;; As above, but now the cast might succeed because we allow null.
      (ref.cast structref
        (block (result anyref)
          (br_on_cast_fail $outer anyref i31ref
            (block (result anyref)
              (br_on_cast_fail $outer anyref structref
                (local.get $anyref)
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $incompatible-cast-heap-types-unreachable (type $7) (param $anyref anyref) (result anyref)
  ;; CHECK-NEXT:  (block $outer (result anyref)
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result (ref none))
  ;; CHECK-NEXT:      (br_on_cast_fail $outer structref (ref none)
  ;; CHECK-NEXT:       (block (result structref)
  ;; CHECK-NEXT:        (br_on_cast_fail $outer anyref structref
  ;; CHECK-NEXT:         (local.get $anyref)
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $incompatible-cast-heap-types-unreachable (param $anyref anyref) (result anyref)
    (block $outer (result anyref)
      ;; As above, but now we know the value is not null, so the cast is unreachable.
      (ref.cast structref
        (block (result anyref)
          (br_on_cast_fail $outer anyref (ref i31)
            (block (result anyref)
              (br_on_cast_fail $outer anyref structref
                (local.get $anyref)
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $as_of_unreachable (type $42) (result (ref $A))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $as_of_unreachable (result (ref $A))
    ;; The cast will definitely fail, so we can turn it into an unreachable. The
    ;; ref.as must then ignore the unreachable input and not error on trying to
    ;; infer anything about it.
    (ref.as_non_null
      (ref.cast (ref $A)
        (ref.null none)
      )
    )
  )

  ;; CHECK:      (func $cast-internalized-extern (type $43) (param $externref externref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $A)
  ;; CHECK-NEXT:    (any.convert_extern
  ;; CHECK-NEXT:     (local.get $externref)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $cast-internalized-extern (param $externref externref)
    ;; We cannot optimize this cast, and in particular we should not treat the
    ;; externref as falling through to the cast and incorrectly inferring that
    ;; the cast cannot succeed.
    (drop
      (ref.cast (ref $A)
        (any.convert_extern
          (local.get $externref)
        )
      )
    )
  )

  ;; CHECK:      (func $struct.set.null.fallthrough (type $5)
  ;; CHECK-NEXT:  (local $temp (ref null $struct))
  ;; CHECK-NEXT:  (block ;; (replaces unreachable StructSet we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.tee $temp
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 100)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $struct.set.null.fallthrough
    (local $temp (ref null $struct))
    ;; The value falling through the tee shows the struct.set will trap. We can
    ;; append an unreachable after it. While doing so we must not emit a drop of
    ;; the struct.set (which would be valid for a struct.get etc.).
    (struct.set $struct 0
      (local.tee $temp
        (ref.as_non_null
          (ref.null none)
        )
      )
      (i32.const 100)
    )
  )

  ;; CHECK:      (func $set.array.null (type $5)
  ;; CHECK-NEXT:  (local $temp (ref none))
  ;; CHECK-NEXT:  (block ;; (replaces unreachable ArraySet we can't emit)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.tee $temp
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $set.array.null
    (local $temp (ref none))

    ;; The cast of none will be inferred to be an unreachable. That does not
    ;; propagate through the tee during this pass, however, as it only
    ;; happens during the refinalize at the very end. We must be careful not to
    ;; hit an internal error while processing the array.set, as its reference's
    ;; fallthrough value has null type and not array type.
    (array.set $array
      (local.tee $temp
        (ref.as_non_null
          (ref.null none)
        )
      )
      (i32.const 2)
      (i32.const 3)
    )
  )

  ;; CHECK:      (func $refinalize.select.arm (type $void)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $void2)
  ;; CHECK-NEXT:    (ref.func $refinalize.select.arm)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $refinalize.select.arm (type $void)
    ;; Pick one of the two select sides using the condition. This changes the
    ;; type (the arms are more refined than the declared type), so we must
    ;; refinalize or we'll error.
    (drop
      (ref.cast (ref null $void2)
        (select (result (ref null $void))
          (ref.func $refinalize.select.arm)
          (ref.func $refinalize.select.arm)
          (i32.const 1)
        )
      )
    )
  )

  ;; CHECK:      (func $refinalize.select.arm.flip (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $void2)
  ;; CHECK-NEXT:    (ref.func $refinalize.select.arm)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $refinalize.select.arm.flip
    ;; Flipped of the above.
    (drop
      (ref.cast (ref null $void2)
        (select (result (ref null $void))
          (ref.func $refinalize.select.arm)
          (ref.func $refinalize.select.arm)
          (i32.const 0)
        )
      )
    )
  )

  ;; CHECK:      (func $refinalize.select.arm.unknown (type $26) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.cast (ref $void2)
  ;; CHECK-NEXT:    (ref.func $refinalize.select.arm)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $refinalize.select.arm.unknown (param $x i32)
    ;; As above but use an unknown value at compile time for the condition.
    (drop
      (ref.cast (ref null $void2)
        (select (result (ref null $void))
          (ref.func $refinalize.select.arm)
          (ref.func $refinalize.select.arm)
          (local.get $x)
        )
      )
    )
  )

  ;; CHECK:      (func $non-null-bottom-ref (type $44) (result (ref func))
  ;; CHECK-NEXT:  (local $0 funcref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $0
  ;; CHECK-NEXT:    (loop (result (ref nofunc))
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $non-null-bottom-ref (result (ref func))
    (local $0 (ref null func))
    ;; The reference is uninhabitable, a non-null bottom type. The cast is not
    ;; even reached, but we need to be careful: The tee makes this a corner case
    ;; since it makes the type nullable again, so if we thought the cast would
    ;; succeed, and replaced the cast with its child, we'd fail to validate.
    ;; Instead, since the cast fails, we can replace it with an unreachable
    ;; (after the dropped child).
    (ref.cast (ref func)
      (local.tee $0
        (loop (result (ref nofunc))
          (unreachable)
        )
      )
    )
  )

  ;; CHECK:      (func $non-null-bottom-cast (type $45) (result (ref nofunc))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $non-null-bottom-cast)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $non-null-bottom-cast (result (ref nofunc))
    ;; As above, but now the cast is uninhabitable.
    (ref.cast (ref nofunc)
      (ref.func $non-null-bottom-cast)
    )
  )

  ;; CHECK:      (func $non-null-bottom-ref-test (type $8) (result i32)
  ;; CHECK-NEXT:  (local $0 funcref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $0
  ;; CHECK-NEXT:    (loop (result (ref nofunc))
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $non-null-bottom-ref-test (result i32)
    (local $0 (ref null func))
    ;; As above, but now it's a ref.test instead of cast.
    (ref.test (ref func)
      (local.tee $0
        (loop (result (ref nofunc))
          (unreachable)
        )
      )
    )
  )

  ;; CHECK:      (func $non-null-bottom-ref-test-notee (type $8) (result i32)
  ;; CHECK-NEXT:  (local $0 funcref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (loop (result (ref nofunc))
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $non-null-bottom-ref-test-notee (result i32)
    (local $0 (ref null func))
    ;; As above, but without an intermediate local.tee. Now ref.test will see
    ;; that it is unreachable, as the input is uninhabitable.
    (ref.test (ref func)
      (loop (result (ref nofunc))
        (unreachable)
      )
    )
  )

  ;; CHECK:      (func $non-null-bottom-test (type $8) (result i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $non-null-bottom-cast)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (i32.const 0)
  ;; CHECK-NEXT: )
  (func $non-null-bottom-test (result i32)
    ;; As above, but now the cast type is uninhabitable, and also use ref.test.
    ;; This cast cannot succeed, so return 0.
    (ref.test (ref nofunc)
      (ref.func $non-null-bottom-cast)
    )
  )

  ;; CHECK:      (func $ref.test-fallthrough (type $5)
  ;; CHECK-NEXT:  (local $A (ref $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.test (ref $B)
  ;; CHECK-NEXT:    (local.tee $A
  ;; CHECK-NEXT:     (struct.new $A
  ;; CHECK-NEXT:      (i32.const 10)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.tee $A
  ;; CHECK-NEXT:      (struct.new $B
  ;; CHECK-NEXT:       (i32.const 20)
  ;; CHECK-NEXT:       (i32.const 30)
  ;; CHECK-NEXT:       (f32.const 40.5)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref.test-fallthrough
    (local $A (ref $A))
    ;; The test will fail, but this pass does not have exact type info, so it
    ;; thinks it can succeed and nothing happens here (GUFA can optimize this,
    ;; however).
    (drop
      (ref.test (ref $B)
        (local.tee $A
          (struct.new $A
            (i32.const 10)
          )
        )
      )
    )
    ;; This test will succeed, even though we tee to the parent type in the
    ;; middle.
    (drop
      (ref.test (ref $B)
        (local.tee $A
          (struct.new $B
            (i32.const 20)
            (i32.const 30)
            (f32.const 40.50)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $ref.test-then-optimizeAddedConstants (type $8) (result i32)
  ;; CHECK-NEXT:  (i32.add
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (i32.const 3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $ref.test-then-optimizeAddedConstants (result i32)
    ;; The cast will become unreachable, and then the test as well. We should
    ;; not error in the subsequent optimizeAddedConstants function that will be
    ;; called on the adds. The risk here is that that code does not expect an
    ;; unreachable to appear inside a non-unreachable add, which can happen as
    ;; we delay updating types til the end of the pass. To avoid that, the
    ;; ref.test should not change its type, but only replace itself with a block
    ;; containing an unreachable (but declared as the old type; leaving
    ;; optimizing it further for other passes).
    (i32.add
      (i32.const 1)
      (i32.add
        (i32.const 2)
        (ref.test (ref func)
          (ref.cast (ref func)
            (ref.null nofunc)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $gc_to_unreachable_in_added_constants (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.wrap_i64
  ;; CHECK-NEXT:    (i64.add
  ;; CHECK-NEXT:     (call $struct_i64_helper
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (i64.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $gc_to_unreachable_in_added_constants
    (drop
      (i32.wrap_i64
        (i64.add
          (i64.const 1)
          (i64.add
            (i64.const 1)
            (call_ref $struct_i64
              ;; This will turn into an unreachable. That should not cause an
              ;; error in later i64 add optimizations (optimizeAddedConstants),
              ;; which should not assume this is an i64-typed expression.
              (ref.as_non_null
                (ref.null none)
              )
              (ref.func $struct_i64_helper)
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $struct_i64_helper (type $struct_i64) (param $0 structref) (result i64)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $struct_i64_helper (type $struct_i64) (param $0 (ref null struct)) (result i64)
    (unreachable)
  )

  ;; CHECK:      (func $array-copy-non-null (type $16) (param $x (ref null $array))
  ;; CHECK-NEXT:  (block $block
  ;; CHECK-NEXT:   (array.copy $array $array
  ;; CHECK-NEXT:    (ref.as_non_null
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (if (result i32)
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (then
  ;; CHECK-NEXT:      (br $block)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (else
  ;; CHECK-NEXT:      (i32.const 10)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:    (i32.const 42)
  ;; CHECK-NEXT:    (i32.const 1337)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $array-copy-non-null (param $x (ref null $array))
    (block $block
      (array.copy $array $array
        ;; This cast cannot be removed: while the array.copy will trap anyhow
        ;; if $x is null, we might branch out in the if, so removing a trap
        ;; here could be noticeable.
        (ref.as_non_null
          (local.get $x)
        )
        (if (result i32)
          (i32.const 1)
          (then
            (br $block)
          )
          (else
            (i32.const 10)
          )
        )
        ;; There are no tricky effects after this, so this cast can be removed.
        (ref.as_non_null
          (local.get $x)
        )
        (i32.const 42)
        (i32.const 1337)
      )
    )
  )

  ;; CHECK:      (func $struct.new (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $struct))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $struct.new)
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (struct.new_default $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default $struct.ref)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i64.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (call $get-i32)
  ;; CHECK-NEXT:    (i64.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $struct.ref
  ;; CHECK-NEXT:    (ref.func $struct.new)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $struct.new
    ;; Convert struct.new with default values into struct.new_default.
    (drop
      (struct.new $struct
        (i32.const 0)
        (block (result i32)
          ;; A block in the middle, even with side effects, is no problem (it
          ;; will be dropped).
          (call $struct.new)
          (i32.const 0)
        )
        (i32.const 0)
        (i64.const 0)
      )
    )

    ;; Refs work too.
    (drop
      (struct.new $struct.ref
        (ref.null func)
      )
    )

    ;; But a single non-default value is enough to prevent this. Test various
    ;; cases of that.
    (drop
      (struct.new $struct
        (i32.const 0)
        (i32.const 0)
        (i32.const 1) ;; constant, but non-default
        (i64.const 0)
      )
    )
    (drop
      (struct.new $struct
        (i32.const 0)
        (i32.const 0)
        (call $get-i32) ;; non-constant
        (i64.const 0)
      )
    )
    (drop
      (struct.new $struct.ref
        (ref.func $struct.new) ;; func constant, but non-default
      )
    )
  )

  ;; CHECK:      (func $array.new (type $5)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $array))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $array.new)
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (array.new_default $array
  ;; CHECK-NEXT:     (i32.const 42)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.new $array
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 42)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.new_fixed $array 1
  ;; CHECK-NEXT:    (i32.const 42)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.new $array
  ;; CHECK-NEXT:    (i32.const 42)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $array.new
    ;; Convert array.new with the default value into array.new_default.
    (drop
      (array.new $array
        (block (result i32)
          (call $array.new)
          (i32.const 0)
        )
        (i32.const 42)
      )
    )

    ;; Ignore any non-default value.
    (drop
      (array.new $array
        (i32.const 1)
        (i32.const 42)
      )
    )

    ;; array.new_fixed is preferable when the size is exactly 1.
    (drop
      (array.new $array
        (i32.const 42)
        (i32.const 1)
      )
    )

    ;; Do nothing for size 0, for now (see TODO in code).
    (drop
      (array.new $array
        (i32.const 42)
        (i32.const 0)
      )
    )
  )

  ;; CHECK:      (func $array.new_fixed (type $5)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $array))
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $array.new_fixed)
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (array.new_default $array
  ;; CHECK-NEXT:     (i32.const 3)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.new_fixed $array 3
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.new_fixed $array 3
  ;; CHECK-NEXT:    (call $get-i32)
  ;; CHECK-NEXT:    (call $get-i32)
  ;; CHECK-NEXT:    (call $get-i32)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $array))
  ;; CHECK-NEXT:    (local.set $0
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $array.new_fixed)
  ;; CHECK-NEXT:      (i32.const 42)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $array.new_fixed)
  ;; CHECK-NEXT:      (i32.const 42)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (array.new $array
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.new_fixed $array 1
  ;; CHECK-NEXT:    (i32.const 42)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $array.new_fixed
    ;; Convert array.new_fixed with default values into array.new_default.
    (drop
      (array.new_fixed $array 3
        (i32.const 0)
        (block (result i32)
          (call $array.new_fixed)
          (i32.const 0)
        )
        (i32.const 0)
      )
    )

    ;; Ignore when the values are not equal.
    (drop
      (array.new_fixed $array 3
        (i32.const 0)
        (i32.const 1)
        (i32.const 0)
      )
    )
    (drop
      (array.new_fixed $array 3
        (call $get-i32)
        (call $get-i32)
        (call $get-i32)
      )
    )

    ;; If they are equal but not default, we can optimize to array.new, even
    ;; with effects.
    (drop
      (array.new_fixed $array 2
        (block (result i32)
          (call $array.new_fixed)
          (i32.const 42)
        )
        (block (result i32)
          (call $array.new_fixed)
          (i32.const 42)
        )
      )
    )

    ;; Do nothing for size 1 (this is better than array.new as-is).
    (drop
      (array.new_fixed $array 1
        (i32.const 42)
      )
    )
  )

  ;; CHECK:      (func $array.new_fixed_fallthrough (type $5)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $array))
  ;; CHECK-NEXT:    (local.set $0
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $array.new_fixed_fallthrough)
  ;; CHECK-NEXT:      (i32.const 42)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (array.new $array
  ;; CHECK-NEXT:     (i32.const 42)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $array))
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $array.new_fixed_fallthrough)
  ;; CHECK-NEXT:      (i32.const 42)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (array.new $array
  ;; CHECK-NEXT:     (local.get $1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $array))
  ;; CHECK-NEXT:    (local.set $2
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $array.new_fixed)
  ;; CHECK-NEXT:      (i32.const 42)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $3
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $array.new_fixed_fallthrough)
  ;; CHECK-NEXT:      (i32.const 42)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (array.new $array
  ;; CHECK-NEXT:     (local.get $2)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.new_fixed $array 2
  ;; CHECK-NEXT:    (block (result i32)
  ;; CHECK-NEXT:     (call $array.new_fixed)
  ;; CHECK-NEXT:     (i32.const 42)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (block (result i32)
  ;; CHECK-NEXT:     (call $array.new_fixed_fallthrough)
  ;; CHECK-NEXT:     (i32.const 43)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $array.new_fixed_fallthrough
    ;; The fallthroughs are identical. The call in the middle must only happen
    ;; once, which we achieve by storing it to a local.
    (drop
      (array.new_fixed $array 2
        (i32.const 42)
        (block (result i32)
          (call $array.new_fixed_fallthrough)
          (i32.const 42)
        )
      )
    )
    ;; As above with order flipped.
    (drop
      (array.new_fixed $array 2
        (block (result i32)
          (call $array.new_fixed_fallthrough)
          (i32.const 42)
        )
        (i32.const 42)
      )
    )
    ;; Still identical fallthroughs, but different effects now.
    (drop
      (array.new_fixed $array 2
        (block (result i32)
          (call $array.new_fixed)
          (i32.const 42)
        )
        (block (result i32)
          (call $array.new_fixed_fallthrough)
          (i32.const 42)
        )
      )
    )
    ;; Different fallthrough, so we cannot optimize.
    (drop
      (array.new_fixed $array 2
        (block (result i32)
          (call $array.new_fixed)
          (i32.const 42)
        )
        (block (result i32)
          (call $array.new_fixed_fallthrough)
          (i32.const 43) ;; this changed
        )
      )
    )
  )

  ;; CHECK:      (func $array.new_fixed_fallthrough_local (type $26) (param $x i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (local $4 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $array))
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $array.new_fixed_fallthrough)
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (array.new $array
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $array))
  ;; CHECK-NEXT:    (local.set $2
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $array.new_fixed_fallthrough)
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (array.new $array
  ;; CHECK-NEXT:     (local.get $2)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref $array))
  ;; CHECK-NEXT:    (local.set $3
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (local.set $x
  ;; CHECK-NEXT:       (i32.const 2)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (local.get $x)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $4
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (array.new $array
  ;; CHECK-NEXT:     (local.get $3)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (array.new_fixed $array 2
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:    (block (result i32)
  ;; CHECK-NEXT:     (local.set $x
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $array.new_fixed_fallthrough_local (param $x i32)
    ;; The fallthroughs are identical local.gets.
    (drop
      (array.new_fixed $array 2
        (local.get $x)
        (block (result i32)
          (call $array.new_fixed_fallthrough)
          (local.get $x)
        )
      )
    )
    ;; Flipped order.
    (drop
      (array.new_fixed $array 2
        (block (result i32)
          (call $array.new_fixed_fallthrough)
          (local.get $x)
        )
        (local.get $x)
      )
    )
    ;; The effect is now a set. We can still optimize.
    (drop
      (array.new_fixed $array 2
        (block (result i32)
          (local.set $x
            (i32.const 2)
          )
          (local.get $x)
        )
        (local.get $x)
      )
    )
    ;; Flipped order, and now the set invalidates the get after it, preventing
    ;; optimization.
    (drop
      (array.new_fixed $array 2
        (local.get $x)
        (block (result i32)
          (local.set $x
            (i32.const 1)
          )
          (local.get $x)
        )
      )
    )
  )
)