| ;; 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 $none_=>_none (func)) |
| |
| ;; CHECK: (type $sig (func (param i32 f64))) |
| (type $sig (func_subtype (param i32) (param i64) (param f32) (param f64) func)) |
| |
| (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 $none_=>_none) |
| ;; 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 $none_=>_none (func)) |
| |
| ;; CHECK: (type $sig (func (param i64 f32))) |
| (type $sig (func_subtype (param i32) (param i64) (param f32) (param f64) func)) |
| |
| (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 $none_=>_none) |
| ;; 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 $none_=>_none (func)) |
| |
| ;; CHECK: (type $sig (func (param i32 i64 f32))) |
| (type $sig (func_subtype (param i32) (param i64) (param f32) (param f64) func)) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (elem declare func $foo) |
| |
| ;; CHECK: (func $foo (type $sig) (param $0 i32) (param $1 i64) (param $2 f32) |
| ;; CHECK-NEXT: (local $3 f64) |
| ;; CHECK-NEXT: (i64.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (f32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; 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 $none_=>_none) |
| ;; CHECK-NEXT: (call $foo |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $caller) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: (f32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call_ref $sig |
| ;; CHECK-NEXT: (i32.const 4) |
| ;; 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 which |
| ;; prevents us from removing it (flattening the IR first would avoid this |
| ;; limitation). We only end up removing a single unused param, the last. |
| (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 only optimize |
| ;; away the very last param. |
| (module |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $none_=>_none (func)) |
| |
| ;; CHECK: (type $sig (func (param i32 i64 f32))) |
| (type $sig (func_subtype (param i32) (param i64) (param f32) (param f64) func)) |
| |
| (memory 1 1) |
| |
| ;; CHECK: (memory $0 1 1) |
| |
| ;; CHECK: (elem declare func $foo) |
| |
| ;; CHECK: (func $foo (type $sig) (param $0 i32) (param $1 i64) (param $2 f32) |
| ;; CHECK-NEXT: (local $3 f64) |
| ;; CHECK-NEXT: (i64.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (f32.store |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; 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 $none_=>_none) |
| ;; CHECK-NEXT: (call $foo |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: (f32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call_ref $sig |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $caller) |
| ;; CHECK-NEXT: (i32.const 4) |
| ;; CHECK-NEXT: ) |
| ;; 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 |
| (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 $none_=>_none (func)) |
| |
| ;; CHECK: (type $sig (func)) |
| (type $sig (func_subtype (param i32) (param i64) (param f32) (param f64) func)) |
| |
| (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 $none_=>_none) |
| ;; 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 $none_=>_none (func)) |
| |
| ;; CHECK: (type $sig (func)) |
| (type $sig (func_subtype (param i32) func)) |
| |
| (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 $none_=>_none) |
| ;; 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 (func)) |
| (type $sig (func_subtype (param i32) func)) |
| |
| (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 (func (param i32))) |
| (type $sig (func_subtype (param i32) func)) |
| |
| ;; 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 (func (param i32))) |
| (type $sig (func_subtype (param i32) func)) |
| |
| (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 (func (param i32))) |
| |
| ;; CHECK: (type $sig (func)) |
| (type $sig (func_subtype (param i32) func)) |
| |
| (type $sig2 (func_subtype (param i32) func)) |
| ) |
| |
| (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 $none_=>_none (func)) |
| |
| ;; CHECK: (type $sig (func)) |
| (type $sig (func_subtype (param i32) func)) |
| |
| (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 $none_=>_none) |
| ;; 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 $none_=>_none) |
| ;; 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 (func (param i32))) |
| (type $sig (func_subtype (param i32) func)) |
| |
| ;; 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 (func (param i32))) |
| (type $sig (func_subtype (param i32) func)) |
| |
| ;; CHECK: (type $none_=>_none (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 $none_=>_none) |
| ;; 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 (func)) |
| |
| ;; CHECK: (type $sig1 (func)) |
| (type $sig1 (func_subtype (param i32) func)) |
| (type $sig2 (func_subtype (param f64) func)) |
| ) |
| |
| ;; 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 (func (param i32))) |
| |
| ;; CHECK: (type $sig-foo (func)) |
| (type $sig-foo (func_subtype (param i32) func)) |
| (type $sig-bar (func_subtype (param i32) func)) |
| ) |
| |
| (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 (func (param funcref))) |
| |
| ;; CHECK: (type $sig-foo (func)) |
| (type $sig-foo (func_subtype (param funcref) func)) |
| (type $sig-bar (func_subtype (param funcref) func)) |
| ) |
| |
| (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 (func (param anyref))) |
| |
| ;; CHECK: (type $sig-foo (func)) |
| (type $sig-foo (func_subtype (param anyref) func)) |
| (type $sig-bar (func_subtype (param anyref) func)) |
| ) |
| |
| (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: (i31.new |
| ;; 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 (i31.new (i32.const 0))) |
| (call $bar (ref.null none)) |
| ) |
| ) |
| |
| (module |
| (type $A (struct)) |
| ;; CHECK: (type $none_=>_none (func)) |
| |
| ;; CHECK: (func $0 (type $none_=>_none) |
| ;; CHECK-NEXT: (local $0 f32) |
| ;; CHECK-NEXT: (block ;; (replaces something unreachable 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 null $A |
| (unreachable) |
| ) |
| ) |
| ) |
| |
| ;; Do not prune signatures used in the call.without.effects intrinsic. |
| (module |
| ;; CHECK: (type $i32_funcref_=>_i32 (func (param i32 funcref) (result i32))) |
| |
| ;; CHECK: (type $i32_=>_i32 (func (param i32) (result i32))) |
| |
| ;; CHECK: (type $none_=>_i32 (func (result i32))) |
| |
| ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $cwe (type $i32_funcref_=>_i32) (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 $i32_=>_i32) (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 $none_=>_i32) (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 (struct (field i32))) |
| (type $struct.A (struct (field i32))) |
| ;; CHECK: (type $array.A (array (ref $struct.A))) |
| |
| ;; CHECK: (type $struct.B (sub $struct.A (struct (field i32) (field i64)))) |
| (type $struct.B (struct_subtype (field i32) (field i64) $struct.A)) |
| |
| (type $array.A (array (ref $struct.A))) |
| ;; CHECK: (type $array.B (sub $array.A (array (ref $struct.B)))) |
| (type $array.B (array_subtype (ref $struct.B) $array.A)) |
| |
| ;; CHECK: (type $func.A (func (param (ref $array.B)) (result (ref $array.A)))) |
| (type $func.A (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 (func_subtype (param (ref $array.A)) (result (ref $array.B)) $func.A)) |
| |
| ;; 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) |
| ) |
| ) |