| ;; 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) |
| ) |
| ) |
| ) |