| ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. |
| ;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up. |
| |
| ;; RUN: foreach %s %t wasm-opt --local-cse -S -o - | filecheck %s |
| |
| (module |
| (memory 100 100) |
| ;; CHECK: (type $0 (func)) |
| |
| ;; CHECK: (type $1 (func (param i32) (result i32))) |
| |
| ;; CHECK: (type $2 (func (param i32))) |
| |
| ;; CHECK: (type $3 (func (result i32))) |
| |
| ;; CHECK: (type $4 (func (result i64))) |
| |
| ;; CHECK: (memory $0 100 100) |
| |
| ;; CHECK: (func $basics |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (local $y i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $2 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $3 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $basics) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $basics |
| (local $x i32) |
| (local $y i32) |
| ;; These two adds can be optimized. |
| (drop |
| (i32.add (i32.const 1) (i32.const 2)) |
| ) |
| (drop |
| (i32.add (i32.const 1) (i32.const 2)) |
| ) |
| (if (i32.const 0) (then (nop))) |
| ;; This add is after an if, which means we are no longer in the same basic |
| ;; block - which means we cannot optimize it with the previous identical |
| ;; adds. |
| (drop |
| (i32.add (i32.const 1) (i32.const 2)) |
| ) |
| ;; More adds with different contents than the previous, but all three are |
| ;; identical. |
| (drop |
| (i32.add (local.get $x) (local.get $y)) |
| ) |
| (drop |
| (i32.add (local.get $x) (local.get $y)) |
| ) |
| (drop |
| (i32.add (local.get $x) (local.get $y)) |
| ) |
| ;; Calls have side effects, but that is not a problem for these adds which |
| ;; only use locals, so we can optimize the add after the call. |
| (call $basics) |
| (drop |
| (i32.add (local.get $x) (local.get $y)) |
| ) |
| ;; Modify $x, which means we cannot optimize the add after the set. |
| (local.set $x (i32.const 100)) |
| (drop |
| (i32.add (local.get $x) (local.get $y)) |
| ) |
| ) |
| |
| ;; CHECK: (func $recursive1 |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (local $y i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $3 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.tee $2 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $recursive1 |
| (local $x i32) |
| (local $y i32) |
| ;; The first two dropped things are identical and can be optimized. |
| (drop |
| (i32.add |
| (i32.const 1) |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| ) |
| (drop |
| (i32.add |
| (i32.const 1) |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| ) |
| ;; The last thing here appears inside the previous pattern, and can still |
| ;; be optimized, with another local. |
| (drop |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $recursive2 |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (local $y i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $3 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.tee $2 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $recursive2 |
| (local $x i32) |
| (local $y i32) |
| ;; As before, but the order is different, with the sub-pattern in the |
| ;; middle. |
| (drop |
| (i32.add |
| (i32.const 1) |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| ) |
| (drop |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| (drop |
| (i32.add |
| (i32.const 1) |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $self |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (local $y i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.tee $2 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $self |
| (local $x i32) |
| (local $y i32) |
| ;; As before, with just the large pattern and the sub pattern, but no |
| ;; repeats of the large pattern. |
| (drop |
| (i32.add |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| ) |
| (drop |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $loads |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $0 |
| ;; CHECK-NEXT: (i32.load |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $loads |
| ;; The possible trap on loads does not prevent optimization, since if we |
| ;; trap then it doesn't matter that we replaced the later expression. |
| (drop |
| (i32.load (i32.const 10)) |
| ) |
| (drop |
| (i32.load (i32.const 10)) |
| ) |
| ) |
| |
| ;; CHECK: (func $calls (param $x i32) (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $calls |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $calls |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| (func $calls (param $x i32) (result i32) |
| ;; The side effects of calls prevent optimization. |
| (drop |
| (call $calls (i32.const 10)) |
| ) |
| (drop |
| (call $calls (i32.const 10)) |
| ) |
| (i32.const 10) |
| ) |
| |
| ;; CHECK: (func $in-calls (param $x i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $calls |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $calls |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $in-calls (param $x i32) |
| ;; The side effects of calls prevent optimization, but expressions nested in |
| ;; calls can be optimized. |
| (drop |
| (call $calls |
| (i32.add |
| (i32.const 10) |
| (i32.const 20) |
| ) |
| ) |
| ) |
| (drop |
| (call $calls |
| (i32.add |
| (i32.const 10) |
| (i32.const 20) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested-calls (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (call $nested-calls) |
| ;; CHECK-NEXT: (call $nested-calls) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (call $nested-calls) |
| ;; CHECK-NEXT: (call $nested-calls) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $nested-calls (result i32) |
| ;; Operations that include nested effects are ignored. |
| (drop |
| (i32.add |
| (call $nested-calls) |
| (call $nested-calls) |
| ) |
| ) |
| (drop |
| (i32.add |
| (call $nested-calls) |
| (call $nested-calls) |
| ) |
| ) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $many-sets (result i64) |
| ;; CHECK-NEXT: (local $temp i64) |
| ;; CHECK-NEXT: (local $1 i64) |
| ;; CHECK-NEXT: (local.set $temp |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (i64.add |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: (i64.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $temp |
| ;; CHECK-NEXT: (i64.const 9999) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $temp |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $temp) |
| ;; CHECK-NEXT: ) |
| (func $many-sets (result i64) |
| (local $temp i64) |
| ;; Assign to $temp three times here. We can optimize the add regardless of |
| ;; that, and should not be confused by the sets themselves having effects |
| ;; that are in conflict (the value is what matters). |
| (local.set $temp |
| (i64.add |
| (i64.const 1) |
| (i64.const 2) |
| ) |
| ) |
| (local.set $temp |
| (i64.const 9999) |
| ) |
| (local.set $temp |
| (i64.add |
| (i64.const 1) |
| (i64.const 2) |
| ) |
| ) |
| (local.get $temp) |
| ) |
| |
| ;; CHECK: (func $switch-children (param $x i32) (result i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (block $label$1 (result i32) |
| ;; CHECK-NEXT: (br_table $label$1 $label$1 |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (i32.and |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $switch-children (param $x i32) (result i32) |
| (block $label$1 (result i32) |
| ;; We can optimize the two children of this switch. This test verifies |
| ;; that we do so properly and do not hit an assertion involving the |
| ;; ordering of the switch's children, which was incorrect in the past. |
| (br_table $label$1 $label$1 |
| (i32.and |
| (local.get $x) |
| (i32.const 3) |
| ) |
| (i32.and |
| (local.get $x) |
| (i32.const 3) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $dominance |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $0 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $dominance |
| (drop |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| (if |
| (i32.const 0) |
| ;; This add is dominated by the above, so we can use a tee of it. |
| (then |
| (drop |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| ) |
| ;; We could optimize this add as well, but do not yet. TODO |
| (else |
| (drop |
| (i32.add |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $0 (func)) |
| |
| ;; CHECK: (global $glob (mut i32) (i32.const 1)) |
| (global $glob (mut i32) (i32.const 1)) |
| |
| ;; CHECK: (global $other-glob (mut i32) (i32.const 1)) |
| (global $other-glob (mut i32) (i32.const 1)) |
| |
| ;; CHECK: (func $global |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (global.get $glob) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (global.get $glob) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (global.set $other-glob |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (global.get $glob) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (global.set $glob |
| ;; CHECK-NEXT: (i32.const 200) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (global.get $glob) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $global |
| ;; We should not optimize redundant global.get operations: they are of size |
| ;; 1 (no children), and so we may end up increasing code size here for |
| ;; unclear benefit. The benefit is unclear since VMs already do GVN/CSE |
| ;; themselves, and so we focus on things of size 2 and above, where we |
| ;; definitely reduce code size at least. |
| (drop (global.get $glob)) |
| (drop (global.get $glob)) |
| ;; We can do it past a write to another global |
| (global.set $other-glob (i32.const 100)) |
| (drop (global.get $glob)) |
| ;; But we can't do it past a write to our global. |
| (global.set $glob (i32.const 200)) |
| (drop (global.get $glob)) |
| ) |
| ) |