blob: deb9d0fef6819177c8ddc79da320e0cfe709b1d7 [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 -all --gufa -S -o - | filecheck %s
(module
;; CHECK: (type $none_=>_i32 (func (result i32)))
;; CHECK: (type $none_=>_none (func))
;; CHECK: (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
;; CHECK: (type $i32_=>_i32 (func (param i32) (result i32)))
;; CHECK: (import "a" "b" (func $import (type $none_=>_i32) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (export "param-no" (func $param-no))
;; CHECK: (func $never-called (type $i32_=>_i32) (param $param i32) (result i32)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $never-called (param $param i32) (result i32)
;; This function is never called, so no content is possible in $param, and
;; we know this must be unreachable code that can be removed (replaced with
;; an unreachable).
(local.get $param)
)
;; CHECK: (func $foo (type $none_=>_i32) (result i32)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
(func $foo (result i32)
(i32.const 1)
)
;; CHECK: (func $bar (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $foo)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $bar
;; Both arms of the select have identical values, 1. Inlining +
;; OptimizeInstructions could of course discover that in this case, but
;; GUFA can do so even without inlining. As a result the select will be
;; dropped (due to the call which may have effects, we keep it), and at the
;; end we emit the constant 1 for the value.
(drop
(select
(call $foo)
(i32.const 1)
(call $import)
)
)
)
;; CHECK: (func $baz (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (select
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $foo)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.eqz
;; CHECK-NEXT: (i32.eqz
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $baz
(drop
(select
(call $foo)
;; As above, but replace 1 with eqz(eqz(1)).This pass assumes any eqz
;; etc is a new value, and so here we do not optimize the select (we do
;; still optimize the call's result, though).
(i32.eqz
(i32.eqz
(i32.const 1)
)
)
(call $import)
)
)
)
;; CHECK: (func $return (type $none_=>_i32) (result i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (return
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
(func $return (result i32)
;; Helper function that returns one result in a return and flows another
;; out. There is nothing to optimize in this function, but see the caller
;; below.
(if
(i32.const 0)
(return
(i32.const 1)
)
)
(i32.const 2)
)
;; CHECK: (func $call-return (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $return)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $call-return
;; The called function has two possible return values, so we cannot optimize
;; anything here.
(drop
(call $return)
)
)
;; CHECK: (func $return-same (type $none_=>_i32) (result i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (return
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
(func $return-same (result i32)
;; Helper function that returns the same result in a return as it flows out.
;; This is the same as above, but now the values are identical, and the
;; function must return 1. There is nothing to optimize in this function,
;; but see the caller below.
(if
(i32.const 0)
(return
(i32.const 1)
)
)
(i32.const 1)
)
;; CHECK: (func $call-return-same (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $return-same)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $call-return-same
;; Unlike in $call-return, now we can optimize here.
(drop
(call $return-same)
)
)
;; CHECK: (func $local-no (type $none_=>_i32) (result i32)
;; CHECK-NEXT: (local $x i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
(func $local-no (result i32)
(local $x i32)
(if
(call $import)
(local.set $x
(i32.const 1)
)
)
;; $x has two possible values, 1 and the default 0, so we cannot optimize
;; anything here.
(local.get $x)
)
;; CHECK: (func $local-yes (type $none_=>_i32) (result i32)
;; CHECK-NEXT: (local $x i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: (local.set $x
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
(func $local-yes (result i32)
(local $x i32)
(if
(call $import)
(local.set $x
;; As above, but now we set 0 here. We can optimize the local.get to 0
;; in this case.
(i32.const 0)
)
)
(local.get $x)
)
;; CHECK: (func $param-no (type $i32_=>_i32) (param $param i32) (result i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (local.get $param)
;; CHECK-NEXT: (local.set $param
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $param)
;; CHECK-NEXT: )
(func $param-no (export "param-no") (param $param i32) (result i32)
(if
(local.get $param)
(local.set $param
(i32.const 1)
)
)
;; $x has two possible values, the incoming param value and 1, so we cannot
;; optimize, since the function is exported - anything on the outside could
;; call it with values we are not aware of during the optimization.
(local.get $param)
)
;; CHECK: (func $param-yes (type $i32_=>_i32) (param $param i32) (result i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: (local.set $param
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
(func $param-yes (param $param i32) (result i32)
;; As above, but now the function is not exported. That means it has no
;; callers, so the first local.get can contain nothing, and will become an
;; unreachable. The other local.get later down can only contain the
;; local.set in the if, so we'll optimize it to 1.
(if
(local.get $param)
(local.set $param
(i32.const 1)
)
)
(local.get $param)
)
;; CHECK: (func $cycle (type $i32_i32_=>_i32) (param $x i32) (param $y i32) (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $cycle
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $cycle
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
(func $cycle (param $x i32) (param $y i32) (result i32)
;; Return 42, or else the result from a recursive call. The only possible
;; value is 42, which we can optimize to.
;; (Nothing else calls $cycle, so this is dead code in actuality, but this
;; pass leaves such things to other passes.)
;; Note that the first call passes constants for $x and $y which lets us
;; optimize them too (as we see no other contents arrive to them).
(drop
(call $cycle
(i32.const 42)
(i32.const 1)
)
)
(select
(i32.const 42)
(call $cycle
(local.get $x)
(local.get $y)
)
(local.get $y)
)
)
;; CHECK: (func $cycle-2 (type $i32_i32_=>_i32) (param $x i32) (param $y i32) (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $cycle-2
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $cycle-2
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
(func $cycle-2 (param $x i32) (param $y i32) (result i32)
(drop
(call $cycle-2
(i32.const 42)
(i32.const 1)
)
)
;; As above, but flip one $x and $y on the first and last local.gets. We
;; can see that $y must contain 1, and we cannot infer a value for $x (it
;; is sent both 42 and $y which is 1). Even without $x, however, we can see
;; the value leaving the select is 42, which means the call returns 42.
(select
(i32.const 42)
(call $cycle-2
(local.get $y)
(local.get $y)
)
(local.get $x)
)
)
;; CHECK: (func $cycle-3 (type $i32_i32_=>_i32) (param $x i32) (param $y i32) (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $cycle-3
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $cycle-3
;; CHECK-NEXT: (i32.eqz
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
(func $cycle-3 (param $x i32) (param $y i32) (result i32)
;; Even adding a caller with a different value for $x does not prevent us
;; from optimizing here.
(drop
(call $cycle-3
(i32.const 1337)
(i32.const 1)
)
)
;; As $cycle, but add an i32.eqz on $x. We can still optimize this, as
;; while the eqz is a new value arriving in $x, we do not actually return
;; $x, and again the only possible value flowing in the graph is 42.
(select
(i32.const 42)
(call $cycle-3
(i32.eqz
(local.get $x)
)
(local.get $y)
)
(local.get $y)
)
)
;; CHECK: (func $cycle-4 (type $i32_i32_=>_i32) (param $x i32) (param $y i32) (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $cycle-4
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (select
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (call $cycle-4
;; CHECK-NEXT: (i32.eqz
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $cycle-4 (param $x i32) (param $y i32) (result i32)
(drop
(call $cycle-4
(i32.const 1337)
(i32.const 1)
)
)
(select
;; As above, but we have no constant here, but $x. We may now return $x or
;; $eqz of $x, which means we cannot infer the result of the call. (But we
;; can still infer the value of $y, which is 1.)
(local.get $x)
(call $cycle-4
(i32.eqz
(local.get $x)
)
(local.get $y)
)
(local.get $y)
)
)
;; CHECK: (func $cycle-5 (type $i32_i32_=>_i32) (param $x i32) (param $y i32) (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $cycle-5
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $cycle-5
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
(func $cycle-5 (param $x i32) (param $y i32) (result i32)
(drop
(call $cycle-5
(i32.const 1337)
(i32.const 1)
)
)
(select
(local.get $x)
(call $cycle-5
;; As above, but now we return $x in both cases, so we can optimize, and
;; infer the result is the 1337 which is passed in the earlier call.
(local.get $x)
(local.get $y)
)
(local.get $y)
)
)
;; CHECK: (func $blocks (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block $named (result i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (br $named
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block $named0 (result i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (br $named0
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $blocks
;; A block with a branch to it, which we can infer a constant for.
(drop
(block $named (result i32)
(if
(i32.const 0)
(br $named
(i32.const 1)
)
)
(i32.const 1)
)
)
;; As above, but the two values reaching the block do not agree, so we
;; should not optimize.
(drop
(block $named (result i32)
(if
(i32.const 0)
(br $named
(i32.const 2) ;; this changed
)
)
(i32.const 1)
)
)
)
)
(module
;; CHECK: (type $i (func (param i32)))
(type $i (func (param i32)))
(table 10 funcref)
(elem (i32.const 0) funcref
(ref.func $reffed)
)
;; CHECK: (type $none_=>_none (func))
;; CHECK: (table $0 10 funcref)
;; CHECK: (elem $0 (i32.const 0) $reffed)
;; CHECK: (export "table" (table $0))
(export "table" (table 0))
;; CHECK: (func $reffed (type $i) (param $x i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $reffed (param $x i32)
;; This function is in the table, and the table is exported, so we cannot
;; see all callers, and cannot infer the value here.
(drop
(local.get $x)
)
)
;; CHECK: (func $do-calls (type $none_=>_none)
;; CHECK-NEXT: (call $reffed
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_indirect $0 (type $i)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $do-calls
(call $reffed
(i32.const 42)
)
(call_indirect (type $i)
(i32.const 42)
(i32.const 0)
)
)
)
;; As above, but the table is not exported. We have a direct and an indirect
;; call with the same value, so we can optimize inside $reffed.
(module
;; CHECK: (type $i (func (param i32)))
(type $i (func (param i32)))
(table 10 funcref)
(elem (i32.const 0) funcref
(ref.func $reffed)
)
;; CHECK: (type $none_=>_none (func))
;; CHECK: (table $0 10 funcref)
;; CHECK: (elem $0 (i32.const 0) $reffed)
;; CHECK: (func $reffed (type $i) (param $x i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $reffed (param $x i32)
(drop
(local.get $x)
)
)
;; CHECK: (func $do-calls (type $none_=>_none)
;; CHECK-NEXT: (call $reffed
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_indirect $0 (type $i)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $do-calls
(call $reffed
(i32.const 42)
)
(call_indirect (type $i)
(i32.const 42)
(i32.const 0)
)
)
)
;; As above but the only calls are indirect.
(module
;; CHECK: (type $i (func (param i32)))
(type $i (func (param i32)))
(table 10 funcref)
(elem (i32.const 0) funcref
(ref.func $reffed)
)
;; CHECK: (type $none_=>_none (func))
;; CHECK: (table $0 10 funcref)
;; CHECK: (elem $0 (i32.const 0) $reffed)
;; CHECK: (func $reffed (type $i) (param $x i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $reffed (param $x i32)
(drop
(local.get $x)
)
)
;; CHECK: (func $do-calls (type $none_=>_none)
;; CHECK-NEXT: (call_indirect $0 (type $i)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_indirect $0 (type $i)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $do-calls
(call_indirect (type $i)
(i32.const 42)
(i32.const 0)
)
(call_indirect (type $i)
(i32.const 42)
(i32.const 0)
)
)
)
;; As above but the indirect calls have different parameters, so we do not
;; optimize.
(module
;; CHECK: (type $i (func (param i32)))
(type $i (func (param i32)))
(table 10 funcref)
(elem (i32.const 0) funcref
(ref.func $reffed)
)
;; CHECK: (type $none_=>_none (func))
;; CHECK: (table $0 10 funcref)
;; CHECK: (elem $0 (i32.const 0) $reffed)
;; CHECK: (func $reffed (type $i) (param $x i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $reffed (param $x i32)
(drop
(local.get $x)
)
)
;; CHECK: (func $do-calls (type $none_=>_none)
;; CHECK-NEXT: (call_indirect $0 (type $i)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_indirect $0 (type $i)
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $do-calls
(call_indirect (type $i)
(i32.const 42)
(i32.const 0)
)
(call_indirect (type $i)
(i32.const 1337)
(i32.const 0)
)
)
)
;; As above but the second call is of another function type, so it does not
;; prevent us from optimizing even though it has a different value.
(module
;; CHECK: (type $i (func (param i32)))
(type $i (func (param i32)))
;; CHECK: (type $none_=>_none (func))
;; CHECK: (type $f (func (param f32)))
(type $f (func (param f32)))
(table 10 funcref)
(elem (i32.const 0) funcref
(ref.func $reffed)
)
;; CHECK: (table $0 10 funcref)
;; CHECK: (elem $0 (i32.const 0) $reffed)
;; CHECK: (func $reffed (type $i) (param $x i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $reffed (param $x i32)
(drop
(local.get $x)
)
)
;; CHECK: (func $do-calls (type $none_=>_none)
;; CHECK-NEXT: (call_indirect $0 (type $i)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_indirect $0 (type $f)
;; CHECK-NEXT: (f32.const 1337)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $do-calls
(call_indirect (type $i)
(i32.const 42)
(i32.const 0)
)
(call_indirect (type $f)
(f32.const 1337)
(i32.const 0)
)
)
)
(module
;; CHECK: (type $none_=>_i32 (func (result i32)))
;; CHECK: (type $none_=>_none (func))
;; CHECK: (func $const (type $none_=>_i32) (result i32)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
(func $const (result i32)
;; Return a const to the caller below.
(i32.const 42)
)
;; CHECK: (func $retcall (type $none_=>_i32) (result i32)
;; CHECK-NEXT: (return_call $const)
;; CHECK-NEXT: )
(func $retcall (result i32)
;; Do a return call. This tests that we pass its value out as a result.
(return_call $const)
)
;; CHECK: (func $caller (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $retcall)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller
;; Call the return caller. We can optimize this value to 42.
(drop
(call $retcall)
)
)
)
;; Imports have unknown values.
(module
;; CHECK: (type $none_=>_i32 (func (result i32)))
;; CHECK: (type $none_=>_none (func))
;; CHECK: (import "a" "b" (func $import (type $none_=>_i32) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $internal (type $none_=>_i32) (result i32)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
(func $internal (result i32)
(i32.const 42)
)
;; CHECK: (func $calls (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $internal)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $calls
(drop
(call $import)
)
;; For comparison, we can optimize this call to an internal function.
(drop
(call $internal)
)
)
)
;; Test for nontrivial code in a global init. We need to process such code
;; normally and not hit any internal asserts (nothing can be optimized here).
(module
;; CHECK: (global $global$0 i32 (i32.add
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: ))
(global $global$0 i32
(i32.add
(i32.const 10)
(i32.const 20)
)
)
)
;; The call.without.effects intrinsic does a call to the reference given to it,
;; but for now other imports do not (until we add a flag for closed-world).
(module
;; CHECK: (type $A (func (param i32)))
(type $A (func (param i32)))
;; CHECK: (type $i32_funcref_=>_none (func (param i32 funcref)))
;; CHECK: (type $funcref_=>_none (func (param funcref)))
;; CHECK: (type $none_=>_none (func))
;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (type $i32_funcref_=>_none) (param i32 funcref)))
(import "binaryen-intrinsics" "call.without.effects"
(func $call-without-effects (param i32 funcref)))
;; CHECK: (import "other" "import" (func $other-import (type $funcref_=>_none) (param funcref)))
(import "other" "import"
(func $other-import (param funcref)))
;; CHECK: (elem declare func $target-drop $target-keep)
;; CHECK: (export "foo" (func $foo))
;; CHECK: (func $foo (type $none_=>_none)
;; CHECK-NEXT: (call $call-without-effects
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (ref.func $target-keep)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $other-import
;; CHECK-NEXT: (ref.func $target-drop)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $foo (export "foo")
;; Calling the intrinsic with a reference is considered a call of the
;; reference, so $target-keep's code is reachable. We should leave it alone,
;; but we can put an unreachable in $target-drop.
(call $call-without-effects
(i32.const 1)
(ref.func $target-keep)
)
;; The other import is not enough to keep $target-drop alive.
(call $other-import
(ref.func $target-drop)
)
)
;; CHECK: (func $target-keep (type $A) (param $x i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $target-keep (type $A) (param $x i32)
(drop
(local.get $x)
)
)
;; CHECK: (func $target-drop (type $A) (param $x i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $target-drop (type $A) (param $x i32)
(drop
(local.get $x)
)
)
)
;; As above, but now the call to the intrinsic does not let us see the exact
;; function being called.
(module
;; CHECK: (type $A (func (param i32)))
(type $A (func (param i32)))
;; CHECK: (type $i32_funcref_=>_none (func (param i32 funcref)))
;; CHECK: (type $funcref_=>_none (func (param funcref)))
;; CHECK: (type $ref?|$A|_=>_none (func (param (ref null $A))))
;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (type $i32_funcref_=>_none) (param i32 funcref)))
(import "binaryen-intrinsics" "call.without.effects"
(func $call-without-effects (param i32 funcref)))
;; CHECK: (import "other" "import" (func $other-import (type $funcref_=>_none) (param funcref)))
(import "other" "import"
(func $other-import (param funcref)))
;; CHECK: (elem declare func $target-keep $target-keep-2)
;; CHECK: (export "foo" (func $foo))
;; CHECK: (func $foo (type $ref?|$A|_=>_none) (param $A (ref null $A))
;; CHECK-NEXT: (call $call-without-effects
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $target-keep)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $other-import
;; CHECK-NEXT: (ref.func $target-keep-2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $foo (export "foo") (param $A (ref null $A))
;; Call the intrinsic without a RefFunc. All we infer here is the type,
;; which means we must assume anything with type $A (and a reference) can be
;; called, which will keep alive the bodies of both $target-keep and
;; $target-keep-2 - no unreachables will be placed in either one.
(call $call-without-effects
(i32.const 1)
(local.get $A)
)
(drop
(ref.func $target-keep)
)
(call $other-import
(ref.func $target-keep-2)
)
)
;; CHECK: (func $target-keep (type $A) (param $x i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $target-keep (type $A) (param $x i32)
(drop
(local.get $x)
)
)
;; CHECK: (func $target-keep-2 (type $A) (param $x i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $target-keep-2 (type $A) (param $x i32)
(drop
(local.get $x)
)
)
)
;; Exported mutable globals can contain any value, as the outside can write to
;; them.
(module
;; CHECK: (type $none_=>_none (func))
;; CHECK: (global $exported-mutable (mut i32) (i32.const 42))
(global $exported-mutable (mut i32) (i32.const 42))
;; CHECK: (global $exported-immutable i32 (i32.const 42))
(global $exported-immutable i32 (i32.const 42))
;; CHECK: (global $mutable (mut i32) (i32.const 42))
(global $mutable (mut i32) (i32.const 42))
;; CHECK: (global $immutable i32 (i32.const 42))
(global $immutable i32 (i32.const 42))
;; CHECK: (export "exported-mutable" (global $exported-mutable))
(export "exported-mutable" (global $exported-mutable))
;; CHECK: (export "exported-immutable" (global $exported-immutable))
(export "exported-immutable" (global $exported-immutable))
;; CHECK: (func $test (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (global.get $exported-mutable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; This should not be optimized to a constant.
(drop
(global.get $exported-mutable)
)
;; All the rest can be optimized.
(drop
(global.get $exported-immutable)
)
(drop
(global.get $mutable)
)
(drop
(global.get $immutable)
)
)
)