| ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. |
| ;; RUN: foreach %s %t wasm-opt --signature-pruning --closed-world -all -S -o - | filecheck %s |
| |
| (module |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $0 (func)) |
| |
| ;; CHECK: (type $sig (sub (func (param i32 f64)))) |
| (type $sig (sub (func (param i32) (param i64) (param f32) (param f64)))) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (elem declare func $foo) |
| |
| ;; CHECK: (func $foo (type $sig) (param $0 i32) (param $1 f64) |
| ;; CHECK-NEXT: (local $2 f32) |
| ;; CHECK-NEXT: (local $3 i64) |
| ;; CHECK-NEXT: (i32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (f64.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64) |
| ;; Use the first and last parameter. The middle parameters will be removed |
| ;; both from the function and from $sig, and also in the calls below. |
| (i32.store |
| (i32.const 0) |
| (local.get $i32) |
| ) |
| (f64.store |
| (i32.const 0) |
| (local.get $f64) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $0) |
| ;; CHECK-NEXT: (call $foo |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (f64.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call_ref $sig |
| ;; CHECK-NEXT: (i32.const 4) |
| ;; CHECK-NEXT: (f64.const 7) |
| ;; CHECK-NEXT: (ref.func $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller |
| (call $foo |
| (i32.const 0) |
| (i64.const 1) |
| (f32.const 2) |
| (f64.const 3) |
| ) |
| (call_ref $sig |
| (i32.const 4) |
| (i64.const 5) |
| (f32.const 6) |
| (f64.const 7) |
| (ref.func $foo) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $0 (func)) |
| |
| ;; CHECK: (type $sig (sub (func (param i64 f32)))) |
| (type $sig (sub (func (param i32) (param i64) (param f32) (param f64)))) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (elem declare func $foo) |
| |
| ;; CHECK: (func $foo (type $sig) (param $0 i64) (param $1 f32) |
| ;; CHECK-NEXT: (local $2 f64) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (i64.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (f32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64) |
| ;; Use the middle two parameters. |
| (i64.store |
| (i32.const 0) |
| (local.get $i64) |
| ) |
| (f32.store |
| (i32.const 0) |
| (local.get $f32) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $0) |
| ;; CHECK-NEXT: (call $foo |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: (f32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call_ref $sig |
| ;; CHECK-NEXT: (i64.const 5) |
| ;; CHECK-NEXT: (f32.const 6) |
| ;; CHECK-NEXT: (ref.func $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller |
| (call $foo |
| (i32.const 0) |
| (i64.const 1) |
| (f32.const 2) |
| (f64.const 3) |
| ) |
| (call_ref $sig |
| (i32.const 4) |
| (i64.const 5) |
| (f32.const 6) |
| (f64.const 7) |
| (ref.func $foo) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $0 (func)) |
| |
| ;; CHECK: (type $sig (sub (func (param i64 f32)))) |
| (type $sig (sub (func (param i32) (param i64) (param f32) (param f64)))) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (elem declare func $foo) |
| |
| ;; CHECK: (func $foo (type $sig) (param $0 i64) (param $1 f32) |
| ;; CHECK-NEXT: (local $2 f64) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (i64.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (f32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64) |
| ;; Use the middle two parameters. The other two vanish. |
| (i64.store |
| (i32.const 0) |
| (local.get $i64) |
| ) |
| (f32.store |
| (i32.const 0) |
| (local.get $f32) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $0) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $caller) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $foo |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: (f32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call_ref $sig |
| ;; CHECK-NEXT: (i64.const 5) |
| ;; CHECK-NEXT: (f32.const 6) |
| ;; CHECK-NEXT: (ref.func $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller |
| ;; As above, but now one of the unused parameters has a side effect. We |
| ;; move it to a local, which allows us to remove it (and also the last, |
| ;; which is trivial). |
| (call $foo |
| (block (result i32) |
| (call $caller) |
| (i32.const 0) |
| ) |
| (i64.const 1) |
| (f32.const 2) |
| (f64.const 3) |
| ) |
| (call_ref $sig |
| (i32.const 4) |
| (i64.const 5) |
| (f32.const 6) |
| (f64.const 7) |
| (ref.func $foo) |
| ) |
| ) |
| ) |
| |
| ;; As above, but with the effects on a call_ref. Once more, we can optimize |
| ;; even with effects on a param, using locals. |
| (module |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $0 (func)) |
| |
| ;; CHECK: (type $sig (sub (func (param i64 f32)))) |
| (type $sig (sub (func (param i32) (param i64) (param f32) (param f64)))) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (elem declare func $foo) |
| |
| ;; CHECK: (func $foo (type $sig) (param $0 i64) (param $1 f32) |
| ;; CHECK-NEXT: (local $2 f64) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (i64.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (f32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64) |
| (i64.store |
| (i32.const 0) |
| (local.get $i64) |
| ) |
| (f32.store |
| (i32.const 0) |
| (local.get $f32) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $0) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (call $foo |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: (f32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $caller) |
| ;; CHECK-NEXT: (i32.const 4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call_ref $sig |
| ;; CHECK-NEXT: (i64.const 5) |
| ;; CHECK-NEXT: (f32.const 6) |
| ;; CHECK-NEXT: (ref.func $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller |
| (call $foo |
| (i32.const 0) |
| (i64.const 1) |
| (f32.const 2) |
| (f64.const 3) |
| ) |
| (call_ref $sig |
| (block (result i32) |
| (call $caller) |
| (i32.const 4) |
| ) |
| (i64.const 5) |
| (f32.const 6) |
| (f64.const 7) |
| (ref.func $foo) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $0 (func)) |
| |
| ;; CHECK: (type $sig (sub (func))) |
| (type $sig (sub (func (param i32) (param i64) (param f32) (param f64)))) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (elem declare func $foo) |
| |
| ;; CHECK: (func $foo (type $sig) |
| ;; CHECK-NEXT: (local $0 f64) |
| ;; CHECK-NEXT: (local $1 f32) |
| ;; CHECK-NEXT: (local $2 i64) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) (param $i64 i64) (param $f32 f32) (param $f64 f64) |
| ;; Use nothing at all: all params can be removed. |
| ) |
| |
| ;; CHECK: (func $caller (type $0) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: (call_ref $sig |
| ;; CHECK-NEXT: (ref.func $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller |
| (call $foo |
| (i32.const 0) |
| (i64.const 1) |
| (f32.const 2) |
| (f64.const 3) |
| ) |
| (call_ref $sig |
| (i32.const 4) |
| (i64.const 5) |
| (f32.const 6) |
| (f64.const 7) |
| (ref.func $foo) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $0 (func)) |
| |
| ;; CHECK: (type $sig (sub (func))) |
| (type $sig (sub (func (param i32)))) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (func $foo (type $sig) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) |
| ;; Use the parameters' index, but not its value. We can still remove it, |
| ;; and the value set in the function is then set to a local and not a param, |
| ;; which works just as well. |
| (local.set $i32 |
| (i32.const 1) |
| ) |
| (i32.store |
| (i32.const 0) |
| (local.get $i32) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: ) |
| (func $caller |
| (local $x i32) |
| (call $foo |
| ;; (avoid passing in a constant value to avoid other opts kicking in) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $sig (sub (func))) |
| (type $sig (sub (func (param i32)))) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (func $foo (type $sig) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) |
| ;; This function does not use the parameter. It also has no calls, but that |
| ;; is not a problem - we can still remove the parameter. |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $sig (sub (func (param i32)))) |
| (type $sig (sub (func (param i32)))) |
| |
| ;; As above, but now an import also uses this signature, which prevents us |
| ;; from changing anything. |
| ;; CHECK: (import "out" "func" (func $import (type $sig) (param i32))) |
| (import "out" "func" (func $import (type $sig) (param i32))) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (func $foo (type $sig) (param $i32 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $sig (sub (func (param i32)))) |
| (type $sig (sub (func (param i32)))) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (func $foo (type $sig) (param $i32 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) |
| ) |
| |
| ;; CHECK: (func $bar (type $sig) (param $i32 i32) |
| ;; CHECK-NEXT: (i32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $i32) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bar (type $sig) (param $i32 i32) |
| ;; As above, but now there is a second (non-imported) function using this |
| ;; signature, and it does use the param, so we cannot optimize. |
| (i32.store |
| (i32.const 0) |
| (local.get $i32) |
| ) |
| ) |
| ) |
| |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $sig2 (sub (func (param i32)))) |
| |
| ;; CHECK: (type $sig (sub (func))) |
| (type $sig (sub (func (param i32)))) |
| |
| (type $sig2 (sub (func (param i32)))) |
| ) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (func $foo (type $sig) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) |
| ) |
| |
| ;; CHECK: (func $bar (type $sig2) (param $i32 i32) |
| ;; CHECK-NEXT: (i32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $i32) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bar (type $sig2) (param $i32 i32) |
| ;; As above, but now the second function has a different signature, so we |
| ;; can optimize one while not modifying the other. |
| (i32.store |
| (i32.const 0) |
| (local.get $i32) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $0 (func)) |
| |
| ;; CHECK: (type $sig (sub (func))) |
| (type $sig (sub (func (param i32)))) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (elem declare func $bar $foo) |
| |
| ;; CHECK: (func $foo (type $sig) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) |
| ) |
| |
| ;; CHECK: (func $bar (type $sig) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $bar (type $sig) (param $i32 i32) |
| ;; As above, but the second function also does not use the parameter, and |
| ;; has the same type. We can optimize both at once. |
| ) |
| |
| ;; CHECK: (func $caller (type $0) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: (call $bar) |
| ;; CHECK-NEXT: (call_ref $sig |
| ;; CHECK-NEXT: (ref.func $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call_ref $sig |
| ;; CHECK-NEXT: (ref.func $bar) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller |
| (call $foo |
| (i32.const 0) |
| ) |
| (call $bar |
| (i32.const 1) |
| ) |
| (call_ref $sig |
| (i32.const 2) |
| (ref.func $foo) |
| ) |
| (call_ref $sig |
| (i32.const 2) |
| (ref.func $bar) |
| ) |
| ) |
| |
| ;; CHECK: (func $caller-2 (type $0) |
| ;; CHECK-NEXT: (call $bar) |
| ;; CHECK-NEXT: (call_ref $sig |
| ;; CHECK-NEXT: (ref.func $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller-2 |
| ;; Also add some more calls to see they are updated too. |
| (call $bar |
| (i32.const 1) |
| ) |
| (call_ref $sig |
| (i32.const 2) |
| (ref.func $foo) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; The presence of a table prevents us from doing any optimizations. |
| (table 1 1 anyref) |
| |
| ;; CHECK: (type $sig (sub (func (param i32)))) |
| (type $sig (sub (func (param i32)))) |
| |
| ;; CHECK: (table $0 1 1 anyref) |
| |
| ;; CHECK: (func $foo (type $sig) (param $i32 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig) (param $i32 i32) |
| ) |
| ) |
| |
| ;; Exports cannot be optimized in any way: we cannot remove parameters from |
| ;; them, and also we cannot apply constant parameter values either. |
| (module |
| ;; CHECK: (type $sig (sub (func (param i32)))) |
| (type $sig (sub (func (param i32)))) |
| |
| ;; CHECK: (type $1 (func)) |
| |
| ;; CHECK: (export "foo" (func $foo)) |
| |
| ;; CHECK: (export "bar" (func $bar)) |
| |
| ;; CHECK: (func $foo (type $sig) (param $i32 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $foo (export "foo") (type $sig) (param $i32 i32) |
| ) |
| |
| ;; CHECK: (func $bar (type $sig) (param $i32 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $bar (export "bar") (type $sig) (param $i32 i32) |
| ) |
| |
| ;; CHECK: (func $call-bar (type $1) |
| ;; CHECK-NEXT: (call $bar |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $call-bar |
| (call $bar |
| (i32.const 42) |
| ) |
| ) |
| ) |
| |
| ;; Two functions with two different types have an unused parameter. After |
| ;; removing the parameter from each, they both have no parameters. They should |
| ;; *not* have the same type, however: the type should be different nominally |
| ;; even though after the pruning they are identical structurally. |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $sig2 (sub (func))) |
| |
| ;; CHECK: (type $sig1 (sub (func))) |
| (type $sig1 (sub (func (param i32)))) |
| (type $sig2 (sub (func (param f64)))) |
| ) |
| |
| ;; CHECK: (func $foo1 (type $sig1) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $foo1 (type $sig1) (param $i32 i32) |
| ) |
| |
| ;; CHECK: (func $foo2 (type $sig2) |
| ;; CHECK-NEXT: (local $0 f64) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $foo2 (type $sig2) (param $f64 f64) |
| ) |
| ) |
| |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $sig-bar (sub (func (param i32)))) |
| |
| ;; CHECK: (type $sig-foo (sub (func))) |
| (type $sig-foo (sub (func (param i32)))) |
| (type $sig-bar (sub (func (param i32)))) |
| ) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (func $foo (type $sig-foo) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (i32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig-foo) (param $i32 i32) |
| ;; This function is always called with the same constant, and we can |
| ;; apply that constant here and prune the param. |
| (i32.store |
| (i32.const 0) |
| (local.get $i32) |
| ) |
| (call $foo (i32.const 42)) |
| (call $foo (i32.const 42)) |
| (call $foo (i32.const 42)) |
| ) |
| |
| ;; CHECK: (func $bar (type $sig-bar) (param $i32 i32) |
| ;; CHECK-NEXT: (i32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $i32) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $bar |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $bar |
| ;; CHECK-NEXT: (i32.const 43) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bar (type $sig-bar) (param $i32 i32) |
| ;; This function is called with various values, and cannot be optimized like |
| ;; the previous one. |
| (i32.store |
| (i32.const 0) |
| (local.get $i32) |
| ) |
| (call $bar (i32.const 42)) |
| (call $bar (i32.const 43)) |
| ) |
| ) |
| |
| ;; As above, but $foo's parameter is a ref.func, which is also a constant |
| ;; value that we can optimize in the case of $foo (but not $bar, again, as $bar |
| ;; is not always sent a constant value). |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $sig-bar (sub (func (param funcref)))) |
| |
| ;; CHECK: (type $sig-foo (sub (func))) |
| (type $sig-foo (sub (func (param funcref)))) |
| (type $sig-bar (sub (func (param funcref)))) |
| ) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (elem declare func $bar $foo) |
| |
| ;; CHECK: (func $foo (type $sig-foo) |
| ;; CHECK-NEXT: (local $0 funcref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (ref.func $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig-foo) (param $funcref funcref) |
| (drop (local.get $funcref)) |
| (call $foo (ref.func $foo)) |
| (call $foo (ref.func $foo)) |
| (call $foo (ref.func $foo)) |
| ) |
| |
| ;; CHECK: (func $bar (type $sig-bar) (param $funcref funcref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $funcref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $bar |
| ;; CHECK-NEXT: (ref.func $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $bar |
| ;; CHECK-NEXT: (ref.func $bar) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bar (type $sig-bar) (param $funcref funcref) |
| (drop (local.get $funcref)) |
| (call $bar (ref.func $foo)) |
| (call $bar (ref.func $bar)) |
| ) |
| ) |
| |
| ;; As above, but the values are now ref.nulls. |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $sig-bar (sub (func (param anyref)))) |
| |
| ;; CHECK: (type $sig-foo (sub (func))) |
| (type $sig-foo (sub (func (param anyref)))) |
| (type $sig-bar (sub (func (param anyref)))) |
| ) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (func $foo (type $sig-foo) |
| ;; CHECK-NEXT: (local $0 anyref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: (call $foo) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $foo (type $sig-foo) (param $anyref anyref) |
| (drop (local.get $anyref)) |
| (call $foo (ref.null none)) |
| (call $foo (ref.null none)) |
| ) |
| |
| ;; CHECK: (func $bar (type $sig-bar) (param $anyref anyref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $anyref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $bar |
| ;; CHECK-NEXT: (ref.i31 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $bar |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bar (type $sig-bar) (param $anyref anyref) |
| (drop (local.get $anyref)) |
| ;; Mixing a null with something else prevents optimization, of course. |
| (call $bar (ref.i31 (i32.const 0))) |
| (call $bar (ref.null none)) |
| ) |
| ) |
| |
| (module |
| (type $A (struct)) |
| ;; CHECK: (type $0 (func)) |
| |
| ;; CHECK: (func $0 (type $0) |
| ;; CHECK-NEXT: (local $0 f32) |
| ;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $0 (param $0 f32) |
| ;; $A is only used in an unreachable cast. We should not error when |
| ;; removing the param from this function, during which we collect heap |
| ;; types, and must find this one even though the cast is unreachable, as |
| ;; we do need to handle it in the optimization as well as print it (note how |
| ;; type $A is declared in the output here - it would be a bug if it were |
| ;; not, which this is a regression test for). |
| (ref.cast (ref null $A) |
| (unreachable) |
| ) |
| ) |
| ) |
| |
| ;; Do not prune signatures used in the call.without.effects intrinsic. |
| (module |
| ;; CHECK: (type $0 (func (param i32 funcref) (result i32))) |
| |
| ;; CHECK: (type $1 (func (param i32) (result i32))) |
| |
| ;; CHECK: (type $2 (func (result i32))) |
| |
| ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $cwe (type $0) (param i32 funcref) (result i32))) |
| (import "binaryen-intrinsics" "call.without.effects" (func $cwe (param i32 funcref) (result i32))) |
| |
| ;; CHECK: (elem declare func $func) |
| |
| ;; CHECK: (func $func (type $1) (param $0 i32) (result i32) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| (func $func (param i32) (result i32) |
| ;; The parameter is unused, so we want to prune it. We won't because of the |
| ;; situation in the calling function, below. |
| (i32.const 1) |
| ) |
| |
| ;; CHECK: (func $caller (type $2) (result i32) |
| ;; CHECK-NEXT: (call $cwe |
| ;; CHECK-NEXT: (i32.const 41) |
| ;; CHECK-NEXT: (ref.func $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (result i32) |
| ;; We call $func using call.without.effects. This causes us to not optimize |
| ;; in this case. |
| (call $cwe |
| (i32.const 41) |
| (ref.func $func) |
| ) |
| ) |
| ) |
| |
| ;; Due to function subtyping, we cannot prune fields from $func.B without also |
| ;; pruning them in $func.A, and vice versa, if they have a subtyping |
| ;; relationship. Atm we do not prune such "cycles" so we do not optimize here. |
| ;; TODO |
| (module |
| ;; CHECK: (type $struct.A (sub (struct (field i32)))) |
| (type $struct.A (sub (struct (field i32)))) |
| ;; CHECK: (type $array.A (sub (array (ref $struct.A)))) |
| |
| ;; CHECK: (type $struct.B (sub $struct.A (struct (field i32) (field i64)))) |
| (type $struct.B (sub $struct.A (struct (field i32) (field i64)))) |
| |
| (type $array.A (sub (array (ref $struct.A)))) |
| ;; CHECK: (type $array.B (sub $array.A (array (ref $struct.B)))) |
| (type $array.B (sub $array.A (array (ref $struct.B)))) |
| |
| ;; CHECK: (type $func.A (sub (func (param (ref $array.B)) (result (ref $array.A))))) |
| (type $func.A (sub (func (param (ref $array.B)) (result (ref $array.A))))) |
| ;; CHECK: (type $func.B (sub $func.A (func (param (ref $array.A)) (result (ref $array.B))))) |
| (type $func.B (sub $func.A (func (param (ref $array.A)) (result (ref $array.B))))) |
| |
| ;; CHECK: (func $func.A (type $func.A) (param $0 (ref $array.B)) (result (ref $array.A)) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $func.A (type $func.A) (param $0 (ref $array.B)) (result (ref $array.A)) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $func.B (type $func.B) (param $0 (ref $array.A)) (result (ref $array.B)) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $func.B (type $func.B) (param $0 (ref $array.A)) (result (ref $array.B)) |
| (unreachable) |
| ) |
| ) |
| |
| ;; Test corner cases with var updating. To remove the parameter of $func we |
| ;; must move the parameter to a local first. We must then adjust local types |
| ;; properly while adjusting the signature (when the signature loses a parameter, |
| ;; local indexes change, which is a delicate dance handled by |
| ;; GlobalTypeRewriter::updateSignatures and ParamUtils::removeParameters; |
| ;; moving the parameter to a local first should not get in the way there). |
| (module |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $struct (sub (struct (field v128)))) |
| (type $struct (sub (struct (field v128)))) |
| ;; CHECK: (type $1 (func)) |
| |
| ;; CHECK: (type $func (func)) |
| (type $func (func (param v128))) |
| |
| ;; CHECK: (elem declare func $func) |
| |
| ;; CHECK: (func $func (type $func) |
| ;; CHECK-NEXT: (local $0 v128) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $func (type $func) (param $0 v128) |
| ;; The parameter will be removed. |
| (nop) |
| ) |
| |
| ;; CHECK: (func $caller (type $1) |
| ;; CHECK-NEXT: (local $0 (ref $struct)) |
| ;; CHECK-NEXT: (local $1 externref) |
| ;; CHECK-NEXT: (local $2 v128) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.tee $0 |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call_ref $func |
| ;; CHECK-NEXT: (ref.func $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $caller (param $param externref) |
| (local $var (ref $struct)) |
| ;; The parameter of this call_ref will be removed. |
| (call_ref $func |
| ;; Use a struct.get, which would error if the type the nested tee were |
| ;; incorrect (it asserts on it being a struct type). |
| (struct.get $struct 0 |
| ;; Use a tee to test the updating of tee'd vars, as mentioned above. |
| (local.tee $var |
| (struct.new_default $struct) |
| ) |
| ) |
| (ref.func $func) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $0 (func (param i32))) |
| |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $1 (func (param i32))) |
| |
| ;; CHECK: (type $2 (func (result i32))) |
| |
| ;; CHECK: (type $3 (func (param i32))) |
| |
| ;; CHECK: (tag $tag (param i32)) |
| (tag $tag (param i32)) |
| |
| ;; CHECK: (func $catch-pop (type $2) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (block $block (result i32) |
| ;; CHECK-NEXT: (try $try |
| ;; CHECK-NEXT: (do |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (catch $tag |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (pop i32) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (br_if $block |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $target |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $catch-pop (result i32) |
| (block $block (result i32) |
| (try $try |
| (do |
| (nop) |
| ) |
| (catch $tag |
| (call $target |
| (pop i32) |
| ;; We can remove this parameter by moving it to a local first, which |
| ;; also moves the pop, which then needs to be fixed up. |
| (br_if $block |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| ) |
| ;; This nop causes the call to be in a block. When we add another |
| ;; block to hold the code that we move, we'd get an error if we don't |
| ;; apply fixups. |
| (nop) |
| ) |
| ) |
| (i32.const 3) |
| ) |
| ) |
| |
| ;; CHECK: (func $target (type $1) (param $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $target (param $x i32) (param $y i32) |
| ;; Use only the first param. The second will be removed. |
| (drop |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; As above, but remove the other parameter (the pop). |
| (module |
| ;; CHECK: (type $0 (func (param i32))) |
| |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $1 (func (param i32))) |
| |
| ;; CHECK: (type $2 (func (result i32))) |
| |
| ;; CHECK: (type $3 (func (param i32))) |
| |
| ;; CHECK: (tag $tag (param i32)) |
| (tag $tag (param i32)) |
| |
| ;; CHECK: (func $catch-pop (type $2) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (block $block (result i32) |
| ;; CHECK-NEXT: (try $try |
| ;; CHECK-NEXT: (do |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (catch $tag |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (pop i32) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (br_if $block |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $target |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $catch-pop (result i32) |
| (block $block (result i32) |
| (try $try |
| (do |
| (nop) |
| ) |
| (catch $tag |
| (call $target |
| (pop i32) |
| (br_if $block |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| ) |
| (nop) |
| ) |
| ) |
| (i32.const 3) |
| ) |
| ) |
| |
| ;; CHECK: (func $target (type $1) (param $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $target (param $x i32) (param $y i32) |
| (drop |
| (local.get $y) ;; this changed from $x to $y |
| ) |
| ) |
| ) |
| |
| ;; $exported is exported. The entire rec group becomes exported as well, which |
| ;; causes $unused-param's type to be public, which means we cannot normally |
| ;; modify it. However, in closed world we allow such changes, and we can remove |
| ;; the unused param there. What happens is that we keep the original public rec |
| ;; group as-is, and add a new rec group for private types, put the pruned type |
| ;; there, and use that pruned type on $unused-param. |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $0 (func)) |
| |
| ;; CHECK: (type $much (func)) |
| |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $none (func)) |
| (type $none (func)) |
| (type $much (func (param i32))) |
| ) |
| |
| ;; CHECK: (type $much_0 (func (param i32))) |
| |
| ;; CHECK: (export "exported" (func $exported)) |
| |
| ;; CHECK: (func $exported (type $none) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $exported (export "exported") (type $none) |
| ) |
| |
| ;; CHECK: (func $unused-param (type $much) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $unused-param (type $much) (param $param i32) |
| ) |
| |
| ;; CHECK: (func $caller (type $0) |
| ;; CHECK-NEXT: (call $unused-param) |
| ;; CHECK-NEXT: ) |
| (func $caller |
| (call $unused-param |
| (i32.const 0) |
| ) |
| ) |
| ) |
| |