;; 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 $func-return-i32 (func (result 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)))

 ;; CHECK:      (import "fuzzing-support" "log-i32" (func $log (type $4) (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 $3)
 ;; 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 $4) (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 $5) (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 $3)
 ;; 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 $5) (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 $11) (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:   (ref.eq
 ;; CHECK-NEXT:    (local.tee $tempref
 ;; CHECK-NEXT:     (struct.new_default $empty)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (local.get $tempref)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (i32.const 1)
 ;; CHECK-NEXT: )
 (func $propagate-equal (result i32)
  (local $tempresult i32)
  (local $tempref (ref null $empty))
  ;; assign the result, so that propagate calculates the ref.eq
  (local.set $tempresult
   (ref.eq
    ;; allocate one struct
    (local.tee $tempref
     (struct.new $empty)
    )
    (local.get $tempref)
   )
  )
  ;; we can compute a 1 here as the ref.eq compares a struct to itself. note
  ;; that the ref.eq itself cannot be precomputed away (as it has side effects).
  (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 $6) (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 $12) (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 $6) (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 $3)
 ;; 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 $3)
 ;; 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 $3)
 ;; 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 $3)
 ;; 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 $13) (param $0 i32) (result i32)
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 (func $helper (param i32) (result i32)
  (unreachable)
 )

 ;; CHECK:      (func $odd-cast-and-get (type $3)
 ;; 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 $3)
 ;; 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 $14) (param $0 f64)
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 (func $receive-f64 (param f64)
  (unreachable)
 )

 ;; CHECK:      (func $odd-cast-and-get-non-null (type $15) (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 $8) (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 $16) (result (ref $empty))
 ;; CHECK-NEXT:  (block $label (result (ref $empty))
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (br_on_cast $label (ref $empty) (ref $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 $4) (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 $17) (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 $18) (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 $8) (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 $9) (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 $9) (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 $19) (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)
 )
)
