| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| |
| ;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s |
| |
| (module |
| ;; CHECK: (import "a" "b" (func $import (type $3) (result i32))) |
| (import "a" "b" (func $import (result i32))) |
| |
| ;; CHECK: (func $infinite-loop (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $infinite-loop |
| (local $x i32) |
| ;; An infinite loop. We should not hang, but nothing can be optimized. |
| (loop $loop |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (br $loop) |
| ) |
| ) |
| |
| ;; CHECK: (func $almost-infinite-loop (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $almost-infinite-loop |
| (local $x i32) |
| ;; A loop that continues until an overflow happens. We should not hang, but |
| ;; nothing can be optimized. |
| (loop $loop |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ;; Stop looping after we go all the way back to 0. |
| (br_if $loop |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $infinite-loop-with-branch (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.gt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $infinite-loop-with-branch |
| (local $x i32) |
| ;; An infinite loop that *looks* like it might not be infinite. We have a |
| ;; branch with a loop-like condition, but it is just a nop. |
| (loop $loop |
| (if |
| (i32.gt_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| (then |
| (nop) |
| ) |
| ) |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (br $loop) |
| ) |
| ) |
| |
| ;; CHECK: (func $bound (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (call $import) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.gt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.le_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bound |
| (local $x i32) |
| (loop $loop |
| ;; We arrive at the loop top with {x == 0} || {x > 0 && x <= 100}. Those |
| ;; OR into {x >= 0 && x <= 100} - that is, the x == 0 and x > 0 combine |
| ;; into x >= 0. |
| (drop |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (drop |
| (i32.le_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| ) |
| ;; Set $x to an unknown value before applying the constraints below on the |
| ;; way back to the loop top. |
| (local.set $x |
| (call $import) |
| ) |
| (if |
| (i32.gt_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| (then |
| (if |
| (i32.le_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| (then |
| (br $loop) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $bound-flipped-ifs (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (call $import) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.le_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.gt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bound-flipped-ifs |
| (local $x i32) |
| ;; As above, but with the ifs flipped. We optimize the same way. |
| (loop $loop |
| (drop |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (drop |
| (i32.le_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| ) |
| (local.set $x |
| (call $import) |
| ) |
| (if |
| (i32.le_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| (then |
| (if |
| (i32.gt_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| (then |
| (br $loop) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $bound-nonconstant-no (type $1) (param $p i32) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $p) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.le_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $p) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (call $import) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.gt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $p) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.le_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bound-nonconstant-no (param $p i32) |
| (local $x i32) |
| ;; As above, but rather than zero we have an unknown param $p. |
| (loop $loop |
| ;; We can infer nothing here, as p is unknown, and x might be 0 or <= 100. |
| (drop |
| (i32.ge_s |
| (local.get $x) |
| (local.get $p) |
| ) |
| ) |
| (drop |
| (i32.le_s |
| (local.get $x) |
| (local.get $p) |
| ) |
| ) |
| (local.set $x |
| (call $import) |
| ) |
| (if |
| (i32.gt_s |
| (local.get $x) |
| (local.get $p) |
| ) |
| (then |
| (if |
| (i32.le_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| (then |
| (br $loop) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $bound-incremented (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.lt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bound-incremented |
| (local $x i32) |
| (loop $loop |
| ;; A realistic do-while loop, with $x++ and a bounds check. We can infer |
| ;; that no overflow happens, and prove these two checks are true. |
| (drop |
| (i32.lt_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| ) |
| (drop |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| ;; This changed compared to previous testcases: now we have x++. |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (if |
| (i32.lt_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| (then |
| (br $loop) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $bound-incremented-unsigned (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.lt_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bound-incremented-unsigned |
| ;; As above, but with unsigned operations. Again, we optimize these to 1. |
| (local $x i32) |
| (loop $loop |
| (drop |
| (i32.lt_u |
| (local.get $x) |
| (i32.const 100) |
| ) |
| ) |
| (drop |
| (i32.ge_u |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (if |
| (i32.lt_u |
| (local.get $x) |
| (i32.const 100) |
| ) |
| (then |
| (br $loop) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $bound-incremented-while (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $out) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bound-incremented-while |
| ;; Similar to above, but before we had a do-while loop (loop condition at |
| ;; the bottom) and now it is at the top. |
| (local $x i32) |
| (block $out |
| (loop $loop |
| ;; Conditional branch at the top. |
| (if |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| (then |
| (br $out) |
| ) |
| ) |
| ;; We can infer both of these to be 1. |
| (drop |
| (i32.lt_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| ) |
| (drop |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ;; Unconditional branch at the bottom. |
| (br $loop) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $bound-incremented-inc-first (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $out) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bound-incremented-inc-first |
| ;; Similar to the above "while" loop, but now with the increment before the |
| ;; if. |
| (local $x i32) |
| (block $out |
| (loop $loop |
| ;; Increment at the top. |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ;; If after the increment. |
| (if |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| (then |
| (br $out) |
| ) |
| ) |
| ;; x > 0 && x < 100 here (0 is impossible, compared to before). |
| (drop |
| (i32.gt_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (drop |
| (i32.lt_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| ) |
| (br $loop) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $bound-incremented-inc-first-less (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.gt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $out) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bound-incremented-inc-first-less |
| ;; As in the last testcase, but the if's condition changed. |
| (local $x i32) |
| (block $out |
| (loop $loop |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ;; Before we left the loop when x >= 100. Now we leave when x > 100, |
| ;; so we do actually reach 100 in the code below. |
| (if |
| (i32.gt_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| (then |
| (br $out) |
| ) |
| ) |
| ;; x > 0 && x <= 100 here. |
| (drop |
| (i32.gt_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (drop |
| (i32.le_s |
| (local.get $x) |
| (i32.const 100) |
| ) |
| ) |
| (br $loop) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $bound-incremented-inc-first-less-unsigned (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.gt_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $out) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $bound-incremented-inc-first-less-unsigned |
| ;; As in the last testcase, but unsigned. |
| (local $x i32) |
| (block $out |
| (loop $loop |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (if |
| (i32.gt_u |
| (local.get $x) |
| (i32.const 100) |
| ) |
| (then |
| (br $out) |
| ) |
| ) |
| ;; x > 0 && x <= 100 here. |
| (drop |
| (i32.gt_u |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (drop |
| (i32.le_u |
| (local.get $x) |
| (i32.const 100) |
| ) |
| ) |
| (br $loop) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $extra-constraint (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.gt_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $out) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.eq |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $extra-constraint |
| ;; As in the last testcase, but with extra code below. |
| (local $x i32) |
| (block $out |
| (loop $loop |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (if |
| (i32.gt_u |
| (local.get $x) |
| (i32.const 100) |
| ) |
| (then |
| (br $out) |
| ) |
| ) |
| |
| ;; An extra constraint gets added to the local here. We should still be |
| ;; able to optimize the things below. |
| (if |
| (i32.eq |
| (local.get $x) |
| (i32.const 42) |
| ) |
| (then |
| (unreachable) |
| ) |
| ) |
| |
| (drop |
| (i32.gt_u |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (drop |
| (i32.le_u |
| (local.get $x) |
| (i32.const 100) |
| ) |
| ) |
| ;; And we also optimize that extra constraint: this is true. Note that |
| ;; this shows we optimize three separate constraints (a range 0-100 plus |
| ;; an additional fact, i.e., we can optimize more than simple numerical |
| ;; spans). |
| (drop |
| (i32.ne |
| (local.get $x) |
| (i32.const 42) |
| ) |
| ) |
| (br $loop) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $while-nonconstant (type $1) (param $len i32) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $out) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $while-nonconstant (param $len i32) |
| ;; A while loop whose upper bound is a local and not a constant. |
| (local $x i32) |
| (block $out |
| (loop $loop |
| (if |
| (i32.ge_s |
| (local.get $x) |
| (local.get $len) |
| ) |
| (then |
| (br $out) |
| ) |
| ) |
| ;; We can infer both of these to be 1. |
| (drop |
| (i32.lt_s |
| (local.get $x) |
| (local.get $len) |
| ) |
| ) |
| (drop |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (br $loop) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $while-nonconstant-unsigned (type $1) (param $len i32) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $out) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $while-nonconstant-unsigned (param $len i32) |
| ;; As above, but unsigned. |
| (local $x i32) |
| (block $out |
| (loop $loop |
| (if |
| (i32.ge_u |
| (local.get $x) |
| (local.get $len) |
| ) |
| (then |
| (br $out) |
| ) |
| ) |
| ;; We can infer both of these to be 1. |
| (drop |
| (i32.lt_u |
| (local.get $x) |
| (local.get $len) |
| ) |
| ) |
| (drop |
| (i32.ge_u |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (br $loop) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $do-nonconstant (type $1) (param $len i32) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.lt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (i32.lt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $do-nonconstant (param $len i32) |
| ;; A do loop whose upper bound is a local and not a constant. The loop must |
| ;; be entered only if at least one iteration would run. |
| (local $x i32) |
| (if |
| (i32.lt_s |
| (local.get $x) |
| (local.get $len) |
| ) |
| (then |
| (loop $loop |
| ;; We can infer both of these to be 1. |
| (drop |
| (i32.lt_s |
| (local.get $x) |
| (local.get $len) |
| ) |
| ) |
| (drop |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (br_if $loop |
| (i32.lt_s |
| (local.get $x) |
| (local.get $len) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $do-nonconstant-unsigned (type $1) (param $len i32) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.lt_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (i32.lt_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $do-nonconstant-unsigned (param $len i32) |
| ;; As above, but unsigned. |
| (local $x i32) |
| (if |
| (i32.lt_u |
| (local.get $x) |
| (local.get $len) |
| ) |
| (then |
| (loop $loop |
| ;; We can infer both of these to be 1. |
| (drop |
| (i32.lt_u |
| (local.get $x) |
| (local.get $len) |
| ) |
| ) |
| (drop |
| (i32.ge_u |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (br_if $loop |
| (i32.lt_u |
| (local.get $x) |
| (local.get $len) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested (type $1) (param $len i32) |
| ;; CHECK-NEXT: (local $i i32) |
| ;; CHECK-NEXT: (local $j i32) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (loop $outer |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: (local.get $len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $out) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $i |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block $in |
| ;; CHECK-NEXT: (local.set $j |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (loop $inner |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $j) |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $in) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $j |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $j) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $inner) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $outer) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested (param $len i32) |
| (local $i i32) |
| (local $j i32) |
| (block $out |
| ;; Outer loop, i goes from 0 to len. |
| (loop $outer |
| (if |
| (i32.ge_s |
| (local.get $i) |
| (local.get $len) |
| ) |
| (then |
| (br $out) |
| ) |
| ) |
| ;; We can infer both of these to be 1. |
| (drop |
| (i32.lt_s |
| (local.get $i) |
| (local.get $len) |
| ) |
| ) |
| (drop |
| (i32.ge_s |
| (local.get $i) |
| (i32.const 0) |
| ) |
| ) |
| (local.set $i |
| (i32.add |
| (local.get $i) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; Inner loop, j goes from 0 to i. |
| (block $in |
| (local.set $j |
| (i32.const 0) |
| ) |
| (loop $inner |
| (if |
| (i32.ge_s |
| (local.get $j) |
| (local.get $i) |
| ) |
| (then |
| (br $in) |
| ) |
| ) |
| ;; We can infer both of these to be 1. |
| (drop |
| (i32.lt_s |
| (local.get $j) |
| (local.get $i) |
| ) |
| ) |
| (drop |
| (i32.ge_s |
| (local.get $j) |
| (i32.const 0) |
| ) |
| ) |
| (local.set $j |
| (i32.add |
| (local.get $j) |
| (i32.const 1) |
| ) |
| ) |
| (br $inner) |
| ) |
| ) |
| |
| (br $outer) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested-increment-i-later (type $1) (param $len i32) |
| ;; CHECK-NEXT: (local $i i32) |
| ;; CHECK-NEXT: (local $j i32) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (loop $outer |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: (local.get $len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $out) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block $in |
| ;; CHECK-NEXT: (local.set $j |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (loop $inner |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $j) |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $in) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $j |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $j) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $inner) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $i |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $outer) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-increment-i-later (param $len i32) |
| ;; As $nested, but i is incremented after the inner loop. |
| (local $i i32) |
| (local $j i32) |
| (block $out |
| (loop $outer |
| (if |
| (i32.ge_s |
| (local.get $i) |
| (local.get $len) |
| ) |
| (then |
| (br $out) |
| ) |
| ) |
| ;; We can infer both of these to be 1. |
| (drop |
| (i32.lt_s |
| (local.get $i) |
| (local.get $len) |
| ) |
| ) |
| (drop |
| (i32.ge_s |
| (local.get $i) |
| (i32.const 0) |
| ) |
| ) |
| |
| ;; Inner loop. |
| (block $in |
| (local.set $j |
| (i32.const 0) |
| ) |
| (loop $inner |
| (if |
| (i32.ge_s |
| (local.get $j) |
| (local.get $i) |
| ) |
| (then |
| (br $in) |
| ) |
| ) |
| ;; We can infer both of these to be 1. |
| (drop |
| (i32.lt_s |
| (local.get $j) |
| (local.get $i) |
| ) |
| ) |
| (drop |
| (i32.ge_s |
| (local.get $j) |
| (i32.const 0) |
| ) |
| ) |
| (local.set $j |
| (i32.add |
| (local.get $j) |
| (i32.const 1) |
| ) |
| ) |
| (br $inner) |
| ) |
| ) |
| |
| ;; Outer loop's increment+branch. |
| (local.set $i |
| (i32.add |
| (local.get $i) |
| (i32.const 1) |
| ) |
| ) |
| (br $outer) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested-from-nonconstant-no (type $1) (param $len i32) |
| ;; CHECK-NEXT: (local $i i32) |
| ;; CHECK-NEXT: (local $j i32) |
| ;; CHECK-NEXT: (block $out |
| ;; CHECK-NEXT: (loop $outer |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: (local.get $len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $out) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $i |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block $in |
| ;; CHECK-NEXT: (local.set $j |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (loop $inner |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $j) |
| ;; CHECK-NEXT: (local.get $len) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $in) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $j) |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $j |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $j) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $inner) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $outer) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-from-nonconstant-no (param $len i32) |
| ;; As above, but the inner loop is i..len rather than 0..i |
| (local $i i32) |
| (local $j i32) |
| (block $out |
| (loop $outer |
| (if |
| (i32.ge_s |
| (local.get $i) |
| (local.get $len) |
| ) |
| (then |
| (br $out) |
| ) |
| ) |
| ;; We can infer both of these to be 1. |
| (drop |
| (i32.lt_s |
| (local.get $i) |
| (local.get $len) |
| ) |
| ) |
| (drop |
| (i32.ge_s |
| (local.get $i) |
| (i32.const 0) |
| ) |
| ) |
| (local.set $i |
| (i32.add |
| (local.get $i) |
| (i32.const 1) |
| ) |
| ) |
| |
| (block $in |
| (local.set $j |
| (local.get $i) |
| ) |
| (loop $inner |
| (if |
| (i32.ge_s |
| (local.get $j) |
| (local.get $len) |
| ) |
| (then |
| (br $in) |
| ) |
| ) |
| ;; We don't infer the second of these, as we don't extend ranges for |
| ;; non-constant initial values yet. TODO |
| (drop |
| (i32.lt_s |
| (local.get $j) |
| (local.get $len) |
| ) |
| ) |
| (drop |
| (i32.ge_s |
| (local.get $j) |
| (local.get $i) |
| ) |
| ) |
| (local.set $j |
| (i32.add |
| (local.get $j) |
| (i32.const 1) |
| ) |
| ) |
| (br $inner) |
| ) |
| ) |
| |
| (br $outer) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $impossible-branch (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (local $y i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (i32.lt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $impossible-branch |
| (local $x i32) |
| (local $y i32) |
| (loop $loop |
| ;; x == y == 0, so x < y leads to a contradiction, and we never branch |
| ;; back up to the loop. We should not error here. |
| (br_if $loop |
| (i32.lt_s |
| (local.get $x) |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $impossible-branch-unsigned (type $0) |
| ;; CHECK-NEXT: (local $x i32) |
| ;; CHECK-NEXT: (local $y i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (i32.lt_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $impossible-branch-unsigned |
| (local $x i32) |
| (local $y i32) |
| ;; As above, but unsigned. |
| (loop $loop |
| (br_if $loop |
| (i32.lt_u |
| (local.get $x) |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $add-overflow (type $1) (param $x i32) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.gt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $add-overflow (param $x i32) |
| (if |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| (then |
| ;; Normally, x >= 0 and x++ lead to x > 0. However, we overflow if |
| ;; x == MAX_INT, so we cannot optimize the dropped value below us. |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (drop |
| (i32.gt_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $add-overflow-yes (type $1) (param $x i32) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.le_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1000) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $add-overflow-yes (param $x i32) |
| ;; As above, but we add an inner if that does x < 1000. Now x cannot be |
| ;; MAX_INT, so we do optimize to 1. |
| (if |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| (then |
| (if |
| (i32.le_s |
| (local.get $x) |
| (i32.const 1000) |
| ) |
| (then |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| (drop |
| (i32.gt_s |
| (local.get $x) |
| (i32.const 0) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $add-overflow-unsigned (type $1) (param $x i32) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.gt_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $add-overflow-unsigned (param $x i32) |
| ;; As above, but unsigned. We change the 0 to 1, as x >= 0 is always true |
| ;; for unsigned anyhow. |
| (if |
| (i32.ge_u |
| (local.get $x) |
| (i32.const 1) |
| ) |
| (then |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ;; We do not optimize, due to the risk of overflow. |
| (drop |
| (i32.gt_u |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $add-overflow-unsigned-yes (type $1) (param $x i32) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.le_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1000) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $add-overflow-unsigned-yes (param $x i32) |
| ;; As above, but unsigned. |
| (if |
| (i32.ge_u |
| (local.get $x) |
| (i32.const 1) |
| ) |
| (then |
| (if |
| (i32.le_u |
| (local.get $x) |
| (i32.const 1000) |
| ) |
| (then |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ;; We do optimize to 1. |
| (drop |
| (i32.gt_u |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $add-overflow-unsigned-yes-nonconstant (type $2) (param $x i32) (param $y i32) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.lt_u |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $add-overflow-unsigned-yes-nonconstant (param $x i32) (param $y i32) |
| ;; As above, but the upper bound is not a constant, it is $y, another |
| ;; unknown value. This is still enough: $x < $y means $x++ cannot overflow, |
| ;; as it was not MAX_INT. |
| (if |
| (i32.ge_u |
| (local.get $x) |
| (i32.const 1) |
| ) |
| (then |
| (if |
| (i32.lt_u |
| (local.get $x) |
| (local.get $y) |
| ) |
| (then |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ;; We do optimize to 1. |
| (drop |
| (i32.gt_u |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $add-overflow-signed-yes-nonconstant (type $2) (param $x i32) (param $y i32) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.ge_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.lt_s |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $add-overflow-signed-yes-nonconstant (param $x i32) (param $y i32) |
| ;; Ditto, signed. |
| (if |
| (i32.ge_s |
| (local.get $x) |
| (i32.const 1) |
| ) |
| (then |
| (if |
| (i32.lt_s |
| (local.get $x) |
| (local.get $y) |
| ) |
| (then |
| (local.set $x |
| (i32.add |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ;; We do optimize to 1. |
| (drop |
| (i32.gt_s |
| (local.get $x) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |