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

;; RUN: wasm-opt %s --remove-unused-names --precompute-propagate --fuzz-exec -all -S -o - \
;; RUN:   | filecheck %s

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

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

 ;; two incompatible struct types
 (type $A (struct (field (mut f32))))

 ;; CHECK:      (type $referrer (struct (field (mut (ref null $empty)))))
 (type $referrer (struct (field (mut (ref null $empty)))))

 ;; CHECK:      (type $func-return-i32 (func (result i32)))

 ;; CHECK:      (type $array-i32 (array (mut i32)))

 ;; CHECK:      (type $B (struct (field (mut f64))))
 (type $B (struct (field (mut f64))))

 (type $struct_i8 (struct (field i8)))

 (type $func-return-i32 (func (result i32)))

 (type $array-i32 (array (mut i32)))

 ;; CHECK:      (type $array-ref (array (mut (ref null $array-i32))))
 (type $array-ref (array (mut (ref null $array-i32))))

 ;; CHECK:      (import "fuzzing-support" "log-i32" (func $log (type $6) (param i32)))
 (import "fuzzing-support" "log-i32" (func $log (param i32)))

 ;; CHECK:      (func $test-fallthrough (type $func-return-i32) (result i32)
 ;; CHECK-NEXT:  (local $x funcref)
 ;; CHECK-NEXT:  (local.set $x
 ;; CHECK-NEXT:   (block (result nullfuncref)
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (call $test-fallthrough)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (ref.null nofunc)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (i32.const 1)
 ;; CHECK-NEXT: )
 (func $test-fallthrough (result i32)
  (local $x funcref)
  (local.set $x
   ;; the fallthrough value should be used. for that to be possible with a block
   ;; we need for it not to have a name, which is why --remove-unused-names is
   ;; run
   (block (result funcref)
    ;; make a call so the block is not trivially removable
    (drop
     (call $test-fallthrough)
    )
    (ref.null func)
   )
  )
  ;; the null in the local should be propagated to here
  (ref.is_null
   (local.get $x)
  )
 )

 ;; CHECK:      (func $load-from-struct (type $2)
 ;; CHECK-NEXT:  (local $x (ref null $struct))
 ;; CHECK-NEXT:  (local.set $x
 ;; CHECK-NEXT:   (struct.new $struct
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (struct.get $struct 0
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $x
 ;; CHECK-NEXT:   (struct.new $struct
 ;; CHECK-NEXT:    (i32.const 2)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (struct.get $struct 0
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (struct.set $struct 0
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:   (i32.const 3)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (struct.get $struct 0
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $load-from-struct
  (local $x (ref null $struct))
  (local.set $x
   (struct.new $struct
    (i32.const 1)
   )
  )
  ;; we don't precompute these, as we don't know if the GC data was modified
  ;; elsewhere (we'd need immutability or escape analysis)
  (call $log
   (struct.get $struct 0 (local.get $x))
  )
  ;; Assign a new struct
  (local.set $x
   (struct.new $struct
    (i32.const 2)
   )
  )
  (call $log
   (struct.get $struct 0 (local.get $x))
  )
  ;; Assign a new value
  (struct.set $struct 0
   (local.get $x)
   (i32.const 3)
  )
  (call $log
   (struct.get $struct 0 (local.get $x))
  )
 )
 ;; CHECK:      (func $load-from-struct-bad-merge (type $6) (param $i i32)
 ;; CHECK-NEXT:  (local $x (ref null $struct))
 ;; CHECK-NEXT:  (if
 ;; CHECK-NEXT:   (local.get $i)
 ;; CHECK-NEXT:   (then
 ;; CHECK-NEXT:    (local.set $x
 ;; CHECK-NEXT:     (struct.new $struct
 ;; CHECK-NEXT:      (i32.const 1)
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (else
 ;; CHECK-NEXT:    (local.set $x
 ;; CHECK-NEXT:     (struct.new $struct
 ;; CHECK-NEXT:      (i32.const 2)
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (struct.get $struct 0
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $load-from-struct-bad-merge (param $i i32)
  (local $x (ref null $struct))
  ;; a merge of two different $x values cannot be precomputed
  (if
   (local.get $i)
   (then
    (local.set $x
     (struct.new $struct
      (i32.const 1)
     )
    )
   )
   (else
    (local.set $x
     (struct.new $struct
      (i32.const 2)
     )
    )
   )
  )
  (call $log
   (struct.get $struct 0 (local.get $x))
  )
 )
 ;; CHECK:      (func $modify-gc-heap (type $7) (param $x (ref null $struct))
 ;; CHECK-NEXT:  (struct.set $struct 0
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:   (i32.add
 ;; CHECK-NEXT:    (struct.get $struct 0
 ;; CHECK-NEXT:     (local.get $x)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $modify-gc-heap (param $x (ref null $struct))
  (struct.set $struct 0
   (local.get $x)
   (i32.add
    (struct.get $struct 0
     (local.get $x)
    )
    (i32.const 1)
   )
  )
 )
 ;; --fuzz-exec verifies the output of this function, checking that the change
 ;; makde in modify-gc-heap is not ignored
 ;; CHECK:      (func $load-from-struct-bad-escape (type $2)
 ;; CHECK-NEXT:  (local $x (ref null $struct))
 ;; CHECK-NEXT:  (local.set $x
 ;; CHECK-NEXT:   (struct.new $struct
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $modify-gc-heap
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (struct.get $struct 0
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $load-from-struct-bad-escape (export "test")
  (local $x (ref null $struct))
  (local.set $x
   (struct.new $struct
    (i32.const 1)
   )
  )
  (call $modify-gc-heap
   (local.get $x)
  )
  (call $log
   (struct.get $struct 0 (local.get $x))
  )
 )
 ;; CHECK:      (func $load-from-struct-bad-arrive (type $7) (param $x (ref null $struct))
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (struct.get $struct 0
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $load-from-struct-bad-arrive (param $x (ref null $struct))
  ;; a parameter cannot be precomputed
  (call $log
   (struct.get $struct 0 (local.get $x))
  )
 )
 ;; CHECK:      (func $ref-comparisons (type $12) (param $x (ref null $struct)) (param $y (ref null $struct))
 ;; CHECK-NEXT:  (local $z (ref null $struct))
 ;; CHECK-NEXT:  (local $w (ref null $struct))
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (ref.eq
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:    (local.get $y)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (ref.eq
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:    (ref.null none)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (ref.eq
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:    (ref.null none)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $ref-comparisons
  (param $x (ref null $struct))
  (param $y (ref null $struct))
  (local $z (ref null $struct))
  (local $w (ref null $struct))
  ;; incoming parameters are unknown
  (call $log
   (ref.eq
    (local.get $x)
    (local.get $y)
   )
  )
  (call $log
   (ref.eq
    (local.get $x)
    ;; locals are ref.null which are known, and will be propagated
    (local.get $z)
   )
  )
  (call $log
   (ref.eq
    (local.get $x)
    (local.get $w)
   )
  )
  ;; null-initialized locals are known and can be compared
  (call $log
   (ref.eq
    (local.get $z)
    (local.get $w)
   )
  )
 )
 ;; CHECK:      (func $new-ref-comparisons (type $func-return-i32) (result i32)
 ;; CHECK-NEXT:  (local $x (ref null $struct))
 ;; CHECK-NEXT:  (local $y (ref null $struct))
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local.set $x
 ;; CHECK-NEXT:   (struct.new $struct
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $y
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $tempresult
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (i32.const 1)
 ;; CHECK-NEXT: )
 (func $new-ref-comparisons (result i32)
  (local $x (ref null $struct))
  (local $y (ref null $struct))
  (local $tempresult i32)
  (local.set $x
   (struct.new $struct
    (i32.const 1)
   )
  )
  (local.set $y
   (local.get $x)
  )
  ;; assign the result, so that propagate calculates the ref.eq. both $x and $y
  ;; must refer to the same data, so we can precompute a 1 here.
  (local.set $tempresult
   (ref.eq
    (local.get $x)
    (local.get $y)
   )
  )
  ;; and that 1 is propagated to here.
  (local.get $tempresult)
 )
 ;; CHECK:      (func $propagate-equal (type $func-return-i32) (result i32)
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local $tempref (ref null $empty))
 ;; CHECK-NEXT:  (local.set $tempresult
 ;; CHECK-NEXT:   (block (result i32)
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (local.tee $tempref
 ;; CHECK-NEXT:      (struct.new_default $empty)
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (i32.const 1)
 ;; CHECK-NEXT: )
 (func $propagate-equal (result i32)
  (local $tempresult i32)
  (local $tempref (ref null $empty))
  ;; We can compute a 1 here, as the ref.eq compares a struct to itself. We must
  ;; keep the tee around, however.
  (local.set $tempresult
   (ref.eq
    ;; allocate one struct
    (local.tee $tempref
     (struct.new $empty)
    )
    (local.get $tempref)
   )
  )
  ;; We can propagate 1 to here.
  (local.get $tempresult)
 )
 ;; CHECK:      (func $propagate-unequal (type $func-return-i32) (result i32)
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local $tempref (ref null $empty))
 ;; CHECK-NEXT:  (local.set $tempresult
 ;; CHECK-NEXT:   (i32.const 0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (i32.const 0)
 ;; CHECK-NEXT: )
 (func $propagate-unequal (result i32)
  (local $tempresult i32)
  (local $tempref (ref null $empty))
  ;; assign the result, so that propagate calculates the ref.eq.
  ;; the structs are different, so we will precompute a 0 here, and as creating
  ;; heap data does not have side effects, we can in fact replace the ref.eq
  ;; with that value
  (local.set $tempresult
   ;; allocate two different structs
   (ref.eq
    (struct.new $empty)
    (struct.new $empty)
   )
  )
  (local.get $tempresult)
 )

 ;; CHECK:      (func $propagate-uncertain-param (type $8) (param $input (ref $empty)) (result i32)
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local $tempref (ref null $empty))
 ;; CHECK-NEXT:  (local.set $tempresult
 ;; CHECK-NEXT:   (ref.eq
 ;; CHECK-NEXT:    (struct.new_default $empty)
 ;; CHECK-NEXT:    (local.get $input)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.get $tempresult)
 ;; CHECK-NEXT: )
 (func $propagate-uncertain-param (param $input (ref $empty)) (result i32)
  (local $tempresult i32)
  (local $tempref (ref null $empty))
  (local.set $tempresult
   ;; allocate a struct and compare it to a param, which we know nothing about,
   ;; so we can infer nothing here at all.
   (ref.eq
    (struct.new $empty)
    (local.get $input)
   )
  )
  (local.get $tempresult)
 )

 ;; CHECK:      (func $propagate-different-params (type $13) (param $input1 (ref $empty)) (param $input2 (ref $empty)) (result i32)
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local.set $tempresult
 ;; CHECK-NEXT:   (ref.eq
 ;; CHECK-NEXT:    (local.get $input1)
 ;; CHECK-NEXT:    (local.get $input2)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.get $tempresult)
 ;; CHECK-NEXT: )
 (func $propagate-different-params (param $input1 (ref $empty)) (param $input2 (ref $empty)) (result i32)
  (local $tempresult i32)
  (local.set $tempresult
   ;; We cannot say anything about parameters - they might alias, or not.
   (ref.eq
    (local.get $input1)
    (local.get $input2)
   )
  )
  (local.get $tempresult)
 )

 ;; CHECK:      (func $propagate-same-param (type $8) (param $input (ref $empty)) (result i32)
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local.set $tempresult
 ;; CHECK-NEXT:   (ref.eq
 ;; CHECK-NEXT:    (local.get $input)
 ;; CHECK-NEXT:    (local.get $input)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.get $tempresult)
 ;; CHECK-NEXT: )
 (func $propagate-same-param (param $input (ref $empty)) (result i32)
  (local $tempresult i32)
  (local.set $tempresult
   ;; We could optimize this in principle, but atm do not.
   ;; Note that optimize-instructions can handle patterns like this.
   (ref.eq
    (local.get $input)
    (local.get $input)
   )
  )
  (local.get $tempresult)
 )

 ;; CHECK:      (func $propagate-uncertain-local (type $func-return-i32) (result i32)
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local $tempref (ref null $empty))
 ;; CHECK-NEXT:  (local $stashedref (ref null $empty))
 ;; CHECK-NEXT:  (local.set $tempref
 ;; CHECK-NEXT:   (struct.new_default $empty)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $stashedref
 ;; CHECK-NEXT:   (local.get $tempref)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (if
 ;; CHECK-NEXT:   (call $helper
 ;; CHECK-NEXT:    (i32.const 0)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (then
 ;; CHECK-NEXT:    (local.set $tempref
 ;; CHECK-NEXT:     (struct.new_default $empty)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $tempresult
 ;; CHECK-NEXT:   (ref.eq
 ;; CHECK-NEXT:    (local.get $tempref)
 ;; CHECK-NEXT:    (local.get $stashedref)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.get $tempresult)
 ;; CHECK-NEXT: )
 (func $propagate-uncertain-local (result i32)
  (local $tempresult i32)
  (local $tempref (ref null $empty))
  (local $stashedref (ref null $empty))
  (local.set $tempref
   (struct.new $empty)
  )
  (local.set $stashedref
   (local.get $tempref)
  )
  ;; This if makes it impossible to know what value the ref.eq later should
  ;; return.
  (if
   (call $helper
    (i32.const 0)
   )
   (then
    (local.set $tempref
     (struct.new $empty)
    )
   )
  )
  (local.set $tempresult
   (ref.eq
    (local.get $tempref)
    (local.get $stashedref)
   )
  )
  (local.get $tempresult)
 )

 ;; CHECK:      (func $propagate-uncertain-loop (type $2)
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local $tempref (ref null $empty))
 ;; CHECK-NEXT:  (local $stashedref (ref null $empty))
 ;; CHECK-NEXT:  (local.set $tempref
 ;; CHECK-NEXT:   (struct.new_default $empty)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $stashedref
 ;; CHECK-NEXT:   (local.get $tempref)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (loop $loop
 ;; CHECK-NEXT:   (local.set $tempresult
 ;; CHECK-NEXT:    (ref.eq
 ;; CHECK-NEXT:     (local.get $tempref)
 ;; CHECK-NEXT:     (local.get $stashedref)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (local.set $tempref
 ;; CHECK-NEXT:    (struct.new_default $empty)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (br_if $loop
 ;; CHECK-NEXT:    (call $helper
 ;; CHECK-NEXT:     (local.get $tempresult)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $propagate-uncertain-loop
  (local $tempresult i32)
  (local $tempref (ref null $empty))
  (local $stashedref (ref null $empty))
  (local.set $tempref
   (struct.new $empty)
  )
  (local.set $stashedref
   (local.get $tempref)
  )
  (loop $loop
   ;; Each iteration in this loop may see a different struct, so we cannot
   ;; precompute the ref.eq here.
   (local.set $tempresult
    (ref.eq
     (local.get $tempref)
     (local.get $stashedref)
    )
   )
   (local.set $tempref
    (struct.new $empty)
   )
   (br_if $loop
    (call $helper
     (local.get $tempresult)
    )
   )
  )
 )

 ;; CHECK:      (func $propagate-certain-loop (type $2)
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local $tempref (ref null $empty))
 ;; CHECK-NEXT:  (local $stashedref (ref null $empty))
 ;; CHECK-NEXT:  (local.set $tempref
 ;; CHECK-NEXT:   (struct.new_default $empty)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $stashedref
 ;; CHECK-NEXT:   (local.get $tempref)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (loop $loop
 ;; CHECK-NEXT:   (local.set $tempresult
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (br_if $loop
 ;; CHECK-NEXT:    (call $helper
 ;; CHECK-NEXT:     (i32.const 1)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $propagate-certain-loop
  (local $tempresult i32)
  (local $tempref (ref null $empty))
  (local $stashedref (ref null $empty))
  ;; As above, but remove the new in the loop, so that each loop iteration does
  ;; in fact have the ref locals identical, and we can precompute a 1.
  (local.set $tempref
   (struct.new $empty)
  )
  (local.set $stashedref
   (local.get $tempref)
  )
  (loop $loop
   (local.set $tempresult
    (ref.eq
     (local.get $tempref)
     (local.get $stashedref)
    )
   )
   (br_if $loop
    (call $helper
     (local.get $tempresult)
    )
   )
  )
 )

 ;; CHECK:      (func $propagate-certain-loop-2 (type $2)
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local $tempref (ref null $empty))
 ;; CHECK-NEXT:  (local $stashedref (ref null $empty))
 ;; CHECK-NEXT:  (loop $loop
 ;; CHECK-NEXT:   (local.set $tempref
 ;; CHECK-NEXT:    (struct.new_default $empty)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (local.set $stashedref
 ;; CHECK-NEXT:    (local.get $tempref)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (local.set $tempresult
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (br_if $loop
 ;; CHECK-NEXT:    (call $helper
 ;; CHECK-NEXT:     (i32.const 1)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $propagate-certain-loop-2
  (local $tempresult i32)
  (local $tempref (ref null $empty))
  (local $stashedref (ref null $empty))
  (loop $loop
   ;; Another example of a loop where we can optimize. Here the new is inside
   ;; the loop.
   (local.set $tempref
    (struct.new $empty)
   )
   (local.set $stashedref
    (local.get $tempref)
   )
   (local.set $tempresult
    (ref.eq
     (local.get $tempref)
     (local.get $stashedref)
    )
   )
   (br_if $loop
    (call $helper
     (local.get $tempresult)
    )
   )
  )
 )

 ;; CHECK:      (func $propagate-possibly-certain-loop (type $2)
 ;; CHECK-NEXT:  (local $tempresult i32)
 ;; CHECK-NEXT:  (local $tempref (ref null $empty))
 ;; CHECK-NEXT:  (local $stashedref (ref null $empty))
 ;; CHECK-NEXT:  (loop $loop
 ;; CHECK-NEXT:   (if
 ;; CHECK-NEXT:    (call $helper
 ;; CHECK-NEXT:     (i32.const 0)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (then
 ;; CHECK-NEXT:     (local.set $tempref
 ;; CHECK-NEXT:      (struct.new_default $empty)
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (local.set $stashedref
 ;; CHECK-NEXT:    (local.get $tempref)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (local.set $tempresult
 ;; CHECK-NEXT:    (ref.eq
 ;; CHECK-NEXT:     (local.get $tempref)
 ;; CHECK-NEXT:     (local.get $stashedref)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (br_if $loop
 ;; CHECK-NEXT:    (call $helper
 ;; CHECK-NEXT:     (local.get $tempresult)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $propagate-possibly-certain-loop
  (local $tempresult i32)
  (local $tempref (ref null $empty))
  (local $stashedref (ref null $empty))
  (loop $loop
   ;; As above, but move the set of $stashedref below the if. That means that
   ;; it must be identical to $tempref in each iteration. However, that is
   ;; something we cannot infer atm (while SSA could), so we do not infer
   ;; anything here for now.
   (if
    (call $helper
     (i32.const 0)
    )
    (then
     (local.set $tempref
      (struct.new $empty)
     )
    )
   )
   (local.set $stashedref
    (local.get $tempref)
   )
   (local.set $tempresult
    (ref.eq
     (local.get $tempref)
     (local.get $stashedref)
    )
   )
   (br_if $loop
    (call $helper
     (local.get $tempresult)
    )
   )
  )
 )

 ;; CHECK:      (func $helper (type $14) (param $0 i32) (result i32)
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 (func $helper (param i32) (result i32)
  (unreachable)
 )

 ;; CHECK:      (func $odd-cast-and-get (type $2)
 ;; CHECK-NEXT:  (local $temp (ref null $B))
 ;; CHECK-NEXT:  (local.set $temp
 ;; CHECK-NEXT:   (ref.null none)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (block ;; (replaces unreachable StructGet we can't emit)
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (ref.null none)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (unreachable)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $odd-cast-and-get
  (local $temp (ref null $B))
  ;; Try to cast a null of A to B. While the types are incompatible, ref.cast
  ;; returns a null when given a null (and the null must have the type that the
  ;; ref.cast null instruction has, that is, the value is a null of type $B). So this
  ;; is an odd cast that "works".
  (local.set $temp
   (ref.cast (ref null $B)
    (ref.null $A)
   )
  )
  (drop
   ;; Read from the local, which precompute should set to a null with the proper
   ;; type.
   (struct.get $B 0
    (local.get $temp)
   )
  )
 )

 ;; CHECK:      (func $odd-cast-and-get-tuple (type $2)
 ;; CHECK-NEXT:  (local $temp (tuple (ref null $B) i32))
 ;; CHECK-NEXT:  (local.set $temp
 ;; CHECK-NEXT:   (tuple.make 2
 ;; CHECK-NEXT:    (ref.null none)
 ;; CHECK-NEXT:    (i32.const 10)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (block ;; (replaces unreachable StructGet we can't emit)
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (ref.null none)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (unreachable)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $odd-cast-and-get-tuple
  (local $temp (tuple (ref null $B) i32))
  ;; As above, but with a tuple.
  (local.set $temp
   (tuple.make 2
    (ref.cast (ref null $B)
     (ref.null $A)
    )
    (i32.const 10)
   )
  )
  (drop
   (struct.get $B 0
    (tuple.extract 2 0
     (local.get $temp)
    )
   )
  )
 )

 ;; CHECK:      (func $receive-f64 (type $15) (param $0 f64)
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 (func $receive-f64 (param f64)
  (unreachable)
 )

 ;; CHECK:      (func $odd-cast-and-get-non-null (type $16) (param $temp (ref $func-return-i32))
 ;; CHECK-NEXT:  (local.set $temp
 ;; CHECK-NEXT:   (ref.cast (ref nofunc)
 ;; CHECK-NEXT:    (ref.func $receive-f64)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (call_ref $func-return-i32
 ;; CHECK-NEXT:    (local.get $temp)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $odd-cast-and-get-non-null (param $temp (ref $func-return-i32))
  ;; Try to cast a function to an incompatible type.
  (local.set $temp
   (ref.cast (ref $func-return-i32)
    (ref.func $receive-f64)
   )
  )
  (drop
   ;; Read from the local, checking whether precompute set a value there (it
   ;; should not, as the cast fails).
   (call_ref $func-return-i32
    (local.get $temp)
   )
  )
 )

 ;; CHECK:      (func $new_block_unreachable (type $10) (result anyref)
 ;; CHECK-NEXT:  (block ;; (replaces unreachable StructNew we can't emit)
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (block
 ;; CHECK-NEXT:     (unreachable)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (unreachable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $new_block_unreachable (result anyref)
  (struct.new $struct
   ;; The value is a block with an unreachable. precompute will get rid of the
   ;; block, after which fuzz-exec should not crash - this is a regression test
   ;; for us being careful in how we execute an unreachable struct.new
   (block $label$1 (result i32)
    (unreachable)
   )
  )
 )

 ;; CHECK:      (func $br_on_cast-on-creation (type $17) (result (ref $empty))
 ;; CHECK-NEXT:  (block $label (result (ref (exact $empty)))
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (br_on_cast $label (ref (exact $empty)) (ref (exact $empty))
 ;; CHECK-NEXT:     (struct.new_default $empty)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (unreachable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $br_on_cast-on-creation (result (ref $empty))
  (block $label (result (ref $empty))
   (drop
    (br_on_cast $label anyref (ref $empty)
     (struct.new_default $empty)
    )
   )
   (unreachable)
  )
 )

 ;; CHECK:      (func $ref.is_null (type $6) (param $param i32)
 ;; CHECK-NEXT:  (local $ref (ref null $empty))
 ;; CHECK-NEXT:  (local.set $ref
 ;; CHECK-NEXT:   (struct.new_default $empty)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (call $helper
 ;; CHECK-NEXT:    (i32.const 0)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $ref
 ;; CHECK-NEXT:   (ref.null none)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (call $helper
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (if
 ;; CHECK-NEXT:   (local.get $param)
 ;; CHECK-NEXT:   (then
 ;; CHECK-NEXT:    (local.set $ref
 ;; CHECK-NEXT:     (struct.new_default $empty)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (call $helper
 ;; CHECK-NEXT:    (ref.is_null
 ;; CHECK-NEXT:     (local.get $ref)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $ref.is_null (param $param i32)
  (local $ref (ref null $empty))
  ;; Test ref.null on references, and also test that we can infer multiple
  ;; assignments in the same function, without confusion between them.
  (local.set $ref
   (struct.new $empty)
  )
  (drop
   (call $helper
    ;; The reference here is definitely not null.
    (ref.is_null
     (local.get $ref)
    )
   )
  )
  (local.set $ref
   (ref.null $empty)
  )
  (drop
   (call $helper
    ;; The reference here is definitely null.
    (ref.is_null
     (local.get $ref)
    )
   )
  )
  (if
   (local.get $param)
   (then
    (local.set $ref
     (struct.new $empty)
    )
   )
  )
  (drop
   (call $helper
    ;; The reference here might be null.
    (ref.is_null
     (local.get $ref)
    )
   )
  )
 )

 ;; CHECK:      (func $remove-set (type $18) (result (ref func))
 ;; CHECK-NEXT:  (local $nn funcref)
 ;; CHECK-NEXT:  (local $i i32)
 ;; CHECK-NEXT:  (loop $loop
 ;; CHECK-NEXT:   (local.set $i
 ;; CHECK-NEXT:    (i32.const 0)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (br $loop)
 ;; CHECK-NEXT:   (return
 ;; CHECK-NEXT:    (ref.as_non_null
 ;; CHECK-NEXT:     (local.get $nn)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $remove-set (result (ref func))
  (local $nn (ref func))
  (local $i i32)
  (loop $loop
   ;; Add a local.set here in the loop, just so the entire loop is not optimized
   ;; out.
   (local.set $i
    (i32.const 0)
   )
   ;; This entire block can be precomputed into an unconditional br. That
   ;; removes the local.set, which means the local no longer validates since
   ;; there is a get without a set (the get is never reached, but the validator
   ;; does not take that into account). Fixups will turn the local nullable to
   ;; avoid that problem.
   (block
    (br_if $loop
     (i32.const 1)
    )
    (local.set $nn
     (ref.func $remove-set)
    )
   )
   (return
    (local.get $nn)
   )
  )
 )

 ;; CHECK:      (func $strings (type $19) (param $param (ref string))
 ;; CHECK-NEXT:  (local $s (ref string))
 ;; CHECK-NEXT:  (local.set $s
 ;; CHECK-NEXT:   (string.const "hello, world")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $strings
 ;; CHECK-NEXT:   (string.const "hello, world")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $strings
 ;; CHECK-NEXT:   (string.const "hello, world")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $strings (param $param (ref string))
  (local $s (ref string))
  (local.set $s
   (string.const "hello, world")
  )
  ;; The constant string should be propagated twice, to both of these calls.
  (call $strings
   (local.get $s)
  )
  (call $strings
   (local.get $s)
  )
 )

 ;; CHECK:      (func $struct.new.packed (type $func-return-i32) (result i32)
 ;; CHECK-NEXT:  (i32.const 120)
 ;; CHECK-NEXT: )
 (func $struct.new.packed (result i32)
  ;; Truncation happens when we write to this packed i8 field, so the result we
  ;; read back is 0x12345678 & 0xff which is 0x78 == 120.
  (struct.get_s $struct_i8 0
   (struct.new $struct_i8
    (i32.const 0x12345678)
   )
  )
 )

 ;; CHECK:      (func $get-nonnullable-in-unreachable (type $10) (result anyref)
 ;; CHECK-NEXT:  (local $x (ref any))
 ;; CHECK-NEXT:  (local.tee $x
 ;; CHECK-NEXT:   (unreachable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (if
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:   (then
 ;; CHECK-NEXT:    (unreachable)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.get $x)
 ;; CHECK-NEXT: )
 (func $get-nonnullable-in-unreachable (result anyref)
  (local $x (ref any))
  ;; We cannot read a non-nullable local without setting it first, but it is ok
  ;; to do so here because we are in unreachable code. We should also not error
  ;; about this get seeming to read the default value from the function entry
  ;; (because it does not, as the entry is not reachable from it). Nothing is
  ;; expected to be optimized here.

  ;; This unreachable set is needed for the later get to validate.
  (local.set $x
   (unreachable)
  )
  ;; This if is needed so we have an interesting enough CFG that a possible
  ;; assertion can be hit about reading the default value from the entry in a
  ;; later block.
  (if
   (i32.const 1)
   (then
    (unreachable)
   )
  )
  (local.get $x)
 )

 ;; CHECK:      (func $get-nonnullable-in-unreachable-entry (type $11) (param $x i32) (param $y (ref any))
 ;; CHECK-NEXT:  (local $0 (ref any))
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT:  (local.set $0
 ;; CHECK-NEXT:   (local.get $y)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (loop $loop
 ;; CHECK-NEXT:   (br_if $loop
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (local.get $0)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $get-nonnullable-in-unreachable-entry (param $x i32) (param $y (ref any))
  (local $0 (ref any))
  ;; As above, but now the first basic block is unreachable, and we need to
  ;; detect that specifically, as the block after it *does* have entries even
  ;; though it is unreachable (it is a loop, and has itself as an entry).
  (unreachable)
  (local.set $0
   (local.get $y)
  )
  (loop $loop
   (br_if $loop
    (local.get $x)
   )
   (drop
    (local.get $0)
   )
  )
 )

 ;; CHECK:      (func $get-nonnullable-in-unreachable-later-loop (type $11) (param $x i32) (param $y (ref any))
 ;; CHECK-NEXT:  (local $0 (ref any))
 ;; CHECK-NEXT:  (if
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:   (then
 ;; CHECK-NEXT:    (nop)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT:  (local.set $0
 ;; CHECK-NEXT:   (local.get $y)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (loop $loop
 ;; CHECK-NEXT:   (br_if $loop
 ;; CHECK-NEXT:    (local.get $x)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (local.get $0)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $get-nonnullable-in-unreachable-later-loop (param $x i32) (param $y (ref any))
  (local $0 (ref any))
  ;; This |if| is added, which means the loop is later in the function.
  ;; Otherwise this is the same as before.
  (if
   (local.get $x)
   (then
    (nop)
   )
  )
  (unreachable)
  (local.set $0
   (local.get $y)
  )
  (loop $loop
   (br_if $loop
    (local.get $x)
   )
   (drop
    (local.get $0)
   )
  )
 )

 ;; CHECK:      (func $get-nonnullable-in-unreachable-tuple (type $20) (result anyref i32)
 ;; CHECK-NEXT:  (local $x (tuple (ref any) i32))
 ;; CHECK-NEXT:  (local.tee $x
 ;; CHECK-NEXT:   (unreachable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (if
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:   (then
 ;; CHECK-NEXT:    (unreachable)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.get $x)
 ;; CHECK-NEXT: )
 (func $get-nonnullable-in-unreachable-tuple (result anyref i32)
  ;; As $get-nonnullable-in-unreachable but the local is a tuple (so we need to
  ;; check isDefaultable, and not just isNullable).
  (local $x (tuple (ref any) i32))
  (local.set $x
   (unreachable)
  )
  (if
   (i32.const 1)
   (then
    (unreachable)
   )
  )
  (local.get $x)
 )

 ;; CHECK:      (func $propagate-array-effects (type $2)
 ;; CHECK-NEXT:  (local $1 (ref $array-i32))
 ;; CHECK-NEXT:  (local $2 i32)
 ;; CHECK-NEXT:  (local $3 (ref $array-i32))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (block (result i32)
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (local.tee $2
 ;; CHECK-NEXT:      (block (result i32)
 ;; CHECK-NEXT:       (drop
 ;; CHECK-NEXT:        (block (result i32)
 ;; CHECK-NEXT:         (drop
 ;; CHECK-NEXT:          (array.new $array-ref
 ;; CHECK-NEXT:           (local.tee $1
 ;; CHECK-NEXT:            (array.new_default $array-i32
 ;; CHECK-NEXT:             (i32.const 0)
 ;; CHECK-NEXT:            )
 ;; CHECK-NEXT:           )
 ;; CHECK-NEXT:           (i32.const 0)
 ;; CHECK-NEXT:          )
 ;; CHECK-NEXT:         )
 ;; CHECK-NEXT:         (i32.const 0)
 ;; CHECK-NEXT:        )
 ;; CHECK-NEXT:       )
 ;; CHECK-NEXT:       (i32.const 0)
 ;; CHECK-NEXT:      )
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (block (result i32)
 ;; CHECK-NEXT:      (drop
 ;; CHECK-NEXT:       (local.tee $3
 ;; CHECK-NEXT:        (local.get $1)
 ;; CHECK-NEXT:       )
 ;; CHECK-NEXT:      )
 ;; CHECK-NEXT:      (i32.const 0)
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (i32.const 0)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $propagate-array-effects
  (local $1 (ref $array-i32))
  (local $2 i32)
  (local $3 (ref $array-i32))
  ;; This dropped expression has several local operations that lead to
  ;; propagateLocals doing several passes. While doing so, it will try to
  ;; precompute the value of the outer $array.new. That contains a nested
  ;; tee, so we cannot replace it with the value we compute, but we do know its
  ;; value (the canonical allocation for that array.new location). We ignore the
  ;; local.tee's effect when computing that, but we should not cache the result
  ;; of that computation and use it for when we *do* care about effects, because
  ;; if we did then we'd think that entire array.new has no effects, and can be
  ;; optimized away, together with large chunks of the rest of the code.
  ;;
  ;; We should not optimize away any local.tee (but we can remove things like
  ;; the i32.lt_u).
  (drop
   (i32.lt_u
    (local.tee $2
     (select
      (i32.const 0)
      (block (result i32)
       (drop
        (array.new $array-ref
         (local.tee $1
          (array.new_default $array-i32
           (i32.const 0)
          )
         )
         (i32.const 0)
        )
       )
       (i32.const 0)
      )
      (i32.const 0)
     )
    )
    (array.len
     (local.tee $3
      (local.get $1)
     )
    )
   )
  )
 )

 ;; CHECK:      (func $nested-struct-ref.eq (type $2)
 ;; CHECK-NEXT:  (local $A (ref $referrer))
 ;; CHECK-NEXT:  (local $B (ref $empty))
 ;; CHECK-NEXT:  (local $A2 (ref $referrer))
 ;; CHECK-NEXT:  (local $temp i32)
 ;; CHECK-NEXT:  (local.set $A2
 ;; CHECK-NEXT:   (local.tee $A
 ;; CHECK-NEXT:    (struct.new $referrer
 ;; CHECK-NEXT:     (local.tee $B
 ;; CHECK-NEXT:      (struct.new_default $empty)
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $temp
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $temp
 ;; CHECK-NEXT:   (i32.const 0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $nested-struct-ref.eq
  (local $A (ref $referrer))
  (local $B (ref $empty))
  (local $A2 (ref $referrer))
  (local $temp i32)
  ;; Create $A, and copy to $A2.
  (local.set $A2
   (local.tee $A
    (struct.new $referrer
     (local.tee $B
      (struct.new_default $empty)
     )
    )
   )
  )
  ;; They should be equal, so this can be 1.
  (local.set $temp
   (ref.eq
    (local.get $A)
    (local.get $A2)
   )
  )
  ;; $A and $B are of course different.
  (local.set $temp
   (ref.eq
    (local.get $A)
    (local.get $B)
   )
  )
 )

 ;; CHECK:      (func $nested-struct-ref.eq-tee (type $2)
 ;; CHECK-NEXT:  (local $A (ref $referrer))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (block (result i32)
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (local.tee $A
 ;; CHECK-NEXT:      (struct.new $referrer
 ;; CHECK-NEXT:       (struct.new_default $empty)
 ;; CHECK-NEXT:      )
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $nested-struct-ref.eq-tee
  (local $A (ref $referrer))
  ;; As above, but immediately ref.eq the tee'd value with a get of itself. This
  ;; can be computed to 1, but we must keep the tee effect.
  (drop
   (ref.eq
    (local.tee $A
     (struct.new $referrer
      (struct.new_default $empty)
     )
    )
    (local.get $A)
   )
  )
 )

 ;; CHECK:      (func $nested-struct-ref.eq-tee-2 (type $2)
 ;; CHECK-NEXT:  (local $A (ref $referrer))
 ;; CHECK-NEXT:  (local $B (ref $empty))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (block (result i32)
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (local.tee $A
 ;; CHECK-NEXT:      (struct.new $referrer
 ;; CHECK-NEXT:       (local.tee $B
 ;; CHECK-NEXT:        (struct.new_default $empty)
 ;; CHECK-NEXT:       )
 ;; CHECK-NEXT:      )
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $nested-struct-ref.eq-tee-2
  (local $A (ref $referrer))
  (local $B (ref $empty))
  ;; As above but with an extra nested tee, causing more cache usage. We can
  ;; optimize in the same way.
  (drop
   (ref.eq
    (local.tee $A
     (struct.new $referrer
      (local.tee $B
       (struct.new_default $empty)
      )
     )
    )
    (local.get $A)
   )
  )
 )

 ;; CHECK:      (func $nested-struct-ref.eq-tee-3 (type $2)
 ;; CHECK-NEXT:  (local $A (ref $referrer))
 ;; CHECK-NEXT:  (local $A2 (ref $referrer))
 ;; CHECK-NEXT:  (local $B (ref $empty))
 ;; CHECK-NEXT:  (local $B2 (ref $empty))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (block (result i32)
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (local.tee $A
 ;; CHECK-NEXT:      (struct.new $referrer
 ;; CHECK-NEXT:       (local.tee $B
 ;; CHECK-NEXT:        (local.tee $B
 ;; CHECK-NEXT:         (struct.new_default $empty)
 ;; CHECK-NEXT:        )
 ;; CHECK-NEXT:       )
 ;; CHECK-NEXT:      )
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (local.tee $A2
 ;; CHECK-NEXT:      (local.get $A)
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (i32.const 1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (i32.const 0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $log
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $nested-struct-ref.eq-tee-3
  (local $A (ref $referrer))
  (local $A2 (ref $referrer))
  (local $B (ref $empty))
  (local $B2 (ref $empty))
  ;; As above but with yet more nested tees, causing more cache usage.
  ;; We can optimize in the same way.
  (drop
   (ref.eq
    (local.tee $A
     (struct.new $referrer
      (local.tee $B
       (local.tee $B
        (struct.new_default $empty)
       )
      )
     )
    )
    (local.tee $A2
     (local.get $A)
    )
   )
  )
  ;; Use the extra tee. We can optimize to 1 here.
  (call $log
   (ref.eq
    (local.get $A)
    (local.get $A2)
   )
  )
  ;; This evaluates to 0.
  (call $log
   (ref.eq
    (local.get $A)
    (local.get $B)
   )
  )
  ;; And this to 1.
  (call $log
   (ref.eq
    (local.get $B)
    (local.get $B)
   )
  )
 )
)
