;; 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: ) (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: ) (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: ) (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: ) (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: ) (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: ) (func $foo (type $sig) (param $i32 i32) ) ;; CHECK: (func $bar (type $sig) ;; CHECK-NEXT: (local $0 i32) ;; 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: ) (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: ) (func $foo (export "foo") (type $sig) (param $i32 i32) ) ;; CHECK: (func $bar (type $sig) (param $i32 i32) ;; 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: ) (func $foo1 (type $sig1) (param $i32 i32) ) ;; CHECK: (func $foo2 (type $sig2) ;; CHECK-NEXT: (local $0 f64) ;; 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 could allow such changes, by keeping ;; 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. ;; That can work here, but not in the testcase after us. For now, we also do not ;; optimize here, as figuring out when it is safe is very difficult, and may ;; need a new design TODO (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $none (func)) (type $none (func)) ;; CHECK: (type $much (func (param i32))) (type $much (func (param i32))) ) ;; CHECK: (type $2 (func)) ;; CHECK: (export "exported" (func $exported)) ;; CHECK: (func $exported (type $none) ;; CHECK-NEXT: ) (func $exported (export "exported") (type $none) ) ;; CHECK: (func $unused-param (type $much) (param $param i32) ;; CHECK-NEXT: ) (func $unused-param (type $much) (param $param i32) ) ;; CHECK: (func $caller (type $2) ;; CHECK-NEXT: (call $unused-param ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $unused-param (i32.const 0) ) ) ) ;; As the previous testcase, but add another use of the type we want to prune, ;; in a struct.new. The struct type is public, so we cannot modify it and ;; replace the reference to the function type with the pruned version. (module (rec ;; CHECK: (type $0 (func)) ;; CHECK: (rec ;; CHECK-NEXT: (type $none (func)) (type $none (func)) ;; CHECK: (type $much (func (param i32))) (type $much (func (param i32))) ;; CHECK: (type $struct (struct (field (ref $much)))) (type $struct (struct (field (ref $much)))) ) ;; CHECK: (elem declare func $unused-param) ;; CHECK: (export "exported" (func $exported)) ;; CHECK: (func $exported (type $none) ;; CHECK-NEXT: ) (func $exported (export "exported") (type $none) ;; This makes the rec group public. ) ;; CHECK: (func $unused-param (type $much) (param $param i32) ;; CHECK-NEXT: ) (func $unused-param (type $much) (param $param i32) ) ;; CHECK: (func $caller (type $0) ;; CHECK-NEXT: (call $unused-param ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $caller (call $unused-param (i32.const 0) ) ) ;; CHECK: (func $struct.new (type $0) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (ref.func $unused-param) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $struct.new ;; This struct.new causes the problem mentioned above. (drop (struct.new $struct (ref.func $unused-param) ) ) ) )