summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Graey <maxgraey@gmail.com>2021-10-05 19:06:15 +0300
committerGitHub <noreply@github.com>2021-10-05 16:06:15 +0000
commit653b9d028c12361d3e9e0c4008f8018990b765cb (patch)
tree04fd27a6cfffdbc702a77f62af9c9eef6c3e311f
parent6feb8838887d21f2412176f7fdc1ab68b0e5c23d (diff)
downloadbinaryen-653b9d028c12361d3e9e0c4008f8018990b765cb.tar.gz
binaryen-653b9d028c12361d3e9e0c4008f8018990b765cb.tar.bz2
binaryen-653b9d028c12361d3e9e0c4008f8018990b765cb.zip
[OptimizeInstructions] Fold select into zero or single expression for some patterns (#4181)
i32(x) ? i32(x) : 0 ==> x i32(x) ? 0 : i32(x) ==> {x, 0} i64(x) == 0 ? 0 : i64(x) ==> x i64(x) != 0 ? i64(x) : 0 ==> x i64(x) == 0 ? i64(x) : 0 ==> {x, 0} i64(x) != 0 ? 0 : i64(x) ==> {x, 0}
-rw-r--r--src/passes/OptimizeInstructions.cpp57
-rw-r--r--test/binaryen.js/optimize-levels.js.txt18
-rw-r--r--test/lit/passes/Oz.wast128
-rw-r--r--test/lit/passes/optimize-instructions.wast303
4 files changed, 479 insertions, 27 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp
index 3153af200..97f1cb756 100644
--- a/src/passes/OptimizeInstructions.cpp
+++ b/src/passes/OptimizeInstructions.cpp
@@ -1682,6 +1682,25 @@ private:
return true;
}
+ // Check if two consecutive inputs to an instruction are equal and can be
+ // folded into the first of the two. This identifies reads from the same local
+ // variable when one of them is a "tee" operation.
+ // The inputs here must be consecutive, but it is also ok to have code with no
+ // side effects at all in the middle. For example, a Const in between is ok.
+ bool areConsecutiveInputsEqualAndFoldable(Expression* left,
+ Expression* right) {
+ if (auto* set = left->dynCast<LocalSet>()) {
+ if (auto* get = right->dynCast<LocalGet>()) {
+ if (set->isTee() && get->index == set->index) {
+ return true;
+ }
+ }
+ }
+ // stronger property than we need - we can not only fold
+ // them but remove them entirely.
+ return areConsecutiveInputsEqualAndRemovable(left, right);
+ }
+
// Canonicalizing the order of a symmetric binary helps us
// write more concise pattern matching code elsewhere.
void canonicalize(Binary* binary) {
@@ -1872,6 +1891,44 @@ private:
s->ifTrue = ifFalse;
s->ifFalse = ifTrue;
s->condition = c;
+ return s;
+ }
+ }
+ {
+ // TODO: Remove this after landing SCCP pass. See: #4161
+
+ // i32(x) ? i32(x) : 0 ==> x
+ Expression *x, *y;
+ if (matches(curr, select(any(&x), i32(0), any(&y))) &&
+ areConsecutiveInputsEqualAndFoldable(x, y)) {
+ return curr->ifTrue;
+ }
+ // i32(x) ? 0 : i32(x) ==> { x, 0 }
+ if (matches(curr, select(i32(0), any(&x), any(&y))) &&
+ areConsecutiveInputsEqualAndFoldable(x, y)) {
+ return builder.makeSequence(builder.makeDrop(x), curr->ifTrue);
+ }
+
+ // i64(x) == 0 ? 0 : i64(x) ==> x
+ // i64(x) != 0 ? i64(x) : 0 ==> x
+ if ((matches(curr, select(i64(0), any(&x), unary(EqZInt64, any(&y)))) ||
+ matches(
+ curr,
+ select(any(&x), i64(0), binary(NeInt64, any(&y), i64(0))))) &&
+ areConsecutiveInputsEqualAndFoldable(x, y)) {
+ return curr->condition->is<Unary>() ? curr->ifFalse : curr->ifTrue;
+ }
+
+ // i64(x) == 0 ? i64(x) : 0 ==> { x, 0 }
+ // i64(x) != 0 ? 0 : i64(x) ==> { x, 0 }
+ if ((matches(curr, select(any(&x), i64(0), unary(EqZInt64, any(&y)))) ||
+ matches(
+ curr,
+ select(i64(0), any(&x), binary(NeInt64, any(&y), i64(0))))) &&
+ areConsecutiveInputsEqualAndFoldable(x, y)) {
+ return builder.makeSequence(
+ builder.makeDrop(x),
+ curr->condition->is<Unary>() ? curr->ifFalse : curr->ifTrue);
}
}
{
diff --git a/test/binaryen.js/optimize-levels.js.txt b/test/binaryen.js/optimize-levels.js.txt
index b5005dd0e..e8d918a2f 100644
--- a/test/binaryen.js/optimize-levels.js.txt
+++ b/test/binaryen.js/optimize-levels.js.txt
@@ -37,11 +37,7 @@ shrinkLevel=1
(type $i (func (param i32) (result i32)))
(export "test" (func $test))
(func $test (; has Stack IR ;) (param $0 i32) (result i32)
- (select
- (local.get $0)
- (i32.const 0)
- (local.get $0)
- )
+ (local.get $0)
)
)
@@ -52,11 +48,7 @@ shrinkLevel=0
(type $i (func (param i32) (result i32)))
(export "test" (func $test))
(func $test (param $0 i32) (result i32)
- (select
- (local.get $0)
- (i32.const 0)
- (local.get $0)
- )
+ (local.get $0)
)
)
@@ -67,11 +59,7 @@ shrinkLevel=1
(type $i (func (param i32) (result i32)))
(export "test" (func $test))
(func $test (; has Stack IR ;) (param $0 i32) (result i32)
- (select
- (local.get $0)
- (i32.const 0)
- (local.get $0)
- )
+ (local.get $0)
)
)
diff --git a/test/lit/passes/Oz.wast b/test/lit/passes/Oz.wast
index 82c441213..eabcf9aff 100644
--- a/test/lit/passes/Oz.wast
+++ b/test/lit/passes/Oz.wast
@@ -5,10 +5,10 @@
(module
(memory 100 100)
- ;; CHECK: (type $i32_=>_i32 (func (param i32) (result i32)))
-
;; CHECK: (type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
+ ;; CHECK: (type $i32_=>_i32 (func (param i32) (result i32)))
+
;; CHECK: (type $i32_i32_i32_i32_=>_i32 (func (param i32 i32 i32 i32) (result i32)))
;; CHECK: (memory $0 100 100)
@@ -21,6 +21,18 @@
;; CHECK: (export "propagate-sign-for-mul-i32-smin" (func $10))
+ ;; CHECK: (export "eliminate-redundant-checks-1" (func $11))
+
+ ;; CHECK: (export "eliminate-redundant-checks-1a" (func $11))
+
+ ;; CHECK: (export "eliminate-redundant-checks-2" (func $12))
+
+ ;; CHECK: (export "eliminate-redundant-checks-2a" (func $12))
+
+ ;; CHECK: (export "eliminate-redundant-checks-skip-1" (func $13))
+
+ ;; CHECK: (export "eliminate-redundant-checks-skip-2" (func $14))
+
;; CHECK: (func $basics (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32)
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (local.tee $0
@@ -150,4 +162,116 @@
(i32.const 0x80000000)
)
)
+
+ ;; CHECK: (func $11 (; has Stack IR ;) (param $0 i32) (result i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $11 (export "eliminate-redundant-checks-1") (param $0 i32) (result i32)
+ (if
+ (local.get $0)
+ (if
+ (local.get $0)
+ (return (local.get $0))
+ )
+ )
+ (i32.const 0)
+ )
+ (func $11a (export "eliminate-redundant-checks-1a") (param $0 i32) (result i32)
+ (if
+ (select
+ (local.get $0)
+ (i32.const 0)
+ (local.get $0)
+ )
+ (return (local.get $0))
+ )
+ (i32.const 0)
+ )
+ ;; CHECK: (func $12 (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (local.tee $1
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $12 (export "eliminate-redundant-checks-2") (param $0 i32) (param $1 i32) (result i32)
+ (if
+ (local.tee $1 (local.get $0))
+ (if
+ (local.get $1)
+ (return (local.get $1))
+ )
+ )
+ (i32.const 0)
+ )
+ (func $12a (export "eliminate-redundant-checks-2a") (param $0 i32) (param $1 i32) (result i32)
+ (if
+ (select
+ (local.tee $1 (local.get $0))
+ (i32.const 0)
+ (local.get $1)
+ )
+ (return (local.get $1))
+ )
+ (i32.const 0)
+ )
+ ;; CHECK: (func $13 (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $13 (export "eliminate-redundant-checks-skip-1") (param $0 i32) (param $1 i32) (result i32)
+ (if
+ (select
+ (local.get $1)
+ (i32.const 0)
+ (local.tee $1 (local.get $0))
+ )
+ (return (local.get $1))
+ )
+ (i32.const 0)
+ )
+
+ ;; CHECK: (func $14 (; has Stack IR ;) (param $0 i32) (param $1 i32) (result i32)
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (return
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ (func $14 (export "eliminate-redundant-checks-skip-2") (param $0 i32) (param $1 i32) (result i32)
+ (if
+ (select
+ (i32.const 0)
+ (local.get $1)
+ (local.tee $1 (local.get $0))
+ )
+ (return (local.get $1))
+ )
+ (i32.const 0)
+ )
)
diff --git a/test/lit/passes/optimize-instructions.wast b/test/lit/passes/optimize-instructions.wast
index 22edf58e2..76074dadf 100644
--- a/test/lit/passes/optimize-instructions.wast
+++ b/test/lit/passes/optimize-instructions.wast
@@ -8608,18 +8608,10 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (select
- ;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: (local.get $x)
- ;; CHECK-NEXT: (i32.const 0)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (select
- ;; CHECK-NEXT: (i32.const 2)
- ;; CHECK-NEXT: (local.get $x)
- ;; CHECK-NEXT: (i32.const 2)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (select
@@ -10566,6 +10558,297 @@
(unreachable)
)
)
+ ;; CHECK: (func $select-with-same-arm-and-cond-32 (param $x i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $select-with-same-arm-and-cond-32 (param $x i32)
+ ;; i32(x) ? i32(x) : 0 ==> x
+ (drop (select
+ (local.get $x)
+ (i32.const 0)
+ (local.get $x)
+ ))
+ ;; i32(x) ? 0 : i32(x) ==> {x, 0}
+ (drop (select
+ (i32.const 0)
+ (local.get $x)
+ (local.get $x)
+ ))
+ ;; i32(x) == 0 ? i32(x) : 0 ==> {x, 0}
+ (drop (select
+ (local.get $x)
+ (i32.const 0)
+ (i32.eqz (local.get $x))
+ ))
+ )
+
+ ;; CHECK: (func $select-with-same-arm-and-cond-64 (param $x i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $select-with-same-arm-and-cond-64 (param $x i64)
+ ;; i64(x) != 0 ? i64(x) : 0 ==> x
+ (drop (select
+ (local.get $x)
+ (i64.const 0)
+ (i64.ne
+ (local.get $x)
+ (i64.const 0)
+ )
+ ))
+ ;; i64(x) == 0 ? 0 : i64(x) ==> x
+ (drop (select
+ (i64.const 0)
+ (local.get $x)
+ (i64.eqz
+ (local.get $x)
+ )
+ ))
+ ;; i64(x) != 0 ? 0 : i64(x) ==> 0
+ (drop (select
+ (i64.const 0)
+ (local.get $x)
+ (i64.ne
+ (local.get $x)
+ (i64.const 0)
+ )
+ ))
+ ;; i64(x) == 0 ? i64(x) : 0 ==> {x, 0}
+ (drop (select
+ (local.get $x)
+ (i64.const 0)
+ (i64.eqz
+ (local.get $x)
+ )
+ ))
+ (drop (select
+ (local.get $x)
+ (i64.const 0)
+ (i64.eq
+ (local.get $x)
+ (i64.const 0)
+ )
+ ))
+ )
+
+ ;; CHECK: (func $select-with-same-arm-and-cond-skips (param $x i32) (param $y i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i32.wrap_i64
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i32.sub
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (i32.const -1)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const -1)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i64.const -1)
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: (i64.ne
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: (i64.ne
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: (i64.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $select-with-same-arm-and-cond-skips (param $x i32) (param $y i64)
+ ;; skip not equals
+ (drop (select
+ (local.get $x)
+ (i32.const 0)
+ (i32.wrap_i64 (local.get $y))
+ ))
+ (drop (select
+ (i32.const 0)
+ (i32.sub (i32.const 0) (local.get $x))
+ (local.get $x)
+ ))
+
+ ;; skip not zero
+ (drop (select
+ (local.get $x)
+ (i32.const -1)
+ (local.get $x)
+ ))
+ (drop (select
+ (i32.const -1)
+ (local.get $x)
+ (local.get $x)
+ ))
+ (drop (select
+ (i64.const -1)
+ (local.get $y)
+ (i64.ne
+ (local.get $y)
+ (i64.const 0)
+ )
+ ))
+ (drop (select
+ (i64.const 0)
+ (local.get $y)
+ (i64.ne
+ (local.get $y)
+ (i64.const 1)
+ )
+ ))
+ )
+
+ ;; CHECK: (func $select-with-same-arm-and-cond-skips-side-effects (param $x i32) (param $y i64)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.div_u
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i32.div_u
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (call $ne0)
+ ;; CHECK-NEXT: (call $ne0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (call $select-sign-64-lt
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: (i64.eqz
+ ;; CHECK-NEXT: (call $select-sign-64-lt
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: (call $select-sign-64-lt
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i64.eqz
+ ;; CHECK-NEXT: (call $select-sign-64-lt
+ ;; CHECK-NEXT: (local.get $y)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $select-with-same-arm-and-cond-skips-side-effects (param $x i32) (param $y i64)
+ ;; skip with side effects
+ (drop (select
+ (i32.div_u (i32.const 10) (local.get $x))
+ (i32.const 0)
+ (i32.div_u (i32.const 10) (local.get $x))
+ ))
+ (drop (select
+ (i32.const 0)
+ (call $ne0)
+ (call $ne0)
+ ))
+ (drop (select
+ (call $select-sign-64-lt (local.get $y))
+ (i64.const 0)
+ (i64.eqz
+ (call $select-sign-64-lt (local.get $y))
+ )
+ ))
+ (drop (select
+ (i64.const 0)
+ (call $select-sign-64-lt (local.get $y))
+ (i64.eqz
+ (call $select-sign-64-lt (local.get $y))
+ )
+ ))
+ )
;; CHECK: (func $optimize-boolean-context (param $x i32) (param $y i32)
;; CHECK-NEXT: (if
;; CHECK-NEXT: (local.get $x)