| ;; 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) |
| ) |
| ) |
| ) |