| ;; 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) | |
| ) | |
| ) | |
| ;; 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: (import "" "" (func $effect (type $0))) | |
| (import "" "" (func $effect)) | |
| ;; CHECK: (func $test (type $0) | |
| ;; CHECK-NEXT: (local $unused (ref any)) | |
| ;; CHECK-NEXT: (block | |
| ;; CHECK-NEXT: (call $effect) | |
| ;; CHECK-NEXT: (call $test) | |
| ;; 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: ) | |
| (func $test (param $unused (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. | |
| (call $test | |
| (block (result (ref any)) | |
| (call $effect) | |
| (local.get $unused) | |
| ) | |
| ) | |
| (drop | |
| (local.get $unused) | |
| ) | |
| ;; The get after this does not actually use the parameter and is not | |
| ;; removed. | |
| (local.set $unused | |
| (ref.i31 | |
| (i32.const 0) | |
| ) | |
| ) | |
| (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: (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: (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)) | |
| ;; CHECK: (type $4 (func)) | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $5 (func (param i32))) | |
| ;; CHECK: (type $6 (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 $5) (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 $5) (param $used i32) | |
| ;; CHECK-NEXT: (local $unused i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; 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: ) | |
| ;; CHECK-NEXT: ) | |
| (func $br-table-mixed (param $unused i32) (param $used 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. | |
| (drop | |
| (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)) | |
| ;; CHECK: (global $g (mut i32) (i32.const 0)) | |
| (global $g (mut i32) (i32.const 0)) | |
| ;; CHECK: (func $test (type $0) | |
| ;; CHECK-NEXT: (local $unused i32) | |
| ;; CHECK-NEXT: (global.set $g | |
| ;; 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: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $unused 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. | |
| (global.set $g | |
| (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.set $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) | |
| ) | |
| ) | |
| ) |