blob: 57dd783622c34b23731a1263272232d88283b6fc [file] [log] [blame] [edit]
;; 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)
)
)
)