blob: b6ed701f7f1571efadc66085f5d8ef488a9191c8 [file] [log] [blame]
;; 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)
)
)
)