;; 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 $0 (func (result i32))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $2 (func (param i32 i32) (result i32))) ;; CHECK: (type $3 (func (param i32) (result i32))) ;; CHECK: (import "a" "b" (func $import (type $0) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (export "param-no" (func $param-no)) ;; CHECK: (func $never-called (type $3) (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 $0) (result i32) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) (func $foo (result i32) (i32.const 1) ) ;; CHECK: (func $bar (type $1) ;; 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 $1) ;; 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 $0) (result i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; 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) (then (return (i32.const 1) ) ) ) (i32.const 2) ) ;; CHECK: (func $call-return (type $1) ;; 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 $0) (result i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (return ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; 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) (then (return (i32.const 1) ) ) ) (i32.const 1) ) ;; CHECK: (func $call-return-same (type $1) ;; 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 $0) (result i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) (func $local-no (result i32) (local $x i32) (if (call $import) (then (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 $0) (result i32) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) (func $local-yes (result i32) (local $x i32) (if (call $import) (then (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 $3) (param $param i32) (result i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $param) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (local.set $param ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; 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) (then (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 $3) (param $param i32) (result i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (local.set $param ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; 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) (then (local.set $param (i32.const 1) ) ) ) (local.get $param) ) ;; CHECK: (func $cycle (type $2) (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 $2) (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 $2) (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 $2) (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 $2) (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 $1) ;; 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: (then ;; CHECK-NEXT: (br $named ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; 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: (then ;; CHECK-NEXT: (br $named0 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; 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) (then (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) (then (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) ) (export "table" (table 0)) ;; CHECK: (type $1 (func)) ;; CHECK: (table $0 10 funcref) ;; CHECK: (elem $0 (i32.const 0) $reffed) ;; CHECK: (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 $1) ;; 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 $1 (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 $1) ;; 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 $1 (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 $1) ;; 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 $1 (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 $1) ;; 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 $1 (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 $1) ;; 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 $0 (func (result i32))) ;; CHECK: (type $1 (func)) ;; CHECK: (func $const (type $0) (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 $0) (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 $1) ;; 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 $0 (func (result i32))) ;; CHECK: (type $1 (func)) ;; CHECK: (import "a" "b" (func $import (type $0) (result i32))) (import "a" "b" (func $import (result i32))) ;; CHECK: (func $internal (type $0) (result i32) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) (func $internal (result i32) (i32.const 42) ) ;; CHECK: (func $calls (type $1) ;; 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))) (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param i32 funcref))) (import "other" "import" (func $other-import (param funcref))) ;; CHECK: (type $1 (func (param i32 funcref))) ;; CHECK: (type $2 (func (param funcref))) ;; CHECK: (type $3 (func)) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (type $1) (param i32 funcref))) ;; CHECK: (import "other" "import" (func $other-import (type $2) (param funcref))) ;; CHECK: (elem declare func $target-drop $target-keep) ;; CHECK: (export "foo" (func $foo)) ;; CHECK: (func $foo (type $3) ;; 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))) (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param i32 funcref))) (import "other" "import" (func $other-import (param funcref))) ;; CHECK: (type $1 (func (param i32 funcref))) ;; CHECK: (type $2 (func (param funcref))) ;; CHECK: (type $3 (func (param (ref null $A)))) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (type $1) (param i32 funcref))) ;; CHECK: (import "other" "import" (func $other-import (type $2) (param funcref))) ;; CHECK: (elem declare func $target-keep $target-keep-2) ;; CHECK: (export "foo" (func $foo)) ;; CHECK: (func $foo (type $3) (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 $0 (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 $0) ;; 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) ) ) )