blob: d86e855dbe81a7e9c930544cea1013bb1253611a [file] [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: )
(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, even though they have the same signature.
(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: (rec
;; CHECK-NEXT: (type $0 (func (param i32)))
;; CHECK: (type $1 (func (result i32)))
;; CHECK: (type $2 (func (param i32)))
;; CHECK: (tag $tag (type $2) (param i32))
(tag $tag (param i32))
;; CHECK: (func $catch-pop (type $1) (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 $0) (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: (rec
;; CHECK-NEXT: (type $0 (func (param i32)))
;; CHECK: (type $1 (func (result i32)))
;; CHECK: (type $2 (func (param i32)))
;; CHECK: (tag $tag (type $2) (param i32))
(tag $tag (param i32))
;; CHECK: (func $catch-pop (type $1) (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 $0) (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)
)
)
)
)
;; Test that we do not prune parameters from types used in tags.
(module
;; CHECK: (type $sig (func (param anyref)))
(type $sig (func (param anyref)))
;; CHECK: (type $1 (func))
;; CHECK: (tag $e (type $sig) (param anyref))
(tag $e (type $sig))
;; CHECK: (func $unused-param (type $sig) (param $0 anyref)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $unused-param (type $sig) (param anyref)
(nop)
)
;; CHECK: (func $throw (type $1)
;; CHECK-NEXT: (local $0 anyref)
;; CHECK-NEXT: (throw $e
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $throw
(local anyref)
;; This would be invalid if we optimized $sig.
(throw $e
(local.get 0)
)
)
)
(module
;; If a signature is used in a continuation, we cannot refine its parameters,
;; as we do not yet support updating continuation instructions with new types.
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $cont (cont $sig))
;; CHECK: (type $1 (func))
;; CHECK: (type $other (func))
;; CHECK: (type $sig (func (param anyref)))
(type $sig (func (param anyref)))
(type $other (func (param anyref)))
(type $cont (cont $sig))
)
;; CHECK: (elem declare func $cont $not-cont $other)
;; CHECK: (func $cont (type $sig) (param $0 anyref)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $cont (type $sig) (param anyref)
;; The param is unused here, and in all functions below, so we want to
;; remove it where possible.
(nop)
)
;; CHECK: (func $not-cont (type $sig) (param $0 anyref)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $not-cont (type $sig) (param anyref)
;; This function cannot be optimized even though it is not used in a
;; continuation. It is enough that it shares a type with a continuation
;; function.
(nop)
)
;; CHECK: (func $other (type $other)
;; CHECK-NEXT: (local $0 anyref)
;; CHECK-NEXT: (local.set $0
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $other (type $other) (param anyref)
;; This function uses a different type, so it can be optimized.
(nop)
)
;; CHECK: (func $test (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (cont.new $cont
;; CHECK-NEXT: (ref.func $cont)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $sig
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: (ref.func $not-cont)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $other
;; CHECK-NEXT: (ref.func $other)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(cont.new $cont
(ref.func $cont)
)
)
(call_ref $sig
(ref.null none)
(ref.func $not-cont)
)
(call_ref $other
(ref.null none)
(ref.func $other)
)
)
)