;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
;; RUN: wasm-opt %s -all --dae -S -o - | filecheck %s

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

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

 ;; CHECK:      (type $"{i32_i64}" (sub $"{i32}" (struct (field i32) (field i64))))

 ;; CHECK:      (type $"{i32_f32}" (sub $"{i32}" (struct (field i32) (field f32))))

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

 (type $"{i32_i64}" (sub $"{i32}" (struct (field i32) (field i64))))

 (type $"{i32_f32}" (sub $"{i32}" (struct (field i32) (field f32))))

 ;; CHECK:      (func $call-various-params-no (type $2)
 ;; CHECK-NEXT:  (call $various-params-no
 ;; CHECK-NEXT:   (call $"get_{}")
 ;; CHECK-NEXT:   (call $"get_{i32}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $various-params-no
 ;; CHECK-NEXT:   (call $"get_{i32}")
 ;; CHECK-NEXT:   (call $"get_{f64}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $call-various-params-no
  ;; The first argument gets {} and {i32}; the second {i32} and {f64}; none of
  ;; those pairs can be optimized. Note that we do not pass in all nulls, as
  ;; all nulls are identical and we could do other optimization work due to
  ;; that.
  (call $various-params-no
   (call $"get_{}")
   (call $"get_{i32}")
  )
  (call $various-params-no
   (call $"get_{i32}")
   (call $"get_{f64}")
  )
 )
 ;; This function is called in ways that do not allow us to alter the types of
 ;; its parameters (see last function).
 ;; CHECK:      (func $various-params-no (type $8) (param $x (ref null $"{}")) (param $y (ref null $"{}"))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $y)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $various-params-no (param $x (ref null $"{}")) (param $y (ref null $"{}"))
  ;; "Use" the locals to avoid other optimizations kicking in.
  (drop (local.get $x))
  (drop (local.get $y))
 )

 ;; CHECK:      (func $"get_{}" (type $9) (result (ref null $"{}"))
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 (func $"get_{}" (result (ref null $"{}"))
  (unreachable)
 )
 ;; CHECK:      (func $"get_{i32}" (type $5) (result (ref null $"{i32}"))
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 (func $"get_{i32}" (result (ref null $"{i32}"))
  (unreachable)
 )
 ;; CHECK:      (func $"get_{f64}" (type $10) (result (ref null $"{f64}"))
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 (func $"get_{f64}" (result (ref null $"{f64}"))
  (unreachable)
 )

 ;; CHECK:      (func $call-various-params-yes (type $2)
 ;; CHECK-NEXT:  (call $various-params-yes
 ;; CHECK-NEXT:   (call $"get_null_{i32}")
 ;; CHECK-NEXT:   (i32.const 0)
 ;; CHECK-NEXT:   (call $"get_null_{i32}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $various-params-yes
 ;; CHECK-NEXT:   (call $"get_null_{i32}")
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:   (call $"get_null_{i32_i64}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $call-various-params-yes
  ;; The first argument gets {i32} and {i32}; the second {i32} and {i32_i64};
  ;; both of those pairs can be optimized to {i32}.
  ;; There is also an i32 in the middle, which should not confuse us.
  (call $various-params-yes
   (call $"get_null_{i32}")
   (i32.const 0)
   (call $"get_null_{i32}")
  )
  (call $various-params-yes
   (call $"get_null_{i32}")
   (i32.const 1)
   (call $"get_null_{i32_i64}")
  )
 )
 ;; This function is called in ways that *do* allow us to alter the types of
 ;; its parameters (see last function).
 ;; CHECK:      (func $various-params-yes (type $11) (param $x (ref null $"{i32}")) (param $i i32) (param $y (ref null $"{i32}"))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $i)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $y)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $various-params-yes (param $x (ref null $"{}")) (param $i i32) (param $y (ref null $"{}"))
  ;; "Use" the locals to avoid other optimizations kicking in.
  (drop (local.get $x))
  (drop (local.get $i))
  (drop (local.get $y))
 )

 ;; CHECK:      (func $call-various-params-set (type $2)
 ;; CHECK-NEXT:  (call $various-params-set
 ;; CHECK-NEXT:   (call $"get_null_{i32}")
 ;; CHECK-NEXT:   (call $"get_null_{i32}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $various-params-set
 ;; CHECK-NEXT:   (call $"get_null_{i32}")
 ;; CHECK-NEXT:   (call $"get_null_{i32_i64}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $call-various-params-set
  ;; The first argument gets {i32} and {i32}; the second {i32} and {i32_i64;
  ;; both of those pairs can be optimized to {i32}
  (call $various-params-set
   (call $"get_null_{i32}")
   (call $"get_null_{i32}")
  )
  (call $various-params-set
   (call $"get_null_{i32}")
   (call $"get_null_{i32_i64}")
  )
 )
 ;; This function is called in ways that *do* allow us to alter the types of
 ;; its parameters (see last function), however, we reuse the parameters by
 ;; writing to them, which causes problems in one case.
 ;; CHECK:      (func $various-params-set (type $12) (param $x (ref null $"{i32}")) (param $y (ref null $"{i32}"))
 ;; CHECK-NEXT:  (local $2 (ref null $"{}"))
 ;; CHECK-NEXT:  (local.set $2
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (block
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (local.get $2)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (local.get $y)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (local.set $2
 ;; CHECK-NEXT:    (struct.new_default $"{}")
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (local.get $2)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (local.set $y
 ;; CHECK-NEXT:    (call $"get_null_{i32_i64}")
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (local.get $y)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $various-params-set (param $x (ref null $"{}")) (param $y (ref null $"{}"))
  ;; "Use" the locals to avoid other optimizations kicking in.
  (drop (local.get $x))
  (drop (local.get $y))
  ;; Write to $x a value that will not fit in the refined type, which will
  ;; force us to do a fixup: the param will get the new type, and a new local
  ;; will stay at the old type, and we will use that local throughout the
  ;; function.
  (local.set $x (struct.new $"{}"))
  (drop
   (local.get $x)
  )
  ;; Write to $y in a way that does not cause any issue, and we should not do
  ;; any fixup while we refine the type.
  (local.set $y (call $"get_null_{i32_i64}"))
  (drop
   (local.get $y)
  )
 )

 ;; CHECK:      (func $call-various-params-tee (type $2)
 ;; CHECK-NEXT:  (call $various-params-tee
 ;; CHECK-NEXT:   (call $"get_null_{i32}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $call-various-params-tee
  ;; The argument gets {i32}, which allows us to refine.
  (call $various-params-tee
   (call $"get_null_{i32}")
  )
 )
 ;; CHECK:      (func $various-params-tee (type $6) (param $x (ref null $"{i32}"))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (block (result (ref null $"{i32}"))
 ;; CHECK-NEXT:    (local.tee $x
 ;; CHECK-NEXT:     (call $"get_null_{i32_i64}")
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $various-params-tee (param $x (ref null $"{}"))
  ;; "Use" the locals to avoid other optimizations kicking in.
  (drop (local.get $x))
  ;; Write to $x in a way that allows us to make the type more specific. We
  ;; must also update the type of the tee (if we do not, a validation error
  ;; would occur), and that will also cause the block's type to update as well.
  (drop
   (block (result (ref null $"{}"))
    (local.tee $x (call $"get_null_{i32_i64}"))
   )
  )
 )

 ;; CHECK:      (func $call-various-params-null (type $2)
 ;; CHECK-NEXT:  (call $various-params-null
 ;; CHECK-NEXT:   (ref.as_non_null
 ;; CHECK-NEXT:    (ref.null none)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (call $"get_null_{i32}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $various-params-null
 ;; CHECK-NEXT:   (ref.as_non_null
 ;; CHECK-NEXT:    (ref.null none)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (ref.as_non_null
 ;; CHECK-NEXT:    (ref.null none)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $call-various-params-null
  ;; The first argument gets non-null values, allowing us to refine it. The
  ;; second gets only one.
  (call $various-params-null
   (ref.as_non_null (ref.null $"{i32}"))
   (call $"get_null_{i32}")
  )
  (call $various-params-null
   (ref.as_non_null (ref.null $"{i32}"))
   (ref.as_non_null (ref.null $"{i32}"))
  )
 )
 ;; This function is called in ways that allow us to make the first parameter
 ;; non-nullable.
 ;; CHECK:      (func $various-params-null (type $13) (param $x (ref none)) (param $y (ref null $"{i32}"))
 ;; CHECK-NEXT:  (local $temp i32)
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $y)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $temp
 ;; CHECK-NEXT:   (local.get $temp)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $various-params-null (param $x (ref null $"{}")) (param $y (ref null $"{}"))
  (local $temp i32)
  ;; "Use" the locals to avoid other optimizations kicking in.
  (drop (local.get $x))
  (drop (local.get $y))
  ;; Use a local in this function as well, which should be ignored by this pass
  ;; (when we scan and update all local.gets and sets, we should only do so on
  ;; parameters, and not vars - and we can crash if we scan/update things we
  ;; should not).
  (local.set $temp (local.get $temp))
 )

 ;; CHECK:      (func $call-various-params-middle (type $2)
 ;; CHECK-NEXT:  (call $various-params-middle
 ;; CHECK-NEXT:   (call $"get_null_{i32_i64}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $various-params-middle
 ;; CHECK-NEXT:   (call $"get_null_{i32_f32}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $call-various-params-middle
  ;; The argument gets {i32_i64} and {i32_f32}. This allows us to refine from
  ;; {} to {i32}, a type "in the middle".
  (call $various-params-middle
   (call $"get_null_{i32_i64}")
  )
  (call $various-params-middle
   (call $"get_null_{i32_f32}")
  )
 )
 ;; CHECK:      (func $various-params-middle (type $6) (param $x (ref null $"{i32}"))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $various-params-middle (param $x (ref null $"{}"))
  ;; "Use" the local to avoid other optimizations kicking in.
  (drop (local.get $x))
 )

 ;; CHECK:      (func $unused-and-refinable (type $2)
 ;; CHECK-NEXT:  (local $0 structref)
 ;; CHECK-NEXT: )
 (func $unused-and-refinable (param $0 structref)
  ;; This function does not use $0. It is called with $"{}", so it is also
  ;; a parameter whose type we can refine. Do not do both operations: instead,
  ;; just remove it because it is ignored, without altering the type (handling
  ;; both operations would introduce some corner cases, and it just isn't worth
  ;; handling them if the param is completely unused anyhow). We should see in
  ;; the test output that the local $0 (the unused param) becomes a local
  ;; because it is unused, and that local does *not* have its type refined to
  ;; $"{}" (it will however be changed to be nullable, which it must be as a
  ;; local).
 )

 ;; CHECK:      (func $call-unused-and-refinable (type $2)
 ;; CHECK-NEXT:  (call $unused-and-refinable)
 ;; CHECK-NEXT: )
 (func $call-unused-and-refinable
  (call $unused-and-refinable
   (struct.new_default $"{}")
  )
 )

 ;; CHECK:      (func $non-nullable-fixup (type $14) (param $0 (ref (exact $"{}")))
 ;; CHECK-NEXT:  (local $1 structref)
 ;; CHECK-NEXT:  (local.set $1
 ;; CHECK-NEXT:   (local.get $0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $1
 ;; CHECK-NEXT:   (local.get $1)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $non-nullable-fixup (param $0 structref)
  ;; Use the param to avoid other opts removing it, and to force us to do a
  ;; fixup when we refine the param's type. When doing so, we must handle the
  ;; fact that the new local's type is non-nullable.
  (local.set $0
   (local.get $0)
  )
 )

 ;; CHECK:      (func $call-non-nullable-fixup (type $2)
 ;; CHECK-NEXT:  (call $non-nullable-fixup
 ;; CHECK-NEXT:   (struct.new_default $"{}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $call-non-nullable-fixup
  (call $non-nullable-fixup
   (struct.new_default $"{}")
  )
 )

 ;; CHECK:      (func $call-update-null (type $2)
 ;; CHECK-NEXT:  (call $update-null
 ;; CHECK-NEXT:   (ref.null none)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $update-null
 ;; CHECK-NEXT:   (struct.new_default $"{}")
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $call-update-null
  ;; Call a function with one of the parameters a null of a type that we can
  ;; update in order to get a better LUB.
  (call $update-null
   (ref.null any)
  )
  (call $update-null
   (struct.new_default $"{}")
  )
 )

 ;; CHECK:      (func $update-null (type $15) (param $x (ref null (exact $"{}")))
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $update-null (param $x (ref null any))
  ;; "Use" the param to avoid other optimizations kicking in. We should only
  ;; see the type of the param refined to a null $"{}" after updating the null
  ;; in the caller.
  (drop (local.get $x))
 )

 ;; CHECK:      (func $"get_null_{i32}" (type $5) (result (ref null $"{i32}"))
 ;; CHECK-NEXT:  (select (result (ref null $"{i32}"))
 ;; CHECK-NEXT:   (ref.null none)
 ;; CHECK-NEXT:   (select (result (ref $"{i32}"))
 ;; CHECK-NEXT:    (struct.new_default $"{i32}")
 ;; CHECK-NEXT:    (struct.new_default $"{i32_i64}")
 ;; CHECK-NEXT:    (i32.const 0)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (i32.const 0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $"get_null_{i32}" (result (ref null $"{i32}"))
  ;; Helper function that returns a null value of $"{i32}." We use this instead
  ;; of a direct ref.null because those can be rewritten by LUBFinder. Use two
  ;; selects to create a return type that cannot be improved to be non-null, a
  ;; subtype, or exact.
  (select (result (ref null $"{i32}"))
   (ref.null none)
   (select (result (ref $"{i32}"))
     (struct.new_default $"{i32}")
     (struct.new_default $"{i32_i64}")
     (i32.const 0)
   )
   (i32.const 0)
  )
 )

 ;; CHECK:      (func $"get_null_{i32_i64}" (type $16) (result (ref null (exact $"{i32_i64}")))
 ;; CHECK-NEXT:  (select (result (ref null (exact $"{i32_i64}")))
 ;; CHECK-NEXT:   (ref.null none)
 ;; CHECK-NEXT:   (struct.new_default $"{i32_i64}")
 ;; CHECK-NEXT:   (i32.const 0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $"get_null_{i32_i64}" (result (ref null $"{i32_i64}"))
  (select
   (ref.null none)
   (struct.new_default $"{i32_i64}")
   (i32.const 0)
  )
 )

 ;; CHECK:      (func $"get_null_{i32_f32}" (type $17) (result (ref null (exact $"{i32_f32}")))
 ;; CHECK-NEXT:  (select (result (ref null (exact $"{i32_f32}")))
 ;; CHECK-NEXT:   (ref.null none)
 ;; CHECK-NEXT:   (struct.new_default $"{i32_f32}")
 ;; CHECK-NEXT:   (i32.const 0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $"get_null_{i32_f32}" (result (ref null $"{i32_f32}"))
  (select
   (ref.null none)
   (struct.new_default $"{i32_f32}")
   (i32.const 0)
  )
 )
)
