;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-opt -all --dae2 --closed-world -S -o - | filecheck %s

;; Basic tests.

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

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $test (param $unused i32)
    ;; Trivially unused parameter.
    (nop)
  )
)

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

  ;; CHECK:      (global $g (mut i64) (i64.const 0))
  (global $g (mut i64) (i64.const 0))

  ;; CHECK:      (func $test (type $0) (param $used i64)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $unused i32) (param $used i64)
    ;; Add a used parameter.
    (global.set $g
      (local.get $used)
    )
  )
)

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $test (type $0) (param $used i32)
  ;; CHECK-NEXT:  (local $unused i64)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32) (param $unused i64)
    ;; Same, but use the other parameter.
    (global.set $g
      (local.get $used)
    )
  )
)

;; Tests with cycles.

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

  ;; CHECK:      (global $g1 (mut i32) (i32.const 0))
  (global $g1 (mut i32) (i32.const 0))
  ;; CHECK:      (global $g2 (mut f32) (f32.const 0))
  (global $g2 (mut f32) (f32.const 0))

  ;; CHECK:      (func $test (type $0) (param $used1 i32) (param $used2 f32)
  ;; CHECK-NEXT:  (local $unused2 f64)
  ;; CHECK-NEXT:  (local $unused1 i64)
  ;; CHECK-NEXT:  (global.set $g1
  ;; CHECK-NEXT:   (local.get $used1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g2
  ;; CHECK-NEXT:   (local.get $used2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used1 i32)
              (param $unused1 i64)
              (param $used2 f32)
              (param $unused2 f64)
    ;; Multiple interleaved used and unused params.
    (global.set $g1
      (local.get $used1)
    )
    (global.set $g2
      (local.get $used2)
    )
  )
)

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

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call $test)
  ;; CHECK-NEXT: )
  (func $test (param $unused i32)
    ;; The parameter is used only for a recursive call, so can still be removed.
    (call $test
      (local.get $unused)
    )
  )
)

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

  ;; CHECK:      (func $test1 (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call $test2)
  ;; CHECK-NEXT: )
  (func $test1 (param $unused i32)
    ;; We can optimize mutual recursion.
    (call $test2
      (local.get $unused)
    )
  )

  ;; CHECK:      (func $test2 (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call $test1)
  ;; CHECK-NEXT: )
  (func $test2 (param $unused i32)
    (call $test1
      (local.get $unused)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32)))

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $test1 (type $0) (param $used i32)
  ;; CHECK-NEXT:  (call $test2
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test1 (param $used i32)
    (call $test2
      (local.get $used)
    )
  )

  ;; CHECK:      (func $test2 (type $0) (param $used i32)
  ;; CHECK-NEXT:  (call $test1
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test2 (param $used i32)
    ;; Same, but now the parameter is actually used, so it must be kept
    ;; throughout the recursive call chain.
    (call $test1
      (local.get $used)
    )
    (global.set $g
      (local.get $used)
    )
  )
)

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

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (call $test)
  ;; CHECK-NEXT: )
  (func $test (param i32 i32 i32)
    ;; We can analyze recursive cycles involving multiple parameters being
    ;; shuffled.
    (call $test
      (local.get 1)
      (local.get 2)
      (local.get 0)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32 i32 i32)))

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $test (type $0) (param $0 i32) (param $1 i32) (param $2 i32)
  ;; CHECK-NEXT:  (call $test
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:   (local.get $2)
  ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param i32 i32 i32)
    ;; Same, but now one parameter is used so all must be kept.
    (call $test
      (local.get 1)
      (local.get 2)
      (local.get 0)
    )
    (global.set $g
      (local.get 2)
    )
  )
)

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

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $1 (func (param i32)))

  ;; CHECK:       (type $2 (struct))

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $caller (type $1) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $used)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (call $callee)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller (param $used i32)
    ;; The parameter is used here in the caller, but it can still be removed in
    ;; the callee.
    (global.set $g
      (local.get $used)
    )
    (call $callee
      (local.get $used)
    )
  )

  ;; CHECK:      (func $callee (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $callee (param $unused i32)
    (nop)
  )
)

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

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $1 (func (param i32) (result i64)))

  ;; CHECK:       (type $2 (struct))

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $caller (type $1) (param $used i32) (result i64)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block (result i64)
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (local.get $used)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (call $callee)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $caller (param $used i32) (result i64)
    ;; Same, but now both functions have a concrete result.
    (global.set $g
      (local.get $used)
    )
    (call $callee
      (local.get $used)
    )
  )

  ;; CHECK:      (func $callee (type $0) (result i64)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (i64.const 0)
  ;; CHECK-NEXT: )
  (func $callee (param $unused i32) (result i64)
    (i64.const 0)
  )
)

;; Tests with indirect function calls.

(module
  ;; CHECK:      (type $f (func))
  (type $f (func (param i32)))
  ;; CHECK:      (elem declare func $test)

  ;; CHECK:      (func $test (type $f)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call_ref $f
  ;; CHECK-NEXT:   (ref.func $test)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (type $f) (param $unused i32)
    ;; If a parameter is unused in all referenced functions of a particular
    ;; type, we can optimize it out in all such functions.
    (call_ref $f
      (local.get $unused)
      (ref.func $test)
    )
  )
)

(module
  ;; CHECK:      (type $f (func))
  (type $f (func (param i32)))
  ;; CHECK:      (func $test (type $f)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $test (type $f) (param $unused i32)
    ;; Same, but now the passed operand is unreachable. The call cannot remain
    ;; unreachable without its unreachable child, so we replace it entirely.
    (call_ref $f
      (unreachable)
      (ref.func $test)
    )
  )
)

(module
  ;; CHECK:      (type $f (func))
  (type $f (func (param i32)))

  ;; CHECK:      (table $t 1 1 funcref)
  (table $t funcref (elem $test))
  ;; CHECK:      (elem $implicit-elem (i32.const 0) $test)

  ;; CHECK:      (func $test (type $f)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call_indirect $t (type $f)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (type $f) (param $unused i32)
    ;; Same, but with a call_indirect.
    (call_indirect (type $f)
      (local.get $unused)
      (i32.const 0)
    )
  )
)

(module
  ;; CHECK:      (type $f (func))
  (type $f (func (param i32)))
  ;; CHECK:      (elem declare func $test1 $test2)

  ;; CHECK:      (func $test1 (type $f)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call_ref $f
  ;; CHECK-NEXT:   (ref.func $test1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test1 (type $f) (param $unused i32)
    ;; We can optimize because the parameter is unused in both referenced
    ;; functions.
    (call_ref $f
      (local.get $unused)
      (ref.func $test1)
    )
  )

  ;; CHECK:      (func $test2 (type $f)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call_ref $f
  ;; CHECK-NEXT:   (ref.func $test2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test2 (type $f) (param $unused i32)
    (call_ref $f
      (local.get $unused)
      (ref.func $test2)
    )
  )
)

(module
  ;; CHECK:      (type $f (func))
  (type $f (func (param i32)))

  ;; CHECK:      (table $t 2 2 funcref)
  (table $t funcref (elem $test1 $test2))
  ;; CHECK:      (elem $implicit-elem (i32.const 0) $test1 $test2)

  ;; CHECK:      (func $test1 (type $f)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call_indirect $t (type $f)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test1 (type $f) (param $unused i32)
    ;; Same, but with call_indirect. We can optimize because the parameter is
    ;; unused in both referenced functions.
    (call_indirect (type $f)
      (local.get $unused)
      (i32.const 0)
    )
  )

  ;; CHECK:      (func $test2 (type $f)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call_indirect $t (type $f)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test2 (type $f) (param $unused i32)
    (call_indirect (type $f)
      (local.get $unused)
      (i32.const 1)
    )
  )
)

(module
  ;; CHECK:      (type $f (func (param i32)))
  (type $f (func (param i32)))

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (elem declare func $test1 $test2)

  ;; CHECK:      (func $test1 (type $f) (param $unused i32)
  ;; CHECK-NEXT:  (call_ref $f
  ;; CHECK-NEXT:   (local.get $unused)
  ;; CHECK-NEXT:   (ref.func $test1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test1 (type $f) (param $unused i32)
    ;; We cannot optimize because the parameter is used in another referenced
    ;; function of the same type.
    (call_ref $f
      (local.get $unused)
      (ref.func $test1)
    )
  )

  ;; CHECK:      (func $test2 (type $f) (param $used i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $test2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test2 (type $f) (param $used i32)
    (drop
      (ref.func $test2)
    )
    (global.set $g
      (local.get $used)
    )
  )
)

(module
  ;; CHECK:      (type $f (func (param i32)))
  (type $f (func (param i32)))

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (table $t 2 2 funcref)
  (table $t funcref (elem $test1 $test2))

  ;; CHECK:      (elem $implicit-elem (i32.const 0) $test1 $test2)

  ;; CHECK:      (func $test1 (type $f) (param $unused i32)
  ;; CHECK-NEXT:  (call_indirect $t (type $f)
  ;; CHECK-NEXT:   (local.get $unused)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test1 (type $f) (param $unused i32)
    ;; Same, but with call_indirect. We cannot optimize because the parameter is
    ;; used in another referenced function of the same type.
    (call_indirect (type $f)
      (local.get $unused)
      (i32.const 0)
    )
  )

  ;; CHECK:      (func $test2 (type $f) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test2 (type $f) (param $used i32)
    (global.set $g
      (local.get $used)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $f (func))
  (type $f (func (param i32 i64)))
  ;; CHECK:       (type $1 (func))

  ;; CHECK:      (func $test (type $f)
  ;; CHECK-NEXT:  (local $f (ref null $f))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i64.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $f
  ;; CHECK-NEXT:   (local.get $f)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    ;; There is no function with type $f, so we can remove all its parameters.
    (local $f (ref null $f))
    (call_ref $f
      (i32.const 0)
      (i64.const 1)
      (local.get $f)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $f (func))
  (type $f (func (param i32 i64)))

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

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

  ;; CHECK:      (func $test (type $f)
  ;; CHECK-NEXT:  (local $f (ref null $f))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i64.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_indirect $t (type $f)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    ;; Same, but with call_indirect. There is no function with type $f, so we
    ;; can remove all its parameters.
    (local $f (ref null $f))
    (call_indirect (type $f)
      (i32.const 0)
      (i64.const 1)
      (i32.const 0)
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (func)))
    (type $super (sub (func (param i32 i64))))
    ;; CHECK:       (type $sub1 (sub $super (func)))
    (type $sub1 (sub $super (func (param i32 i64))))
    ;; CHECK:       (type $2 (func))

    ;; CHECK:       (type $sub2 (sub $super (func)))
    (type $sub2 (sub $super (func (param i32 i64))))
  )

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (func $referenced (type $sub2)
  ;; CHECK-NEXT:  (local $0 i64)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $sub2)
    (drop
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $test (type $super)
  ;; CHECK-NEXT:  (local $sub1 (ref null $sub1))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i64.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $sub1
  ;; CHECK-NEXT:   (local.get $sub1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    ;; There is no function with type $sub1, but there is a referenced function
    ;; with a type in the same tree. Since its parameters are not used, we can
    ;; optimize all the types in the tree.
    (local $sub1 (ref null $sub1))
    (call_ref $sub1
      (i32.const 0)
      (i64.const 1)
      (local.get $sub1)
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (func (param i32 i64))))
    (type $super (sub (func (param i32 i64))))
    ;; CHECK:       (type $sub1 (sub $super (func (param i32 i64))))
    (type $sub1 (sub $super (func (param i32 i64))))
    ;; CHECK:       (type $2 (func))

    ;; CHECK:       (type $sub2 (sub $super (func (param i32 i64))))
    (type $sub2 (sub $super (func (param i32 i64))))
  )

  ;; CHECK:      (global $g1 (mut i32) (i32.const 0))
  (global $g1 (mut i32) (i32.const 0))
  ;; CHECK:      (global $g2 (mut i64) (i64.const 0))
  (global $g2 (mut i64) (i64.const 0))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (func $referenced (type $sub2) (param $used1 i32) (param $used2 i64)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g1
  ;; CHECK-NEXT:   (local.get $used1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g2
  ;; CHECK-NEXT:   (local.get $used2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $sub2) (param $used1 i32) (param $used2 i64)
    (drop
      (ref.func $referenced)
    )
    (global.set $g1
      (local.get $used1)
    )
    (global.set $g2
      (local.get $used2)
    )
  )

  ;; CHECK:      (func $test (type $2)
  ;; CHECK-NEXT:  (local $sub1 (ref null $sub1))
  ;; CHECK-NEXT:  (call_ref $sub1
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (i64.const 1)
  ;; CHECK-NEXT:   (local.get $sub1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    ;; Same, but now the parameters are used. We cannot optimize any of the
    ;; types in the tree.
    (local $sub1 (ref null $sub1))
    (call_ref $sub1
      (i32.const 0)
      (i64.const 1)
      (local.get $sub1)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $f (func (param i32)))
  (type $f (func (param i32 i32 i32)))

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (elem declare func $callee)

  ;; CHECK:      (func $test (type $f) (param $forwarded i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (local.get $forwarded)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $f
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:   (ref.func $callee)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $forwarded i32)
    ;; The forwarded parameter is forwarded from index 0 to index 2. Tests that
    ;; the vectors in the reverse indirect call graph are the correct size.
    (call_ref $f
      (i32.const 0)
      (i32.const 0)
      (local.get $forwarded)
      (ref.func $callee)
    )
  )

  ;; CHECK:      (func $callee (type $f) (param $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $callee (type $f) (param i32 i32 i32)
    (global.set $g
      (local.get 2)
    )
  )
)

;; Tests with unreferenced functions with optimized types.

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

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $1 (func (param i32)))

  ;; CHECK:       (type $2 (struct))

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  (type $f (func (param i32)))
  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (func $referenced (type $f)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call_ref $f
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $f) (param $unused i32)
    ;; Now the other function is not referenced, so we can optimize.
    (call_ref $f
      (local.get $unused)
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $unreferenced (type $1) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreferenced (type $f) (param $used i32)
    ;; This function will get a new type to avoid its type being rewritten along
    ;; with $referenced's type.
    (global.set $g
      (local.get $used)
    )
  )
)

(module

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $f2 (func))

  ;; CHECK:       (type $f1 (func))
  (type $f1 (func (param i32)))
  (type $f2 (func (param i64)))
  ;; CHECK:      (elem declare func $referenced1 $referenced2)

  ;; CHECK:      (func $referenced1 (type $f1)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced1 (type $f1) (param $unused i32)
    (drop
      (ref.func $referenced1)
    )
  )

  ;; CHECK:      (func $referenced2 (type $f2)
  ;; CHECK-NEXT:  (local $unused i64)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced2 (type $f2) (param $unused i64)
    ;; Although it is optimized to the same signature, this must have a
    ;; different type than $referenced1's type to maintain separate type
    ;; identities
    (drop
      (ref.func $referenced2)
    )
  )

  ;; CHECK:      (func $unreferenced1 (type $f2)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $unreferenced1 (type $f1) (param $unused i32)
    ;; This does not have to reuse $f1, but it does because it will be optimized
    ;; the same way.
    (nop)
  )

  ;; CHECK:      (func $unreferenced2 (type $f2)
  ;; CHECK-NEXT:  (local $unused i64)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $unreferenced2 (type $f2) (param $unused i64)
    ;; Same here.
    (nop)
  )
)

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

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $f2 (func))

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  (type $f1 (func (param i32 i32)))
  (type $f2 (func (param i64 i32)))
  ;; CHECK:      (elem declare func $referenced1 $referenced2)

  ;; CHECK:      (func $referenced1 (type $f1)
  ;; CHECK-NEXT:  (local $unused2 i32)
  ;; CHECK-NEXT:  (local $unused1 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced1 (type $f1) (param $unused1 i32) (param $unused2 i32)
    (drop
      (ref.func $referenced1)
    )
  )

  ;; CHECK:      (func $referenced2 (type $f2)
  ;; CHECK-NEXT:  (local $unused2 i32)
  ;; CHECK-NEXT:  (local $unused i64)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced2 (type $f2) (param $unused i64) (param $unused2 i32)
    ;; Although it is optimized to the same signature, this must have a
    ;; different type than $referenced1's type to maintain separate type
    ;; identities
    (drop
      (ref.func $referenced2)
    )
  )

  ;; CHECK:      (func $unreferenced1 (type $0) (param $used i32)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreferenced1 (type $f1) (param $unused i32) (param $used i32)
    ;; Now this needs a new type because $f1 will be optimized differently.
    (global.set $g
      (local.get $used)
    )
  )

  ;; CHECK:      (func $unreferenced2 (type $0) (param $used i32)
  ;; CHECK-NEXT:  (local $unused i64)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreferenced2 (type $f2) (param $unused i64) (param $used i32)
    ;; This also needs a new type. It should be the same new type as used by
    ;; $unreferenced1 to minimize the number of new types and because identity
    ;; doesn't matter for unreferenced functions.
    (global.set $g
      (local.get $used)
    )
  )
)

(module
  ;; CHECK:      (type $public (sub (func (param i32))))
  (type $public (sub (func (param i32))))

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

  ;; CHECK:      (global $public (ref null $public) (ref.null nofunc))
  (global $public (export "public") (ref null $public) (ref.null nofunc))

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (export "public" (global $public))

  ;; CHECK:      (func $referenced (type $f)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $f) (param $unused i32)
    (drop
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $unreferenced (type $public) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreferenced (type $f) (param $used i32)
    ;; Since type identity doesn't matter for unreferenced functions, it's ok
    ;; to use a public type as the replacement.
    (global.set $g
      (local.get $used)
    )
  )
)

(module
  ;; CHECK:      (type $f (func (param i32)))
  (type $f (func (param i32)))

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (func $referenced (type $f) (param $used i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $f) (param $used i32)
    (drop
      (ref.func $referenced)
    )
    (global.set $g
      (local.get $used)
    )
  )

  ;; CHECK:      (func $unreferenced (type $f) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreferenced (type $f) (param $used i32)
    ;; Now there is no replacement type necessary because all the parameters of
    ;; type $f are used in referenced functions, so the type will not be
    ;; optimized.
    (global.set $g
      (local.get $used)
    )
  )
)

(module
  ;; CHECK:      (type $f (func (param i32)))
  (type $f (func (param i32)))

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (func $referenced (type $f) (param $used i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $f) (param $used i32)
    (drop
      (ref.func $referenced)
    )
    (global.set $g
      (local.get $used)
    )
  )

  ;; CHECK:      (func $unreferenced (type $1)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $unreferenced (type $f) (param $unused i32)
    ;; Same, but now the unreferenced function can still be optimized.
    (nop)
  )
)

(module
  ;; CHECK:      (type $f (func (param (ref $f))))
  (type $f (func (param (ref $f) (ref $f))))

  ;; CHECK:      (type $1 (func (param (ref $f) (ref $f))))

  ;; CHECK:      (global $g (mut (ref null $f)) (ref.null nofunc))
  (global $g (mut (ref null $f)) (ref.null nofunc))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (func $referenced (type $f) (param $used (ref $f))
  ;; CHECK-NEXT:  (local $unused (ref $f))
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $f) (param $used (ref $f)) (param $unused (ref $f))
    ;; The updated type should still be self-recursive.
    (global.set $g
      (local.get $used)
    )
    (drop
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $unreferenced1 (type $1) (param $used1 (ref $f)) (param $used2 (ref $f))
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreferenced1 (type $f) (param $used1 (ref $f)) (param $used2 (ref $f))
    ;; In contrast, the replacement type does not need to be self-recursive
    ;; because its identity does not matter. It should reference but not be the
    ;; updated type $f.
    (global.set $g
      (local.get $used1)
    )
    (global.set $g
      (local.get $used2)
    )
  )

  ;; CHECK:      (func $unreferenced2 (type $f) (param $used (ref $f))
  ;; CHECK-NEXT:  (local $unused (ref $f))
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreferenced2 (type $f) (param $unused (ref $f)) (param $used (ref $f))
    ;; Because this unreferenced function will be optimized to have the same
    ;; that $f will be optimized to, it can keep using type $f.
    (global.set $g
      (local.get $used)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $optimized (func (param i64)))

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

  (type $optimized (func (param i32 i64)))

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $2 (func (param i32)))

  ;; CHECK:       (type $3 (struct))

  ;; CHECK:      (global $g1 (mut i32) (i32.const 0))
  (global $g1 (mut i32) (i32.const 0))
  ;; CHECK:      (global $g2 (mut i64) (i64.const 0))
  (global $g2 (mut i64) (i64.const 0))

  ;; CHECK:      (global $use-uninhabited (ref null $uninhabited) (ref.null nofunc))
  (global $use-uninhabited (ref null $uninhabited) (ref.null nofunc))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (func $referenced (type $optimized) (param $used i64)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (global.set $g2
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $optimized) (param $unused i32) (param $used i64)
    (global.set $g2
      (local.get $used)
    )
    (drop
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $unreferenced (type $2) (param $used i32)
  ;; CHECK-NEXT:  (local $unused i64)
  ;; CHECK-NEXT:  (global.set $g1
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreferenced (type $optimized) (param $used i32) (param $unused i64)
    ;; The placeholder type here should not be the same as unoptimized
    ;; $uninhabited, because then the global type rewrite would further rewrite
    ;; it incorrectly.
    (global.set $g1
      (local.get $used)
    )
  )
)

(module
  ;; Keep this type public so it will remain the same.
  ;; CHECK:      (type $public (struct))
  (type $public (struct))

  (rec
    ;; The type of the test function. Conflicts with the default brand. Will
    ;; be optimized because no referenced functions have type $f.
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $f (func))
    (type $f (func (param (ref $public))))
    (type (struct))
  )
  ;; The signature of the test function, but without the brand. Since this type
  ;; is not inhabited in this module, its parameters will be removed.
  ;; CHECK:       (type $f' (func))
  (type $f' (func (param (ref $public))))

  ;; Make $public public.
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $3 (func (param (ref $public))))

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

  ;; CHECK:      (global $public (ref null $public) (ref.null none))
  (global $public (export "public") (ref null $public) (ref.null none))

  ;; Keep $f' alive.
  ;; CHECK:      (global $f' (ref null $f') (ref.null nofunc))
  (global $f' (ref null $f') (ref.null nofunc))

  ;; CHECK:      (global $g (mut (ref null $public)) (ref.null none))
  (global $g (mut (ref null $public)) (ref.null none))

  ;; CHECK:      (export "public" (global $public))

  ;; CHECK:      (func $test (type $3) (param $used (ref $public))
  ;; CHECK-NEXT:  (local $1 anyref)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (type $f) (param $used (ref $public))
    ;; Regression test. A previous version of the code would try to create a new
    ;; type with the desired signature, which is the same as the current
    ;; signature, see that the new type is $f', which is mapped to a different
    ;; signature, so then try to add a brand to disambiguate from $f'. But this
    ;; was done incorrectly, so the replacement type was $f itself, which was
    ;; then rewritten to have the parameters removed. The local.get below then
    ;; picked up the wrong type, making the output invalid.
    (local anyref)
    (global.set $g
      (local.get $used)
    )
  )
)

;; Tests for public and tag function types not being optimized.

(module
  ;; CHECK:      (type $public (func (param i32)))
  (type $public (func (param i32)))

  (func $import (import "" "") (type $public) (param i32))

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

  ;; CHECK:      (import "" "" (func $import (type $public) (param i32)))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (func $referenced (type $public) (param $unused i32)
  ;; CHECK-NEXT:  (call_ref $public
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $public) (param $unused i32)
    ;; This referenced function cannot be optimized because we cannot rewrite
    ;; public types, and we cannot just give referenced functions new types.
    ;;
    ;; TODO: In many cases we could give referenced functions new types, as long
    ;; as we do so for all referenced functions with types in the same tree.
    ;; Investigate this.
    (call_ref $public
      (i32.const 0)
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $unreferenced (type $1)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $unreferenced (type $public) (param $unused i32)
    ;; The unreferenced function can still be optimized.
    (nop)
  )
)

(module
  ;; CHECK:      (type $public (func (param i32)))
  (type $public (func (param i32)))

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

  ;; CHECK:      (elem declare func $referenced)

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

  ;; CHECK:      (func $export (type $public) (param $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $export (export "") (type $public) (param $unused i32)
    (nop)
  )

  ;; CHECK:      (func $referenced (type $public) (param $unused i32)
  ;; CHECK-NEXT:  (call_ref $public
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $public) (param $unused i32)
    ;; Same, but now the type is public because of an export.
    (call_ref $public
      (i32.const 0)
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $unreferenced (type $1)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $unreferenced (type $public) (param $unused i32)
    ;; The unreferenced function can still be optimized.
    (nop)
  )
)

(module
  ;; CHECK:      (type $public (func (param i32)))
  (type $public (func (param i32)))

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

  ;; CHECK:      (global $g (ref null $public) (ref.null nofunc))
  (global $g (export "") (ref null $public) (ref.null nofunc))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (export "" (global $g))

  ;; CHECK:      (func $referenced (type $public) (param $unused i32)
  ;; CHECK-NEXT:  (call_ref $public
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $public) (param $unused i32)
    ;; Same, but now the type is public because of a global.
    (call_ref $public
      (i32.const 0)
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $unreferenced (type $1)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $unreferenced (type $public) (param $unused i32)
    ;; The unreferenced function can still be optimized.
    (nop)
  )
)

(module
  ;; CHECK:      (type $public (func (param i32)))
  (type $public (func (param i32)))
  ;; CHECK:      (type $direct-public (struct (field (ref $public))))
  (type $direct-public (struct (field (ref $public))))

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

  ;; CHECK:      (global $g (ref null $direct-public) (ref.null none))
  (global $g (export "") (ref null $direct-public) (ref.null none))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (export "" (global $g))

  ;; CHECK:      (func $referenced (type $public) (param $unused i32)
  ;; CHECK-NEXT:  (call_ref $public
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $public) (param $unused i32)
    ;; Same, but now the type is public only indirectly.
    (call_ref $public
      (i32.const 0)
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $unreferenced (type $2)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $unreferenced (type $public) (param $unused i32)
    ;; The unreferenced function can still be optimized.
    (nop)
  )
)

(module
  ;; CHECK:      (type $public-super (sub (func (param i32))))
  (type $public-super (sub (func (param i32))))
  ;; CHECK:      (type $private-sub (sub $public-super (func (param i32))))
  (type $private-sub (sub $public-super (func (param i32))))

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

  ;; CHECK:      (global $g (ref null $public-super) (ref.null nofunc))
  (global $g (export "") (ref null $public-super) (ref.null nofunc))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (export "" (global $g))

  ;; CHECK:      (func $referenced (type $private-sub) (param $unused i32)
  ;; CHECK-NEXT:  (call_ref $private-sub
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $private-sub) (param $unused i32)
    ;; The type is no longer public, but still cannot be rewritten because it is
    ;; constrained by its public supertype. We cannot optimize.
    (call_ref $private-sub
      (i32.const 0)
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $unreferenced (type $2)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $unreferenced (type $private-sub) (param $unused i32)
    ;; The unreferenced function can still be optimized.
    (nop)
  )
)

(module
  ;; CHECK:      (type $public-super (sub (func (param i32))))
  (type $public-super (sub (func (param i32))))
  ;; CHECK:      (type $private-sub (sub $public-super (func (param i32))))
  (type $private-sub (sub $public-super (func (param i32))))

  ;; CHECK:      (global $public (ref null $public-super) (ref.null nofunc))
  (global $public (export "") (ref null $public-super) (ref.null nofunc))

  ;; Even though there are no functions with type $private-sub, we cannot
  ;; optimize it because we cannot rewrite its public supertype.
  ;; CHECK:      (global $private (ref null $private-sub) (ref.null nofunc))
  (global $private (ref null $private-sub) (ref.null nofunc))
)

;; CHECK:      (export "" (global $public))
(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $used-super (sub (func (param i32))))
  (type $used-super (sub (func (param i32))))
  ;; CHECK:       (type $unused-sub (sub $used-super (func (param i32))))
  (type $unused-sub (sub $used-super (func (param i32))))

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; Even though there are no functions with type $unused-sub, we cannot
  ;; optimize it because we do not optimize its supertype.
  ;; CHECK:      (global $unused (ref null $unused-sub) (ref.null nofunc))
  (global $unused (ref null $unused-sub) (ref.null nofunc))

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (func $referenced (type $used-super) (param $used i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $used-super) (param $used i32)
    (drop
      (ref.func $referenced)
    )
    (global.set $g
      (local.get $used)
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (func (param i32))))
    (type $super (sub (func (param i32))))
    ;; CHECK:       (type $sub1 (sub $super (func (param i32))))
    (type $sub1 (sub $super (func (param i32))))
    ;; CHECK:       (type $sub2 (sub $super (func (param i32))))
    (type $sub2 (sub $super (func (param i32))))
  )

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

  ;; CHECK:      (elem declare func $referenced)

  ;; CHECK:      (tag $t (type $sub2) (param i32))
  (tag $t (type $sub2))

  ;; CHECK:      (func $referenced (type $sub1) (param $0 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced (type $sub1) (param i32)
    ;; The tag type cannot be rewritten, so no other type in its type tree can
    ;; be rewritten. We cannot optimize this referenced function.
    (drop
      (ref.func $referenced)
    )
  )

  ;; CHECK:      (func $unreferenced (type $3)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $unreferenced (type $sub1) (param i32)
    ;; The unreferenced function can still be optimized.
    (nop)
  )
)

;; Test optimizations on subtyping trees of function types.

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (func (param i32 f32))))
    (type $super (sub (func (param i32 i64 f32))))
    ;; CHECK:       (type $sub2 (sub $super (func (param i32 f32))))

    ;; CHECK:       (type $sub1 (sub $super (func (param i32 f32))))
    (type $sub1 (sub $super (func (param i32 i64 f32))))
    (type $sub2 (sub $super (func (param i32 i64 f32))))
  )

  ;; CHECK:      (global $g1 (mut i32) (i32.const 0))
  (global $g1 (mut i32) (i32.const 0))
  ;; CHECK:      (global $g2 (mut f32) (f32.const 0))
  (global $g2 (mut f32) (f32.const 0))

  ;; CHECK:      (elem declare func $referenced1 $referenced2)

  ;; CHECK:      (func $referenced1 (type $sub1) (param $0 i32) (param $1 f32)
  ;; CHECK-NEXT:  (local $2 i64)
  ;; CHECK-NEXT:  (global.set $g1
  ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced1 (type $sub1) (param i32 i64 f32)
    ;; Use parameter 0. Only parameter 1 will be removed.
    (global.set $g1
      (local.get 0)
    )
    (drop
      (ref.func $referenced1)
    )
  )

  ;; CHECK:      (func $referenced2 (type $sub2) (param $0 i32) (param $1 f32)
  ;; CHECK-NEXT:  (local $2 i64)
  ;; CHECK-NEXT:  (global.set $g2
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced2 (type $sub2) (param i32 i64 f32)
    ;; Use parameter 2. Only parameter 1 will be removed.
    (global.set $g2
      (local.get 2)
    )
    (drop
      (ref.func $referenced2)
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (func)))
    (type $super (sub (func (param i32 i32))))
    ;; CHECK:       (type $sub2 (sub $super (func)))

    ;; CHECK:       (type $sub1 (sub $super (func)))
    (type $sub1 (sub $super (func (param i32 i32))))
    (type $sub2 (sub $super (func (param i32 i32))))
  )

  ;; CHECK:      (elem declare func $referenced1 $referenced2)

  ;; CHECK:      (func $referenced1 (type $sub1)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $sub2
  ;; CHECK-NEXT:   (ref.func $referenced2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced1 (type $sub1) (param i32 i32)
    ;; Forward parameter 0 as parameter 1 in another type in the same tree.
    (call_ref $sub2
      (i32.const 0)
      (local.get 0)
      (ref.func $referenced2)
    )
  )

  ;; CHECK:      (func $referenced2 (type $sub2)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $sub1
  ;; CHECK-NEXT:   (ref.func $referenced1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced2 (type $sub2) (param i32 i32)
    ;; Forward parameter 1 as parameter 0 in another type in the same tree.
    (call_ref $sub1
      (local.get 1)
      (i32.const 1)
      (ref.func $referenced1)
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (func (param i32 i32))))
    (type $super (sub (func (param i32 i32))))
    ;; CHECK:       (type $sub2 (sub $super (func (param i32 i32))))

    ;; CHECK:       (type $sub1 (sub $super (func (param i32 i32))))
    (type $sub1 (sub $super (func (param i32 i32))))
    (type $sub2 (sub $super (func (param i32 i32))))
  )

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (elem declare func $referenced1 $referenced2)

  ;; CHECK:      (func $referenced1 (type $sub1) (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (call_ref $sub2
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:   (ref.func $referenced2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced1 (type $sub1) (param i32 i32)
    ;; Forward parameter 0 as parameter 1 in another type in the same tree. This
    ;; time also use parameter 1.
    (call_ref $sub2
      (i32.const 0)
      (local.get 0)
      (ref.func $referenced2)
    )
    (global.set $g
      (local.get 1)
    )
  )

  ;; CHECK:      (func $referenced2 (type $sub2) (param $0 i32) (param $1 i32)
  ;; CHECK-NEXT:  (call_ref $sub1
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:   (ref.func $referenced1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced2 (type $sub2) (param i32 i32)
    ;; Forward parameter 1 as parameter 0 in another type in the same tree.
    (call_ref $sub1
      (local.get 1)
      (i32.const 1)
      (ref.func $referenced1)
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (func)))
    (type $super (sub (func (param i32 i32))))
    ;; CHECK:       (type $sub2 (sub $super (func)))

    ;; CHECK:       (type $sub1 (sub $super (func)))
    (type $sub1 (sub $super (func (param i32 i32))))
    (type $sub2 (sub $super (func (param i32 i32))))
  )

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (elem declare func $referenced2)

  ;; CHECK:      (func $unreferenced1 (type $3) (param $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (call_ref $sub2
  ;; CHECK-NEXT:    (ref.func $referenced2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreferenced1 (type $sub1) (param i32 i32)
    ;; Forward parameter 0 as parameter 1 in another type in the same tree. This
    ;; time the first function is not referenced.
    (call_ref $sub2
      (i32.const 0)
      (local.get 0)
      (ref.func $referenced2)
    )
    (global.set $g
      (local.get 1)
    )
  )

  ;; CHECK:      (func $referenced2 (type $sub2)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local.set $2
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $unreferenced1
  ;; CHECK-NEXT:   (local.get $2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced2 (type $sub2) (param i32 i32)
    ;; Forward parameter 1 as parameter 0 in another type in the same tree (but
    ;; it's not referenced)
    (call $unreferenced1
      (local.get 1)
      (i32.const 1)
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $super (sub (func (param i32))))
    (type $super (sub (func (param i32 i32))))
    ;; CHECK:       (type $sub2 (sub $super (func (param i32))))

    ;; CHECK:       (type $sub1 (sub $super (func (param i32))))
    (type $sub1 (sub $super (func (param i32 i32))))
    (type $sub2 (sub $super (func (param i32 i32))))
  )

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (elem declare func $referenced1)

  ;; CHECK:      (func $referenced1 (type $sub1) (param $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (call $unreferenced2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced1 (type $sub1) (param i32 i32)
    ;; Forward parameter 0 as parameter 1 in another type in the same tree. This
    ;; time the second function is not referenced.
    (call $unreferenced2
      (i32.const 0)
      (local.get 0)
    )
    (global.set $g
      (local.get 1)
    )
  )

  ;; CHECK:      (func $unreferenced2 (type $3)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local.set $2
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $sub1
  ;; CHECK-NEXT:   (local.get $2)
  ;; CHECK-NEXT:   (ref.func $referenced1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreferenced2 (type $sub2) (param i32 i32)
    ;; Forward parameter 1 as parameter 0 in another type in the same tree.
    (call_ref $sub1
      (local.get 1)
      (i32.const 1)
      (ref.func $referenced1)
    )
  )
)

;; Tests with side effects and interesting usages.

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

  ;; CHECK:      (import "" "" (func $effect (type $0)))
  (import "" "" (func $effect))

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call $effect)
  ;; CHECK-NEXT:  (call $test)
  ;; CHECK-NEXT: )
  (func $test (param $unused i32)
    ;; The parameter is unused, but there is a side effect we cannot remove.
    (call $test
      (block (result i32)
        (call $effect)
        (local.get $unused)
      )
    )
  )
)

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

  ;; CHECK:      (import "" "" (func $effect (type $0)))
  (import "" "" (func $effect))

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (local $unused (ref any))
  ;; CHECK-NEXT:  (call $effect)
  ;; CHECK-NEXT:  (call $test)
  ;; CHECK-NEXT: )
  (func $test (param $unused (ref any))
    ;; Same, but now the unused parameter type is not defaultable. We can still
    ;; optimize.
    (call $test
      (block (result (ref any))
        (call $effect)
        (local.get $unused)
      )
    )
  )
)

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

  ;; CHECK:      (type $1 (func (result (ref any))))

  ;; CHECK:      (import "" "" (func $effect (type $0)))
  (import "" "" (func $effect))

  ;; CHECK:      (func $test (type $1) (result (ref any))
  ;; CHECK-NEXT:  (local $unused (ref any))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref any))
  ;; CHECK-NEXT:    (call $effect)
  ;; CHECK-NEXT:    (call $test)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT:  (local.set $unused
  ;; CHECK-NEXT:   (ref.i31
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $unused)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $unused)
  ;; CHECK-NEXT: )
  (func $test (param $unused (ref any)) (result (ref any))
    ;; Same, but now there are also subsequent gets of the local. The get that
    ;; can read the original parameter value must be removed.
    (drop
      (call $test
        (block (result (ref any))
          (call $effect)
          (local.get $unused)
        )
      )
    )
    (drop
      (local.get $unused)
    )
    ;; The gets after this do not actually use the parameter and are not
    ;; removed.
    (local.set $unused
      (ref.i31
        (i32.const 0)
      )
    )
    (drop
      (local.get $unused)
    )
    (local.get $unused)
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32)))

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $test (type $0) (param $used i32)
  ;; CHECK-NEXT:  (call $test
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    ;; The parameter is still not necessary for the recursive call alone, but it
    ;; is also used elsewhere.
    (call $test
      (local.get $used)
    )
    (global.set $g
      (local.get $used)
    )
  )
)

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

  ;; CHECK:      (import "" "" (func $imported (type $0) (param i32)))
  (import "" "" (func $imported (param i32)))

  ;; CHECK:      (func $test (type $0) (param $used i32)
  ;; CHECK-NEXT:  (call $imported
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    ;; Calling an imported function counts as a use.
    (call $imported
      (local.get $used)
    )
  )
)

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

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $1 (func (param i32)))

  ;; CHECK:       (type $2 (struct))

  ;; CHECK:      (import "" "" (func $imported (type $0) (param i32 i32)))
  (import "" "" (func $imported (param i32 i32)))

  ;; CHECK:      (func $test (type $1) (param $used i32)
  ;; CHECK-NEXT:  (call $imported
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    ;; If the imported function is never reached due to an unreachable child, we
    ;; could consider the get unused. But we leave handling this to DCE.
    (call $imported
      (local.get $used)
      (unreachable)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32)))

  ;; CHECK:       (type $cwe (func (param i32 funcref)))
  (type $cwe (func (param i32) (param funcref)))
  (import "binaryen-intrinsics" "call.without.effects"
    (func $call.without.effects (type $cwe) (param i32) (param funcref)))

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

  ;; CHECK:      (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $cwe) (param i32 funcref)))

  ;; CHECK:      (elem declare func $callee $referenced-same-type)

  ;; CHECK:      (func $test (type $0) (param $unused i32)
  ;; CHECK-NEXT:  (call $call.without.effects
  ;; CHECK-NEXT:   (local.get $unused)
  ;; CHECK-NEXT:   (ref.func $callee)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $unused i32)
    ;; We cannot yet optimize functions called via call.without.effects.
    (call $call.without.effects
      (local.get $unused)
      (ref.func $callee)
    )
  )

  ;; CHECK:      (func $callee (type $0) (param $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $callee (param $unused i32)
    (nop)
  )

  ;; CHECK:      (func $referenced-same-type (type $cwe) (param $0 i32) (param $1 funcref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.func $referenced-same-type)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $referenced-same-type (type $cwe) (param i32) (param funcref)
    ;; Since call.without.effects is modeled as an import, it inhibits the
    ;; optimization of other referenced functions that happen to share a type
    ;; with it.
    (drop
      (ref.func $referenced-same-type)
    )
  )

  ;; CHECK:      (func $unreferenced-same-type (type $2)
  ;; CHECK-NEXT:  (local $0 funcref)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT: )
  (func $unreferenced-same-type (type $cwe) (param i32) (param funcref)
    ;; But unreferenced functions with the same type can still be optimized.
  )
)

(module
  ;; CHECK:      (type $f (func (param i32)))
  (type $f (func (param i32)))
  ;; CHECK:      (import "" "" (func $imported (type $f) (param i32)))
  (import "" "" (func $imported (type $f) (param i32)))

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

  ;; CHECK:      (func $test (type $f) (param $used i32)
  ;; CHECK-NEXT:  (return_call $imported
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    ;; We should not be confused by return calls.
    (return_call $imported
      (local.get 0)
    )
  )
)

(module
  ;; CHECK:      (type $f (func (param i32)))
  (type $f (func (param i32)))
  ;; CHECK:      (import "" "" (func $imported (type $f) (param i32)))
  (import "" "" (func $imported (type $f) (param i32)))

  ;; CHECK:      (table $t 1 1 funcref)
  (table $t funcref (elem $imported))

  ;; CHECK:      (elem $implicit-elem (i32.const 0) $imported)

  ;; CHECK:      (func $test (type $f) (param $used i32)
  ;; CHECK-NEXT:  (return_call_indirect $t (type $f)
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    ;; We should not be confused by indirect return calls.
    (return_call_indirect (type $f)
      (local.get 0)
      (i32.const 0)
    )
  )
)

(module
  ;; CHECK:      (type $f (func (param i32)))
  (type $f (func (param i32)))
  ;; CHECK:      (import "" "" (func $imported (type $f) (param i32)))
  (import "" "" (func $imported (type $f) (param i32)))

  ;; CHECK:      (elem declare func $imported)

  ;; CHECK:      (func $test (type $f) (param $used i32)
  ;; CHECK-NEXT:  (return_call_ref $f
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:   (ref.func $imported)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    ;; We should not be confused by indirect reference return calls.
    (return_call_ref $f
      (local.get 0)
      (ref.func $imported)
    )
  )
)

;; Tests for what does or does not count as a use and removal of expressions.

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

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $test (param $unused i32)
    ;; The drop does not count as a use.
    (drop
      (local.get $unused)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32)))

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $test (type $0) (param $used i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    ;; The parameter is not used here, but it is below.
    (drop
      (local.get $used)
    )
    (global.set $g
      (local.get $used)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32) (result i32)))

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

  ;; CHECK:      (func $test (type $0) (param $used i32) (result i32)
  ;; CHECK-NEXT:  (local.get $used)
  ;; CHECK-NEXT: )
  (func $test (param $used i32) (result i32)
    ;; Returning the result of the local.get counts as using it.
    ;; TODO: Optimize out unused function results as well.
    (local.get $used)
  )
)

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

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $test (param $unused i32)
    ;; The parameter is unused even though it flows through a block.
    (drop
      (block (result i32)
        (local.get $unused)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32)))

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $test (type $0) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (local.get $used)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    ;; Now it is used even though it flows through a block.
    (global.set $g
      (block (result i32)
        (local.get $used)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32)))

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $test (type $0) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (i32.eqz
  ;; CHECK-NEXT:    (local.get $used)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    (global.set $g
      ;; The parameter can be used even if it flows through a unary op.
      (i32.eqz
        (local.get $used)
      )
    )
  )
)

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

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $1 (func (param i32)))

  ;; CHECK:       (type $2 (struct))

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $lhs (type $1) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (i32.add
  ;; CHECK-NEXT:    (local.get $used)
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $lhs (param $used i32)
    (global.set $g
      ;; The parameter can be used if it flows through either side of a binary
      ;; op.
      (i32.add
        (local.get $used)
        (i32.const 1)
      )
    )
  )

  ;; CHECK:      (func $rhs (type $1) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (i32.add
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (local.get $used)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $rhs (param $used i32)
    (global.set $g
      (i32.add
        (i32.const 1)
        (local.get $used)
      )
    )
  )

  ;; CHECK:      (func $dropped-lhs (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $dropped-lhs (param $unused i32)
    ;; If the result of the add is dropped, then the parameter is not used.
    (drop
      (i32.add
        (local.get $unused)
        (i32.const 1)
      )
    )
  )

  ;; CHECK:      (func $dropped-rhs (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $dropped-rhs (param $unused i32)
    (drop
      (i32.add
        (i32.const 1)
        (local.get $unused)
      )
    )
  )

  ;; CHECK:      (func $unreachable-lhs (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachable-lhs (param $unused i32)
    (global.set $g
      ;; Since the add never returns, we do not continue on to analyze the
      ;; global.set, so we consider the parameter unused.
      (i32.add
        (local.get $unused)
        (unreachable)
      )
    )
  )

  ;; CHECK:      (func $unreachable-rhs (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (i32.add
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:    (local.get $unused)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachable-rhs (param $unused i32)
    ;; Here the get does not read from the parameter because it is not
    ;; reachable. We can still optimize out the parameter, but we do not remove
    ;; the get.
    (global.set $g
      (i32.add
        (unreachable)
        (local.get $unused)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32)))

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

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

  ;; CHECK:      (func $div_s (type $0) (param $used i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.div_s
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (local.get $used)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $div_s (param $used i32)
    ;; Since division can trap, it counts as a use all on its own.
    (drop
      (i32.div_s
        (i32.const 1)
        (local.get $used)
      )
    )
  )

  ;; CHECK:      (func $div_u (type $0) (param $used i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.div_u
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (local.get $used)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $div_u (param $used i32)
    ;; Since division can trap, it counts as a use all on its own.
    (drop
      (i32.div_u
        (i32.const 1)
        (local.get $used)
      )
    )
  )

  ;; CHECK:      (func $numerator (type $2)
  ;; CHECK-NEXT:  (local $used i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $numerator (param $used i32)
    ;; This division is known not to trap, so we can optimize.
    (drop
      (i32.div_s
        (local.get $used)
        (i32.const 1)
      )
    )
  )

  ;; CHECK:      (func $numerator-obscured (type $0) (param $used i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.div_s
  ;; CHECK-NEXT:    (local.get $used)
  ;; CHECK-NEXT:    (block (result i32)
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $numerator-obscured (param $used i32)
    ;; The block prevents the effects analysis from knowing that the division
    ;; will not trap. We do not optimize even though the value of the numerator
    ;; cannot change trapping behavior.
    (drop
      (i32.div_s
        (local.get $used)
        (block (result i32)
          (i32.const 1)
        )
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32)))

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

  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))
  ;; CHECK:      (func $test (type $0) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (tuple.extract 2 1
  ;; CHECK-NEXT:    (tuple.make 2
  ;; CHECK-NEXT:     (local.get $used)
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    (global.set $g
      ;; We are not precise about which tuple elements are extracted, so this
      ;; counts as a use.
      ;; TODO: We could be more precise about tracking tuple elements.
      (tuple.extract 2 1
        (tuple.make 2
          (local.get 0)
          (i32.const 1)
        )
      )
    )
  )
)

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

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $1 (func (param i32)))

  ;; CHECK:       (type $2 (struct))

  ;; CHECK:      (import "" "" (func $effect (type $0)))
  (import "" "" (func $effect))
  ;; CHECK:      (global $g (mut i32) (i32.const 0))
  (global $g (mut i32) (i32.const 0))

  ;; CHECK:      (func $dropped-arm (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (nop)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $dropped-arm (param $unused i32)
    ;; local.gets of parameters in if arms can be removed. Here the ifFalse arm
    ;; would become empty, so we just remove it.
    (drop
      (if (result i32)
        (i32.const 0)
        (then
          (local.get $unused)
        )
        (else
          (local.get $unused)
        )
      )
    )
  )

  ;; CHECK:      (func $dropped-arm-effects (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (call $effect)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (else
  ;; CHECK-NEXT:    (call $effect)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $dropped-arm-effects (param $unused i32)
    ;; We must be careful to preserve the If structure so only the proper
    ;; effects are executed.
    (drop
      (if (result i32)
        (i32.const 0)
        (then
          (call $effect)
          (local.get $unused)
        )
        (else
          (call $effect)
          (local.get $unused)
        )
      )
    )
  )

  ;; CHECK:      (func $used-iftrue (type $1) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (if (result i32)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (then
  ;; CHECK-NEXT:     (local.get $used)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (else
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $used-iftrue (param $used i32)
    ;; local.gets of parameters in if arms can be treated like normal gets.
    (global.set $g
      (if (result i32)
        (i32.const 0)
        (then
          (local.get $used)
        )
        (else
          (i32.const 1)
        )
      )
    )
  )

  ;; CHECK:      (func $used-iffalse (type $1) (param $used i32)
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (if (result i32)
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:    (then
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (else
  ;; CHECK-NEXT:     (local.get $used)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $used-iffalse (param $used i32)
    ;; Same, with the local.get in the other arm.
    (global.set $g
      (if (result i32)
        (i32.const 0)
        (then
          (i32.const 1)
        )
        (else
          (local.get $used)
        )
      )
    )
  )

  ;; CHECK:      (func $condition (type $1) (param $used i32)
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (nop)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (else
  ;; CHECK-NEXT:    (nop)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $condition (param $used i32)
    ;; The value flowing into an if condition controls which arm is executed. If
    ;; the arms have side effects (which we conservatively assume they may),
    ;; then potentially changing the value of the condition by making it an
    ;; uninitialized local would be incorrect. Parameters flowing into if
    ;; conditions must be considered used.
    (if
      (local.get $used)
      (then
        (nop)
      )
      (else
        (nop)
      )
    )
  )

  ;; CHECK:      (func $condition-effect (type $1) (param $used i32)
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (else
  ;; CHECK-NEXT:    (nop)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $condition-effect (param $used i32)
    ;; This would start trapping where it might not have before if we optimized.
    (if
      (local.get $used)
      (then
        (unreachable)
      )
      (else
        (nop)
      )
    )
  )
)

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

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

  ;; CHECK:      (import "" "" (func $effect (type $1) (result i32)))
  (import "" "" (func $effect (result i32)))

  ;; CHECK:      (func $loop (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (loop $l
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (call $effect)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (br_if $l
  ;; CHECK-NEXT:    (call $effect)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $loop (param $unused i32)
    ;; We can optimize out the unused parameter and its gets, but the loop must
    ;; be preserved so the effects are executed the correct number of times.
    (drop
      (loop $l (result i32)
        (drop
          (call $effect)
        )
        (drop
          (local.get $unused)
        )
        (br_if $l
          (call $effect)
        )
        (local.get $unused)
      )
    )
  )

  ;; CHECK:      (func $loop-empty (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (loop $l
  ;; CHECK-NEXT:   (nop)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $loop-empty (param $unused i32)
    ;; We should not be tripped up when the entire loop body is erased.
    (drop
      (loop $l (result i32)
        (local.get $unused)
      )
    )
  )
)

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

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

  ;; CHECK:      (import "" "" (func $effect (type $0)))
  (import "" "" (func $effect))
  ;; CHECK:      (import "" "" (func $effect-i32 (type $1) (result i32)))
  (import "" "" (func $effect-i32 (result i32)))

  ;; CHECK:      (tag $e (type $0))
  (tag $e)

  ;; CHECK:      (func $try-empty (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (try
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:    (nop)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $try-empty (param $unused i32)
    (drop
      (try (result i32)
        (do
          (local.get $unused)
        )
      )
    )
  )

  ;; CHECK:      (func $try-effect (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (try
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:    (call $effect)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $try-effect (param $unused i32)
    (drop
      (try (result i32)
        (do
          (call $effect)
          (local.get $unused)
        )
      )
    )
  )

  ;; CHECK:      (func $try-catch (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (try
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch $e
  ;; CHECK-NEXT:    (nop)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch $e
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $effect)
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch_all
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result i32)
  ;; CHECK-NEXT:      (call $effect)
  ;; CHECK-NEXT:      (i32.const 2)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $try-catch (param $unused i32)
    (drop
      (try (result i32)
        (do
          (i32.const 0)
        )
        (catch $e
          (local.get $unused)
        )
        (catch $e
          (call $effect)
          (i32.const 1)
        )
        (catch_all
          (call $effect)
          (i32.const 2)
        )
      )
    )
  )
)

(module
  ;; CHECK:      (type $0 (func (param (ref any))))

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

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $2 (func))

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

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

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

  ;; CHECK:      (type $6 (func (param i32) (result i32)))

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $7 (func (param i32)))

  ;; CHECK:       (type $8 (struct))

  ;; CHECK:      (import "" "" (func $effect (type $1) (result i32)))
  (import "" "" (func $effect (result i32)))
  ;; CHECK:      (import "" "" (func $effect-i32 (type $1) (result i32)))
  (import "" "" (func $effect-i32 (result i32)))

  ;; CHECK:      (func $labeled-block (type $2)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $labeled-block (param $unused i32)
    ;; We do not bother keeping what would be an empty block.
    (drop
      (block $l (result i32)
        (local.get $unused)
      )
    )
  )

  ;; CHECK:      (func $labeled-block-effect (type $2)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (block $trampoline0
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block $l (result i32)
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $effect)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (br $trampoline0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $labeled-block-effect (param $unused i32)
    ;; We keep the effect, but we don't know that it doesn't branch, so we set
    ;; up the trampoline anyway.
    (drop
      (block $l (result i32)
        (drop
          (call $effect)
        )
        (local.get $unused)
      )
    )
  )

  ;; CHECK:      (func $br (type $2)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $l (result i32)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (call $effect)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (br $l
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (call $effect)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $unused)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $br (param $unused i32)
    ;; Here the local.get is not reachable, so we don't try to remove it.
    (drop
      (block $l (result i32)
        (drop
          (call $effect)
        )
        (br $l
          (i32.const 0)
        )
        (drop
          (call $effect)
        )
        (local.get $unused)
      )
    )
  )

  ;; CHECK:      (func $br-if (type $7) (param $used i32)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (block $trampoline0
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block $l (result i32)
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (call $effect)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (br_if $l
  ;; CHECK-NEXT:        (i32.const 0)
  ;; CHECK-NEXT:        (local.get $used)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (call $effect)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (br $trampoline0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $br-if (param $unused i32) (param $used i32)
    (drop
      (block $l (result i32)
        (drop
          (call $effect)
        )
        (drop
          ;; The br_if counts as a use because it branches out.
          ;; TODO: A control flow aware analysis could still optimize this.
          (br_if $l
            (i32.const 0)
            (local.get $used)
          )
        )
        (drop
          (call $effect)
        )
        (local.get $unused)
      )
    )
  )

  ;; CHECK:      (func $br-table-mixed (type $6) (param $used i32) (result i32)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (block $outer (result i32)
  ;; CHECK-NEXT:   (block $trampoline0
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block $l (result i32)
  ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (drop
  ;; CHECK-NEXT:        (call $effect)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (drop
  ;; CHECK-NEXT:        (block $inner (result i32)
  ;; CHECK-NEXT:         (br_table $inner $l $outer
  ;; CHECK-NEXT:          (i32.const 0)
  ;; CHECK-NEXT:          (local.get $used)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (drop
  ;; CHECK-NEXT:        (call $effect)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (br $trampoline0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $br-table-mixed (param $unused i32) (param $used i32) (result i32)
    ;; This is a case where it would be impossible to remove the branch values
    ;; because they also go out to labels we are not modifying.
    (block $outer (result i32)
      (drop
        (block $l (result i32)
          (drop
            (call $effect)
          )
          (drop
            (block $inner (result i32)
              (br_table $inner $l $outer
                (i32.const 0)
                (local.get $used)
              )
            )
          )
          (drop
            (call $effect)
          )
          (local.get $unused)
        )
      )
      (i32.const 1)
    )
  )

  ;; CHECK:      (func $br-on-non-null (type $0) (param $used (ref any))
  ;; CHECK-NEXT:  (local $unused (ref any))
  ;; CHECK-NEXT:  (block $trampoline0
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block $l (result (ref any))
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (call $effect)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (br_on_non_null $l
  ;; CHECK-NEXT:       (local.get $used)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (call $effect)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (br $trampoline0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $br-on-non-null (param $unused (ref any)) (param $used (ref any))
    (drop
      (block $l (result (ref any))
        (drop
          (call $effect)
        )
        (br_on_non_null $l
          (local.get $used)
        )
        (drop
          (call $effect)
        )
        (local.get $unused)
      )
    )
  )

  ;; CHECK:      (func $br-on-cast (type $0) (param $used (ref any))
  ;; CHECK-NEXT:  (local $unused (ref any))
  ;; CHECK-NEXT:  (block $trampoline0
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block $l (result (ref any))
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (call $effect)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (br_on_cast $l (ref any) (ref i31)
  ;; CHECK-NEXT:        (local.get $used)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (call $effect)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (br $trampoline0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $br-on-cast (param $unused (ref any)) (param $used (ref any))
    (drop
      (block $l (result (ref any))
        (drop
          (call $effect)
        )
        (drop
          (br_on_cast $l (ref any) (ref i31)
            (local.get $used)
          )
        )
        (drop
          (call $effect)
        )
        (local.get $unused)
      )
    )
  )

  ;; CHECK:      (func $br-on-cast-fail (type $0) (param $used (ref any))
  ;; CHECK-NEXT:  (local $unused (ref any))
  ;; CHECK-NEXT:  (block $trampoline0
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block $l (result (ref any))
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (call $effect)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (br_on_cast_fail $l (ref any) (ref i31)
  ;; CHECK-NEXT:        (local.get $used)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (call $effect)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (br $trampoline0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $br-on-cast-fail (param $unused (ref any)) (param $used (ref any))
    (drop
      (block $l (result (ref any))
        (drop
          (call $effect)
        )
        (drop
          (br_on_cast_fail $l (ref any) (ref i31)
            (local.get $used)
          )
        )
        (drop
          (call $effect)
        )
        (local.get $unused)
      )
    )
  )

  ;; CHECK:      (func $no-overwrite-label (type $2)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (block $trampoline0
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block $outer (result i32)
  ;; CHECK-NEXT:     (block $inner
  ;; CHECK-NEXT:      (br_if $inner
  ;; CHECK-NEXT:       (i32.const 0)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (br $trampoline0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $no-overwrite-label (param $unused i32)
    (drop
      ;; When we add the trampoline, we should not accidentally remove the used
      ;; $inner label.
      (block $outer (result i32)
        (block $inner
          (br_if $inner
            (i32.const 0)
          )
        )
        (local.get $unused)
      )
    )
  )

  ;; CHECK:      (func $no-skip-effect (type $2)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (block $trampoline0
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block $block1 (result i32)
  ;; CHECK-NEXT:     (nop)
  ;; CHECK-NEXT:     (br $trampoline0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (call $effect-i32)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $no-skip-effect (param $1 i32)
    (drop
      ;; We should not trampoline past the effect on the right hand side of the
      ;; removed binary operation.
      (i32.add
        (block $block1 (result i32)
          (nop)
          (local.get $1)
        )
        (call $effect-i32)
      )
    )
  )
)

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

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

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $2 (func))

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

  ;; CHECK:      (import "" "" (func $effect (type $0) (param i32)))
  (import "" "" (func $effect (param i32)))
  ;; CHECK:      (tag $e (type $3))
  (tag $e)

  ;; CHECK:      (func $test (type $1) (param $used1 i32) (param $used2 i32)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (call $effect
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block $trampoline0
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block $l (result i32)
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (call $effect
  ;; CHECK-NEXT:       (i32.const 1)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (loop $loop
  ;; CHECK-NEXT:       (call $effect
  ;; CHECK-NEXT:        (i32.const 2)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (br_if $loop
  ;; CHECK-NEXT:        (local.get $used1)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (try
  ;; CHECK-NEXT:        (do
  ;; CHECK-NEXT:         (drop
  ;; CHECK-NEXT:          (block (result i32)
  ;; CHECK-NEXT:           (call $effect
  ;; CHECK-NEXT:            (i32.const 3)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:           (i32.const 0)
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:        (catch $e
  ;; CHECK-NEXT:         (call $effect
  ;; CHECK-NEXT:          (i32.const 4)
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:         (if
  ;; CHECK-NEXT:          (local.get $used2)
  ;; CHECK-NEXT:          (then
  ;; CHECK-NEXT:           (drop
  ;; CHECK-NEXT:            (block (result i32)
  ;; CHECK-NEXT:             (call $effect
  ;; CHECK-NEXT:              (i32.const 5)
  ;; CHECK-NEXT:             )
  ;; CHECK-NEXT:             (i32.const 1)
  ;; CHECK-NEXT:            )
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:          (else
  ;; CHECK-NEXT:           (call $effect
  ;; CHECK-NEXT:            (i32.const 6)
  ;; CHECK-NEXT:           )
  ;; CHECK-NEXT:          )
  ;; CHECK-NEXT:         )
  ;; CHECK-NEXT:        )
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (br $trampoline0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $unused i32) (param $used1 i32) (param $used2 i32)
    ;; Test mixed and nested control flow structures, all of which will have
    ;; their values removed at the same time. The locations of the effects
    ;; inside and before the control flow structures should remain unchanged.
    (drop
      (block (result i32)
        (call $effect (i32.const 0))
        (block $l (result i32)
          (call $effect (i32.const 1))
          (loop $loop (result i32)
            (call $effect (i32.const 2))
            (br_if $loop (local.get $used1))
            (try (result i32)
              (do
                (call $effect (i32.const 3))
                (i32.const 0)
              )
              (catch $e
                (call $effect (i32.const 4))
                (if (result i32)
                  (local.get $used2)
                  (then
                    (call $effect (i32.const 5))
                    (i32.const 1)
                  )
                  (else
                    (call $effect (i32.const 6))
                    (local.get $unused)
                  )
                )
              )
            )
          )
        )
      )
    )
  )
)

(module
  ;; CHECK:      (type $f (func))
  (type $f (func (param i32)))
  ;; CHECK:      (type $1 (func (result i32)))

  ;; CHECK:      (import "" "" (func $effect (type $1) (result i32)))
  (import "" "" (func $effect (result i32)))
  ;; CHECK:      (table $t 0 funcref)
  (table $t 0 funcref)
  ;; CHECK:      (func $test (type $f)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (block $trampoline0
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (block $l (result i32)
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (call_indirect $t (type $f)
  ;; CHECK-NEXT:       (call $effect)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (br $trampoline0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (type $f) (param $unused i32)
    ;; Regression test. If the call_indirect is incorrectly marked for removal
    ;; during optimization, the $effect will end up appearing in the IR twice.
    (drop
      (block $l (result i32)
        (call_indirect (type $f)
          (local.get $unused)
          (call $effect)
        )
        (local.get $unused)
      )
    )
  )
)

(module
  ;; CHECK:      (type $f (func (param (ref any))))
  (type $f (func (param (ref any))))
  ;; CHECK:      (type $1 (func (result (ref $f))))

  ;; CHECK:      (import "" "" (func $effect (type $1) (result (ref $f))))
  (import "" "" (func $effect (result (ref $f))))
  ;; CHECK:      (func $test (type $f) (param $used (ref any))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block $l (result (ref any))
  ;; CHECK-NEXT:    (call_ref $f
  ;; CHECK-NEXT:     (local.get $used)
  ;; CHECK-NEXT:     (call $effect)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $used)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used (ref any))
    ;; Similarly, if the call_ref here is incorrectly marked for removal, it
    ;; it will incorrectly be removed. This also tests that we properly
    ;; propagate the use of the parameter in the call_ref of public type $f back
    ;; to the caller even though there is no referenced function of type $f.
    (drop
      (block $l (result (ref any))
        (call_ref $f
          (local.get $used)
          (call $effect)
        )
        (local.get $used)
      )
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $f (func))
  (type $f (func (param i32)))
  ;; CHECK:       (type $1 (func))

  ;; CHECK:      (type $2 (func (param (ref null $f))))

  ;; CHECK:      (func $test (type $2) (param $used (ref null $f))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_ref $f
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used (ref null $f))
    ;; We should not get confused when a parameter is the target of a call_ref.
    (call_ref $f
      (i32.const 0)
      (local.get $used)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32)))

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

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $f (func))
  (type $f (func (param i64)))

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

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

  ;; CHECK:      (func $test (type $0) (param $used i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (i64.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call_indirect $t (type $f)
  ;; CHECK-NEXT:   (local.get $used)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $used i32)
    ;; Same with a call_indirect.
    (call_indirect (type $f)
      (i64.const 0)
      (local.get $used)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func))

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

  ;; CHECK:      (tag $e (type $1) (param i32))
  (tag $e (param i32))

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (try
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:    (nop)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch $e
  ;; CHECK-NEXT:    (local.set $0
  ;; CHECK-NEXT:     (pop i32)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (local.get $0)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (call $callee)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (nop)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    (try
      (do
        (nop)
      )
      (catch $e
        ;; When we optimize out the parameter, the dropped pop will be inside a
        ;; new block. This is invalid, so we must fix it up.
        (call $callee
          (pop i32)
        )
        (nop)
      )
    )
  )

  ;; CHECK:      (func $callee (type $0)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $callee (param $unused i32)
    (nop)
  )
)

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

  ;; CHECK:      (import "" "" (func $effect (type $0)))
  (import "" "" (func $effect))
  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (loop $l
  ;; CHECK-NEXT:     (call $effect)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $0 i32)
    ;; Regression test. A previous version of the expression removal code would
    ;; incorrectly duplicate the loop label and effect in the output.
    (drop
      (block (result i32)
        (drop
          (block (result i32)
            (drop
              (loop $l (result i32)
                (call $effect)
                (local.get $0)
              )
            )
            (i32.const 0)
          )
        )
        (local.get $0)
      )
    )
  )
)

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

  ;; CHECK:      (func $test (type $0) (result i32)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $unused i32) (result i32)
    ;; If we did not remove the If from the set of removed expressions after
    ;; processing it once, then when we process it a second time it would fail
    ;; the assertion that removed Ifs must have two arms.
    (i32.add
      (if (result i32)
        (unreachable)
        (then
          (i32.const 0)
        )
        (else
          (local.get $unused)
        )
      )
      (local.get $unused)
    )
  )
)

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

  ;; CHECK:      (func $test (type $0) (param $x i32)
  ;; CHECK-NEXT:  (local $unused (ref any))
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $unused
  ;; CHECK-NEXT:     (ref.i31
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $test (param $unused (ref any)) (param $x i32)
    (if
      (local.get $x)
      (then
        (local.set $unused
          (ref.i31
            (i32.const 0)
          )
        )
      )
    )
    ;; We can optimize this out even though it might not read from the
    ;; parameter. This should not produce validation errors about reads of
    ;; uninitialized parameters.
    (drop
      (local.get $unused)
    )
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param (ref any) i32)))

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

  ;; CHECK:      (global $g (mut anyref) (ref.null none))
  (global $g (mut anyref) (ref.null none))

  ;; CHECK:      (func $test (type $0) (param $unused (ref any)) (param $x i32)
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $unused
  ;; CHECK-NEXT:     (ref.i31
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $unused)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $unused (ref any)) (param $x i32)
    (if
      (local.get $x)
      (then
        (local.set $unused
          (ref.i31
            (i32.const 0)
          )
        )
      )
    )
    ;; Same, but now the parameter is actually used and we do not optimize.
    (global.set $g
      (local.get $unused)
    )
  )
)

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

  ;; CHECK:      (import "" "" (global $g (mut (ref $struct))))
  (import "" "" (global $g (mut (ref $struct))))

  ;; CHECK:      (func $test (type $1)
  ;; CHECK-NEXT:  (local $a (ref $struct))
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $unused i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.tee $a
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (global.set $g
  ;; CHECK-NEXT:   (local.get $a)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test (param $unused i32)
    (local $a (ref $struct))
    (local $1 i32)
    ;; If we were to run the non-defaultable local fixup after renumbering
    ;; locals but before updating function types, then the fixup code would get
    ;; confused and think that this is a get of an uninitialized non-nullable
    ;; local because it thinks local index 1 has type (ref $struct).
    (drop
      (local.get $1)
    )
    ;; Set $a just to make the get below valid in the input.
    (local.set $a
      (unreachable)
    )
    ;; This only validates if $a remains non-nullable. If the confused
    ;; non-defaultable local fixup runs, this will become invalid.
    (global.set $g
      (local.get $a)
    )
  )
)
