diff options
author | Max Graey <maxgraey@gmail.com> | 2021-10-05 19:06:15 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-05 16:06:15 +0000 |
commit | 653b9d028c12361d3e9e0c4008f8018990b765cb (patch) | |
tree | 04fd27a6cfffdbc702a77f62af9c9eef6c3e311f | |
parent | 6feb8838887d21f2412176f7fdc1ab68b0e5c23d (diff) | |
download | binaryen-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.cpp | 57 | ||||
-rw-r--r-- | test/binaryen.js/optimize-levels.js.txt | 18 | ||||
-rw-r--r-- | test/lit/passes/Oz.wast | 128 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions.wast | 303 |
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) |