;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.

;; RUN: foreach %s %t wasm-opt --dae --all-features -S -o - | filecheck %s

(module

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

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

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

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

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

  ;; CHECK:      (import "a" "b" (func $get-i32 (type $2) (result i32)))
  (import "a" "b" (func $get-i32 (result i32)))
  ;; CHECK:      (import "a" "c" (func $get-f64 (type $3) (result f64)))
  (import "a" "c" (func $get-f64 (result f64)))

  ;; CHECK:      (table $0 2 2 funcref)

  ;; CHECK:      (elem $0 (i32.const 0) $a9 $c8)

  ;; CHECK:      (export "a8" (func $a8))
  (export "a8" (func $a8))
  (table 2 2 funcref)
  (elem (i32.const 0) $a9 $c8)
  ;; CHECK:      (func $a (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $a (param $x i32))
  ;; CHECK:      (func $b (type $0)
  ;; CHECK-NEXT:  (call $a)
  ;; CHECK-NEXT: )
  (func $b
    (call $a (i32.const 1)) ;; best case scenario
  )
  ;; CHECK:      (func $a1 (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (i32.const 2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $a1 (param $x i32)
    (unreachable)
  )
  ;; CHECK:      (func $b1 (type $0)
  ;; CHECK-NEXT:  (call $a1)
  ;; CHECK-NEXT: )
  (func $b1
    (call $a1 (i32.const 2)) ;; same value in both, so works
  )
  ;; CHECK:      (func $b11 (type $0)
  ;; CHECK-NEXT:  (call $a1)
  ;; CHECK-NEXT: )
  (func $b11
    (call $a1 (i32.const 2))
  )
  ;; CHECK:      (func $a2 (type $1) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $a2 (param $x i32)
    (drop (local.get $x))
  )
  ;; CHECK:      (func $b2 (type $0)
  ;; CHECK-NEXT:  (call $a2
  ;; CHECK-NEXT:   (i32.const 3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $b2
    (call $a2 (i32.const 3)) ;; different value!
  )
  ;; CHECK:      (func $b22 (type $0)
  ;; CHECK-NEXT:  (call $a2
  ;; CHECK-NEXT:   (i32.const 4)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $b22
    (call $a2 (i32.const 4))
  )
  ;; CHECK:      (func $a3 (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const -1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $a3 (param $x i32)
    (drop (i32.const -1)) ;; diff value, but at least unused, so no need to send
  )
  ;; CHECK:      (func $b3 (type $0)
  ;; CHECK-NEXT:  (call $a3)
  ;; CHECK-NEXT: )
  (func $b3
    (call $a3 (i32.const 3))
  )
  ;; CHECK:      (func $b33 (type $0)
  ;; CHECK-NEXT:  (call $a3)
  ;; CHECK-NEXT: )
  (func $b33
    (call $a3 (i32.const 4))
  )
  ;; CHECK:      (func $a4 (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (i32.const 4)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $a4 (param $x i32)
    ;; This function is called with one constant and one unreachable. We can
    ;; remove the param despite the unreachable's effects.
  )
  ;; CHECK:      (func $b4 (type $0)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $b4
    ;; This call will vanish entirely, because the unreachable child executes
    ;; first (so we cannot see here that we removed the parameter from $a4, but
    ;; that can be confirmed in $a4 itself).
    (call $a4 (unreachable))
  )
  ;; CHECK:      (func $b43 (type $0)
  ;; CHECK-NEXT:  (call $a4)
  ;; CHECK-NEXT: )
  (func $b43
    ;; We will remove the parameter here.
    (call $a4 (i32.const 4))
  )
  ;; CHECK:      (func $a5 (type $0)
  ;; CHECK-NEXT:  (local $0 f64)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (f64.const 3.14159)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $1
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $a5 (param $x i32) (param $y f64) ;; optimize two
    (drop (local.get $x))
    (drop (local.get $y))
  )
  ;; CHECK:      (func $b5 (type $0)
  ;; CHECK-NEXT:  (call $a5)
  ;; CHECK-NEXT: )
  (func $b5
    (call $a5 (i32.const 1) (f64.const 3.14159))
  )
  ;; CHECK:      (func $a6 (type $1) (param $0 i32)
  ;; CHECK-NEXT:  (local $1 f64)
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (f64.const 3.14159)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $a6 (param $x i32) (param $y f64) ;; optimize just one
    (drop (local.get $x))
    (drop (local.get $y))
  )
  ;; CHECK:      (func $b6 (type $0)
  ;; CHECK-NEXT:  (call $a6
  ;; CHECK-NEXT:   (call $get-i32)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $b6
    (call $a6 (call $get-i32) (f64.const 3.14159))
  )
  ;; CHECK:      (func $a7 (type $4) (param $0 f64)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $a7 (param $x i32) (param $y f64) ;; optimize just the other one
    (drop (local.get $x))
    (drop (local.get $y))
  )
  ;; CHECK:      (func $b7 (type $0)
  ;; CHECK-NEXT:  (call $a7
  ;; CHECK-NEXT:   (call $get-f64)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $b7
    (call $a7 (i32.const 1) (call $get-f64))
  )
  ;; CHECK:      (func $a8 (type $1) (param $x i32)
  ;; CHECK-NEXT: )
  (func $a8 (param $x i32)) ;; exported, do not optimize
  ;; CHECK:      (func $b8 (type $0)
  ;; CHECK-NEXT:  (call $a8
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $b8
    (call $a8 (i32.const 1))
  )
  ;; CHECK:      (func $a9 (type $1) (param $x i32)
  ;; CHECK-NEXT: )
  (func $a9 (param $x i32)) ;; tabled, do not optimize
  ;; CHECK:      (func $b9 (type $0)
  ;; CHECK-NEXT:  (call $a9
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $b9
    (call $a9 (i32.const 1))
  )
  ;; CHECK:      (func $a10 (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (call $a10)
  ;; CHECK-NEXT:   (call $a10)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $a10 (param $x i32) ;; recursion
    (call $a10 (i32.const 1))
    (call $a10 (i32.const 1))
  )
  ;; CHECK:      (func $a11 (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (call $a11)
  ;; CHECK-NEXT:  (call $a11)
  ;; CHECK-NEXT: )
  (func $a11 (param $x i32) ;; partially successful recursion
    (call $a11 (i32.const 1))
    (call $a11 (i32.const 2))
  )
  ;; CHECK:      (func $a12 (type $1) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $a12
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $a12
  ;; CHECK-NEXT:   (i32.const 2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $a12 (param $x i32) ;; unsuccessful recursion
    (drop (local.get $x))
    (call $a12 (i32.const 1))
    (call $a12 (i32.const 2))
  )
  ;; return values
  ;; CHECK:      (func $c1 (type $0)
  ;; CHECK-NEXT:  (local $x i32)
  ;; CHECK-NEXT:  (call $c2)
  ;; CHECK-NEXT:  (call $c3)
  ;; CHECK-NEXT:  (call $c3)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (call $c4)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (call $c4)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $c5
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $c6)
  ;; CHECK-NEXT:  (call $c7)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (call $c8)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $c1
    (local $x i32)
    (drop (call $c2))
    (drop (call $c3))
    (drop (call $c3))
    (drop (call $c4))
    (local.set $x (call $c4))
    (drop (call $c5 (unreachable)))
    (drop (call $c6))
    (drop (call $c7))
    (drop (call $c8))
  )
  ;; CHECK:      (func $c2 (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $c2 (result i32)
    (i32.const 1)
  )
  ;; CHECK:      (func $c3 (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $c3 (result i32)
    (i32.const 2)
  )
  ;; CHECK:      (func $c4 (type $2) (result i32)
  ;; CHECK-NEXT:  (i32.const 3)
  ;; CHECK-NEXT: )
  (func $c4 (result i32)
    (i32.const 3)
  )
  ;; CHECK:      (func $c5 (type $1) (param $x i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $c5 (param $x i32) (result i32)
    (local.get $x)
  )
  ;; CHECK:      (func $c6 (type $0)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $c6 (result i32)
    (unreachable)
  )
  ;; CHECK:      (func $c7 (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 4)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (return)
  ;; CHECK-NEXT: )
  (func $c7 (result i32)
    (return (i32.const 4))
  )
  ;; CHECK:      (func $c8 (type $2) (result i32)
  ;; CHECK-NEXT:  (i32.const 5)
  ;; CHECK-NEXT: )
  (func $c8 (result i32)
    (i32.const 5)
  )
)
(module ;; both operations at once: remove params and return value
  ;; CHECK:      (type $0 (func))

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

  ;; CHECK:      (func $a (type $0)
  ;; CHECK-NEXT:  (call $b)
  ;; CHECK-NEXT: )
  (func $a (export "a")
    (drop
      (call $b
        (i32.const 1)
      )
    )
  )
  ;; CHECK:      (func $b (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (local.set $0
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $b (param $x i32) (result i32)
    (local.get $x)
  )
)
(module ;; tail calls inhibit dropped result removal
  ;; CHECK:      (type $0 (func (param i32) (result i32)))

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

  ;; CHECK:      (func $foo (type $0) (param $x i32) (result i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (return_call $bar)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (i32.const 42)
  ;; CHECK-NEXT: )
  (func $foo (param $x i32) (result i32)
    (drop
      (return_call $bar
        (i32.const 0)
      )
    )
    (i32.const 42)
  )
  ;; CHECK:      (func $bar (type $1) (result i32)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (i32.const 7)
  ;; CHECK-NEXT: )
  (func $bar (param $x i32) (result i32)
    (i32.const 7)
  )
)
(module ;; indirect tail calls inhibit dropped result removal
  ;; CHECK:      (type $T (func (result i32)))
  (type $T (func (result i32)))
  (table 1 1 funcref)
  ;; CHECK:      (type $1 (func))

  ;; CHECK:      (table $0 1 1 funcref)

  ;; CHECK:      (func $foo (type $T) (result i32)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (i32.const 42)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (return_call_indirect $0 (type $T)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $foo (param $x i32) (result i32)
    (drop
      (return_call_indirect (type $T)
        (i32.const 0)
      )
    )
  )
  ;; CHECK:      (func $bar (type $1)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (call $foo)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $bar
    (drop
      (call $foo
        (i32.const 42)
      )
    )
  )
)
(module
 ;; CHECK:      (type $0 (func (param funcref i32 f64) (result i64)))

 ;; CHECK:      (type $1 (func (param f32) (result funcref)))

 ;; CHECK:      (elem declare func $0)

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

 ;; CHECK:      (func $0 (type $0) (param $0 funcref) (param $1 i32) (param $2 f64) (result i64)
 ;; CHECK-NEXT:  (nop)
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 (func $0 (param $0 funcref) (param $1 i32) (param $2 f64) (result i64)
  (nop)
  (unreachable)
 )
 ;; CHECK:      (func $export (type $1) (param $0 f32) (result funcref)
 ;; CHECK-NEXT:  (ref.func $0)
 ;; CHECK-NEXT: )
 (func $export (export "export") (param $0 f32) (result funcref)
  ;; a ref.func should prevent us from changing the type of a function, as it
  ;; may escape
  (ref.func $0)
 )
)
(module
 ;; CHECK:      (type $i64 (func (param i64)))
 (type $i64 (func (param i64)))
 ;; CHECK:      (type $1 (func))

 ;; CHECK:      (global $global$0 (ref $i64) (ref.func $0))
 (global $global$0 (ref $i64) (ref.func $0))
 ;; CHECK:      (export "even" (func $1))
 (export "even" (func $1))
 ;; the argument to this function cannot be removed due to the ref.func of it
 ;; in a global
 ;; CHECK:      (func $0 (type $i64) (param $0 i64)
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT: )
 (func $0 (param $0 i64)
  (unreachable)
 )
 ;; CHECK:      (func $1 (type $1)
 ;; CHECK-NEXT:  (call_ref $i64
 ;; CHECK-NEXT:   (i64.const 0)
 ;; CHECK-NEXT:   (global.get $global$0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $1
  (call_ref $i64
   (i64.const 0)
   (global.get $global$0)
  )
 )
 ;; CHECK:      (func $2 (type $1)
 ;; CHECK-NEXT:  (call $0
 ;; CHECK-NEXT:   (i64.const 0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $2
  (call $0
   (i64.const 0)
  )
 )
)
(module
 ;; a removable non-nullable parameter
 ;; CHECK:      (type $0 (func))

 ;; CHECK:      (func $0 (type $0)
 ;; CHECK-NEXT:  (local $0 i31ref)
 ;; CHECK-NEXT:  (nop)
 ;; CHECK-NEXT: )
 (func $0 (param $x i31ref)
  (nop)
 )
 ;; CHECK:      (func $1 (type $0)
 ;; CHECK-NEXT:  (call $0)
 ;; CHECK-NEXT: )
 (func $1
  (call $0
   (ref.i31 (i32.const 0))
  )
 )
)

;; Arguments that read an immutable global can be optimized, as that is a
;; constant value.
(module
 ;; CHECK:      (type $0 (func))

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

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

 ;; CHECK:      (global $immut i32 (i32.const 42))
 (global $immut i32 (i32.const 42))

 ;; CHECK:      (global $immut2 i32 (i32.const 43))
 (global $immut2 i32 (i32.const 43))

 ;; CHECK:      (global $mut (mut i32) (i32.const 1337))
 (global $mut (mut i32) (i32.const 1337))

 ;; CHECK:      (func $foo (type $1) (param $0 i32)
 ;; CHECK-NEXT:  (local $1 i32)
 ;; CHECK-NEXT:  (local.set $1
 ;; CHECK-NEXT:   (global.get $immut)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (block
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (local.get $1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (local.get $0)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $foo (param $x i32) (param $y i32)
  ;; "Use" the params to avoid other optimizations kicking in.
  (drop (local.get $x))
  (drop (local.get $y))
 )

 ;; CHECK:      (func $foo-caller (type $0)
 ;; CHECK-NEXT:  (global.set $mut
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $foo
 ;; CHECK-NEXT:   (global.get $mut)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (global.set $mut
 ;; CHECK-NEXT:   (i32.const 2)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $foo
 ;; CHECK-NEXT:   (global.get $mut)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $foo-caller
  ;; Note how the mutable param has a different value in each call, which shows
  ;; the reason that we cannot optimize in this case. But we can optimize the
  ;; immutable param.
  (global.set $mut (i32.const 1))
  (call $foo
   (global.get $immut)
   (global.get $mut)
  )
  (global.set $mut (i32.const 2))
  (call $foo
   (global.get $immut)
   (global.get $mut)
  )
 )

 ;; CHECK:      (func $bar (type $2) (param $x i32) (param $y i32)
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $x)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $y)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $bar (param $x i32) (param $y i32)
  (drop (local.get $x))
  (drop (local.get $y))
 )

 ;; CHECK:      (func $bar-caller (type $0)
 ;; CHECK-NEXT:  (global.set $mut
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $bar
 ;; CHECK-NEXT:   (global.get $immut)
 ;; CHECK-NEXT:   (global.get $immut)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (global.set $mut
 ;; CHECK-NEXT:   (i32.const 2)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $bar
 ;; CHECK-NEXT:   (global.get $mut)
 ;; CHECK-NEXT:   (global.get $immut2)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $bar-caller
  ;; Corner cases of mixing mutable with immutable and mixing two immutables.
  (global.set $mut (i32.const 1))
  (call $bar
   (global.get $immut)
   (global.get $immut)
  )
  (global.set $mut (i32.const 2))
  (call $bar
   (global.get $mut)
   (global.get $immut2)
  )
 )
)

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

 ;; CHECK:      (func $0 (type $0)
 ;; CHECK-NEXT:  (local $0 i32)
 ;; CHECK-NEXT:  (local.set $0
 ;; CHECK-NEXT:   (i32.const 1)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (block
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (block
 ;; CHECK-NEXT:     (block
 ;; CHECK-NEXT:      (drop
 ;; CHECK-NEXT:       (i32.const 1)
 ;; CHECK-NEXT:      )
 ;; CHECK-NEXT:      (return)
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (return)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $0 (param $0 i32) (result i32)
  ;; The returns here are nested in each other, and one is a recursive call to
  ;; this function itself, which makes this a corner case we might emit invalid
  ;; code for. We end up removing the parameter, and then the call vanishes as
  ;; it was unreachable; we also remove the return as well as it is dropped in
  ;; the other caller, below.
  (return
   (drop
    (call $0
     (return
      (i32.const 1)
     )
    )
   )
  )
 )

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

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

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

 ;; CHECK:      (func $no-caller (type $A) (result (ref $A))
 ;; CHECK-NEXT:  (block ;; (replaces unreachable CallRef we can't emit)
 ;; CHECK-NEXT:   (drop
 ;; CHECK-NEXT:    (ref.null nofunc)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (unreachable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $no-caller (type $A) (result (ref $A))
  ;; This return_call is to a bottom type, which we should ignore and not error
  ;; on. There is nothing to optimize here (other passes will turn this call
  ;; into an unreachable). In particular we should not be confused by the fact
  ;; that this expression itself is unreachable (as a return call).
  (return_call_ref $A
   (ref.null nofunc)
  )
 )

 ;; CHECK:      (func $caller (type $1)
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (call $no-caller)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $caller
  (drop
   (call $no-caller)
  )
 )
)

(module
 ;; CHECK:      (type $0 (func (param f64) (result i32)))

 ;; CHECK:      (func $target (type $0) (param $0 f64) (result i32)
 ;; CHECK-NEXT:  (local $1 i32)
 ;; CHECK-NEXT:  (local $2 i32)
 ;; CHECK-NEXT:  (local $3 i32)
 ;; CHECK-NEXT:  (local $4 i32)
 ;; CHECK-NEXT:  (local.set $1
 ;; CHECK-NEXT:   (call $target
 ;; CHECK-NEXT:    (f64.const 1.1)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (local.set $2
 ;; CHECK-NEXT:   (call $target
 ;; CHECK-NEXT:    (f64.const 4.4)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $target
 ;; CHECK-NEXT:   (local.get $0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $target (param $a i32) (param $b f64) (param $c i32) (result i32)
  ;; Test removing a parameter despite calls having interesting non-unreachable
  ;; effects. This also tests recursion of such calls. We can remove all the i32
  ;; parameters here.
  (call $target
   (call $target
    (i32.const 0)
    (f64.const 1.1)
    (i32.const 2)
   )
   (local.get $b)
   (call $target
    (i32.const 3)
    (f64.const 4.4)
    (i32.const 5)
   )
  )
 )
)

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

 ;; CHECK:      (type $v128 (func (result v128)))
 (type $v128 (func (result v128)))

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

 ;; CHECK:      (table $0 10 funcref)
 (table $0 10 funcref)

 ;; CHECK:      (func $caller-effects (type $0)
 ;; CHECK-NEXT:  (local $0 v128)
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (block (result f32)
 ;; CHECK-NEXT:    (local.set $0
 ;; CHECK-NEXT:     (call_indirect $0 (type $v128)
 ;; CHECK-NEXT:      (i32.const 0)
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (call $target)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $caller-effects
  (drop
   (call $target
    (i64.const 0)
    ;; We'd like to remove this unused parameter, but it has effects, so we'll
    ;; move it to a local first.
    (call_indirect $0 (type $v128)
     (i32.const 0)
    )
    (i64.const 0)
   )
  )
 )

 ;; CHECK:      (func $target (type $2) (result f32)
 ;; CHECK-NEXT:  (local $0 i64)
 ;; CHECK-NEXT:  (local $1 i64)
 ;; CHECK-NEXT:  (local $2 v128)
 ;; CHECK-NEXT:  (local.set $0
 ;; CHECK-NEXT:   (i64.const 0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (block
 ;; CHECK-NEXT:   (local.set $1
 ;; CHECK-NEXT:    (i64.const 0)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:   (unreachable)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $target (param $0 i64) (param $1 v128) (param $2 i64) (result f32)
  ;; All parameters here should vanish.
  (unreachable)
 )
)

(module
 ;; CHECK:      (type $0 (func (param i32 i64)))

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

 ;; CHECK:      (func $caller-later-br (type $0) (param $x i32) (param $y i64)
 ;; CHECK-NEXT:  (local $2 i32)
 ;; CHECK-NEXT:  (block $block
 ;; CHECK-NEXT:   (block
 ;; CHECK-NEXT:    (local.set $2
 ;; CHECK-NEXT:     (block (result i32)
 ;; CHECK-NEXT:      (if
 ;; CHECK-NEXT:       (local.get $x)
 ;; CHECK-NEXT:       (then
 ;; CHECK-NEXT:        (return)
 ;; CHECK-NEXT:       )
 ;; CHECK-NEXT:      )
 ;; CHECK-NEXT:      (i32.const 42)
 ;; CHECK-NEXT:     )
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (br $block)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT:  (call $target
 ;; CHECK-NEXT:   (local.get $y)
 ;; CHECK-NEXT:   (local.get $y)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $caller-later-br (param $x i32) (param $y i64)
  (block $block
   (drop
    (call $target
     (i64.const 0)
     ;; We'd like to remove this unused parameter, and we can do so by moving
     ;; it to a local, but we need to be careful: the br right after us must be
     ;; kept around, as it is the only thing that makes the outer block have
     ;; type none and not unreachable.
     (block (result i32)
       (if
         (local.get $x)
         (then
           (return)
         )
       )
       (i32.const 42)
     )
     ;; We'll move this around, but won't remove it, as explained above.
     (br $block)
    )
   )
  )
  ;; Another call, to show the effect of removing the i32 parameter (also, if
  ;; no calls remain after removing the unreachable one before us, then the pass
  ;; would stop before removing parameters in $target - we don't remove params
  ;; from functions that look dead).
  (drop
   (call $target
    (local.get $y)
    (local.get $x)
    (local.get $y)
   )
  )
 )

 ;; CHECK:      (func $target (type $1) (param $0 i64) (param $1 i64)
 ;; CHECK-NEXT:  (local $2 i32)
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (block (result f32)
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (local.get $0)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (drop
 ;; CHECK-NEXT:     (local.get $1)
 ;; CHECK-NEXT:    )
 ;; CHECK-NEXT:    (unreachable)
 ;; CHECK-NEXT:   )
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $target (param $0 i64) (param $1 i32) (param $2 i64) (result f32)
  ;; The i32 parameter should vanish.
  (drop
   (local.get $0)
  )
  (drop
   (local.get $2)
  )
  (unreachable)
 )
)

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

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

 ;; CHECK:      (func $target (type $0)
 ;; CHECK-NEXT:  (local $0 i32)
 ;; CHECK-NEXT:  (unreachable)
 ;; CHECK-NEXT:  (drop
 ;; CHECK-NEXT:   (local.get $0)
 ;; CHECK-NEXT:  )
 ;; CHECK-NEXT: )
 (func $target (param $0 i32)
  ;; The parameter here is unused: there is a get, but it is unreachable. We can
  ;; remove the parameter here, and in the caller below.
  (unreachable)
  (drop
   (local.get $0)
  )
 )

 ;; CHECK:      (func $caller (type $1) (param $x i32)
 ;; CHECK-NEXT:  (call $target)
 ;; CHECK-NEXT: )
 (func $caller (param $x i32)
  (call $target
   (local.get $x)
  )
 )
)
