diff options
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 83 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc.wast | 72 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions.wast | 195 | ||||
-rw-r--r-- | test/wasm2js/conversions-modified.2asm.js.opt | 4 | ||||
-rw-r--r-- | test/wasm2js/float-ops.2asm.js.opt | 4 |
5 files changed, 345 insertions, 13 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index b6ebb961a..5df057dcc 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -28,6 +28,7 @@ #include <ir/effects.h> #include <ir/find_all.h> #include <ir/gc-type-utils.h> +#include <ir/iteration.h> #include <ir/literal-utils.h> #include <ir/load-utils.h> #include <ir/manipulation.h> @@ -864,7 +865,7 @@ struct OptimizeInstructions if (auto* ret = optimizeSelect(curr)) { return replaceCurrent(ret); } - optimizeTernary(curr, curr->condition, curr->ifTrue, curr->ifFalse); + optimizeTernary(curr); } void visitGlobalSet(GlobalSet* curr) { @@ -915,7 +916,7 @@ struct OptimizeInstructions return replaceCurrent(ret); } } - optimizeTernary(curr, curr->condition, curr->ifTrue, curr->ifFalse); + optimizeTernary(curr); } } @@ -2784,10 +2785,7 @@ private: // Optimize an if-else or a select, something with a condition and two // arms with outputs. - void optimizeTernary(Expression* curr, - Expression* condition, - Expression*& ifTrue, - Expression*& ifFalse) { + template<typename T> void optimizeTernary(T* curr) { if (curr->type == Type::unreachable) { return; } @@ -2822,7 +2820,8 @@ private: } return false; }; - if (check(ifTrue, ifFalse) || check(ifFalse, ifTrue)) { + if (check(curr->ifTrue, curr->ifFalse) || + check(curr->ifFalse, curr->ifTrue)) { auto updateArm = [&](Expression* arm) -> Expression* { if (arm == un) { // This is the arm that had the eqz, which we need to remove. @@ -2833,12 +2832,78 @@ private: return c; } }; - ifTrue = updateArm(ifTrue); - ifFalse = updateArm(ifFalse); + curr->ifTrue = updateArm(curr->ifTrue); + curr->ifFalse = updateArm(curr->ifFalse); un->value = curr; return replaceCurrent(un); } } + + { + // Identical code on both arms can be folded out, e.g. + // + // (select + // (i32.eqz (X)) + // (i32.eqz (Y)) + // (Z) + // ) + // => + // (i32.eqz + // (select + // (X) + // (Y) + // (Z) + // ) + // ) + // + // Continue doing this while we can, noting the chain of moved expressions + // as we go, then do a single replaceCurrent() at the end. + SmallVector<Expression*, 1> chain; + while (1) { + // TODO: handle none as well as concrete types (can pull a drop out, for + // example) + // TODO: consider the case with more children than 1 + if (curr->ifTrue->type.isConcrete() && + !Properties::isControlFlowStructure(curr->ifTrue) && + ExpressionAnalyzer::shallowEqual(curr->ifTrue, curr->ifFalse)) { + ChildIterator ifTrueChildren(curr->ifTrue); + if (ifTrueChildren.children.size() == 1) { + // If the expression we are about to move outside has side effects, + // we cannot replace two instances with one (and there might be + // ordering issues as well, for a select's condition). + // TODO: handle certain side effects when possible + EffectAnalyzer shallowEffects(getPassOptions(), + getModule()->features); + shallowEffects.visit(curr->ifTrue); + if (!shallowEffects.hasSideEffects()) { + // Replace ifTrue with its child. + curr->ifTrue = *ifTrueChildren.begin(); + // Relace ifFalse with its child, and reuse that node outside. + auto* reuse = curr->ifFalse; + curr->ifFalse = *ChildIterator(curr->ifFalse).begin(); + // curr's type may have changed, if the instructions we moved out + // had different input types than output types. + curr->finalize(); + // Point to curr from the code that is now outside of it. + *ChildIterator(reuse).begin() = curr; + if (!chain.empty()) { + // We've already moved things out, so chain them to there. That + // is, the end of the chain should now point to reuse (which + // in turn already points to curr). + *ChildIterator(chain.back()).begin() = reuse; + } + chain.push_back(reuse); + continue; + } + } + } + break; + } + if (!chain.empty()) { + // The beginning of the chain is the new top parent. + return replaceCurrent(chain[0]); + } + } } }; diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 78a08d9b1..3ae0e1d91 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -829,4 +829,76 @@ ) ) ) + ;; CHECK: (func $ternary-identical-arms (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (if (result (ref null $struct)) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) + (drop + (if (result i32) + (local.get $x) + (ref.is_null (local.get $y)) + (ref.is_null (local.get $z)) + ) + ) + ) + ;; CHECK: (func $ternary-identical-arms-but-side-effect (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (struct.get_u $struct $i8 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.get_u $struct $i8 + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms-but-side-effect (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) + (drop + (select + ;; the arms are equal but have side effects + (struct.get_u $struct 0 + (local.get $x) + ) + (struct.get_u $struct 0 + (local.get $y) + ) + (local.get $z) + ) + ) + ) + ;; CHECK: (func $ternary-identical-arms-no-side-effect (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get_u $struct $i8 + ;; CHECK-NEXT: (select (result (ref $struct)) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms-no-side-effect (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) + (drop + (select + ;; the arms are equal and as the params are non-null, there are no possible side effects + (struct.get_u $struct 0 + (local.get $x) + ) + (struct.get_u $struct 0 + (local.get $y) + ) + (local.get $z) + ) + ) + ) ) diff --git a/test/lit/passes/optimize-instructions.wast b/test/lit/passes/optimize-instructions.wast index bb05f17ba..7150fc839 100644 --- a/test/lit/passes/optimize-instructions.wast +++ b/test/lit/passes/optimize-instructions.wast @@ -11818,4 +11818,199 @@ ) ) ) + ;; CHECK: (func $ternary-identical-arms (param $x i32) (param $y i32) (param $z i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms (param $x i32) (param $y i32) (param $z i32) + (drop + (select + (i32.eqz (local.get $x)) + (i32.eqz (local.get $y)) + (local.get $z) + ) + ) + ) + ;; CHECK: (func $ternary-identical-arms-if (param $x i32) (param $y i32) (param $z i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (if (result i32) + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms-if (param $x i32) (param $y i32) (param $z i32) + (drop + (if (result i32) + (local.get $z) + (i32.eqz (local.get $x)) + (i32.eqz (local.get $y)) + ) + ) + ) + ;; CHECK: (func $ternary-identical-arms-type-change (param $x f64) (param $y f64) (param $z i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.demote_f64 + ;; CHECK-NEXT: (f64.floor + ;; CHECK-NEXT: (if (result f64) + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms-type-change (param $x f64) (param $y f64) (param $z i32) + (drop + ;; the if's type begins as f32, but after moving code out it will be + ;; f64 + (if (result f32) + (local.get $z) + (f32.demote_f64 (f64.floor (local.get $x))) + (f32.demote_f64 (f64.floor (local.get $y))) + ) + ) + ) + ;; CHECK: (func $ternary-identical-arms-more (param $x f32) (param $y f32) (param $z i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.floor + ;; CHECK-NEXT: (f32.neg + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms-more (param $x f32) (param $y f32) (param $z i32) + (drop + (select + (f32.floor (f32.neg (local.get $x))) + (f32.floor (f32.neg (local.get $y))) + (local.get $z) + ) + ) + ) + ;; CHECK: (func $ternary-identical-arms-morer (param $x f32) (param $y f32) (param $z i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (f32.abs + ;; CHECK-NEXT: (f32.floor + ;; CHECK-NEXT: (f32.neg + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms-morer (param $x f32) (param $y f32) (param $z i32) + (drop + (select + (f32.abs (f32.floor (f32.neg (local.get $x)))) + (f32.abs (f32.floor (f32.neg (local.get $y)))) + (local.get $z) + ) + ) + ) + ;; CHECK: (func $ternary-identical-arms-but-type-is-none (param $x i32) (param $y i32) (param $z i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms-but-type-is-none (param $x i32) (param $y i32) (param $z i32) + (if + (local.get $z) + ;; identical arms, but type is none + (drop (i32.eqz (local.get $x))) + (drop (i32.eqz (local.get $y))) + ) + ) + ;; CHECK: (func $ternary-identical-arms-but-block (param $x i32) (param $y i32) (param $z i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (block $block (result i32) + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $block24 (result i32) + ;; CHECK-NEXT: (i32.eqz + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms-but-block (param $x i32) (param $y i32) (param $z i32) + (drop + (select + ;; identical arms, but they are control flow structures + (block (result i32) + (i32.eqz (local.get $x)) + ) + (block (result i32) + (i32.eqz (local.get $y)) + ) + (local.get $z) + ) + ) + ) + ;; CHECK: (func $ternary-identical-arms-but-binary (param $x i32) (param $y i32) (param $z i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $z) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary-identical-arms-but-binary (param $x i32) (param $y i32) (param $z i32) + (drop + (select + ;; identical arms, but they are binaries, not unaries + (i32.add + (local.get $x) + (local.get $x) + ) + (i32.add + (local.get $y) + (local.get $y) + ) + (local.get $z) + ) + ) + ) ) diff --git a/test/wasm2js/conversions-modified.2asm.js.opt b/test/wasm2js/conversions-modified.2asm.js.opt index 26480d72b..1b96f428c 100644 --- a/test/wasm2js/conversions-modified.2asm.js.opt +++ b/test/wasm2js/conversions-modified.2asm.js.opt @@ -123,13 +123,13 @@ function asmFunc(env) { } function legalstub$7($0) { - i64toi32_i32$HIGH_BITS = Math_fround(Math_abs($0)) >= Math_fround(1.0) ? ($0 > Math_fround(0.0) ? ~~Math_fround(Math_min(Math_fround(Math_floor(Math_fround($0 * Math_fround(2.3283064365386963e-10)))), Math_fround(4294967296.0))) >>> 0 : ~~Math_fround(Math_ceil(Math_fround(Math_fround($0 - Math_fround(~~$0 >>> 0 >>> 0)) * Math_fround(2.3283064365386963e-10)))) >>> 0) : 0; + i64toi32_i32$HIGH_BITS = Math_fround(Math_abs($0)) >= Math_fround(1.0) ? ~~($0 > Math_fround(0.0) ? Math_fround(Math_min(Math_fround(Math_floor(Math_fround($0 * Math_fround(2.3283064365386963e-10)))), Math_fround(4294967296.0))) : Math_fround(Math_ceil(Math_fround(Math_fround($0 - Math_fround(~~$0 >>> 0 >>> 0)) * Math_fround(2.3283064365386963e-10))))) >>> 0 : 0; setTempRet0(i64toi32_i32$HIGH_BITS | 0); return ~~$0 >>> 0; } function legalstub$9($0) { - i64toi32_i32$HIGH_BITS = Math_abs($0) >= 1.0 ? ($0 > 0.0 ? ~~Math_min(Math_floor($0 * 2.3283064365386963e-10), 4294967295.0) >>> 0 : ~~Math_ceil(($0 - +(~~$0 >>> 0 >>> 0)) * 2.3283064365386963e-10) >>> 0) : 0; + i64toi32_i32$HIGH_BITS = Math_abs($0) >= 1.0 ? ~~($0 > 0.0 ? Math_min(Math_floor($0 * 2.3283064365386963e-10), 4294967295.0) : Math_ceil(($0 - +(~~$0 >>> 0 >>> 0)) * 2.3283064365386963e-10)) >>> 0 : 0; setTempRet0(i64toi32_i32$HIGH_BITS | 0); return ~~$0 >>> 0; } diff --git a/test/wasm2js/float-ops.2asm.js.opt b/test/wasm2js/float-ops.2asm.js.opt index ae9e7c87f..a78785a62 100644 --- a/test/wasm2js/float-ops.2asm.js.opt +++ b/test/wasm2js/float-ops.2asm.js.opt @@ -239,12 +239,12 @@ function asmFunc(env) { function $47($0) { $0 = Math_fround($0); - return !(~~$0 >>> 0 | (Math_fround(Math_abs($0)) >= Math_fround(1.0) ? ($0 > Math_fround(0.0) ? ~~Math_fround(Math_min(Math_fround(Math_floor(Math_fround($0 * Math_fround(2.3283064365386963e-10)))), Math_fround(4294967296.0))) >>> 0 : ~~Math_fround(Math_ceil(Math_fround(Math_fround($0 - Math_fround(~~$0 >>> 0 >>> 0)) * Math_fround(2.3283064365386963e-10)))) >>> 0) : 0)) | 0; + return !(~~$0 >>> 0 | (Math_fround(Math_abs($0)) >= Math_fround(1.0) ? ~~($0 > Math_fround(0.0) ? Math_fround(Math_min(Math_fround(Math_floor(Math_fround($0 * Math_fround(2.3283064365386963e-10)))), Math_fround(4294967296.0))) : Math_fround(Math_ceil(Math_fround(Math_fround($0 - Math_fround(~~$0 >>> 0 >>> 0)) * Math_fround(2.3283064365386963e-10))))) >>> 0 : 0)) | 0; } function $48($0) { $0 = +$0; - return !(~~$0 >>> 0 | (Math_abs($0) >= 1.0 ? ($0 > 0.0 ? ~~Math_min(Math_floor($0 * 2.3283064365386963e-10), 4294967295.0) >>> 0 : ~~Math_ceil(($0 - +(~~$0 >>> 0 >>> 0)) * 2.3283064365386963e-10) >>> 0) : 0)) | 0; + return !(~~$0 >>> 0 | (Math_abs($0) >= 1.0 ? ~~($0 > 0.0 ? Math_min(Math_floor($0 * 2.3283064365386963e-10), 4294967295.0) : Math_ceil(($0 - +(~~$0 >>> 0 >>> 0)) * 2.3283064365386963e-10)) >>> 0 : 0)) | 0; } function legalstub$43($0, $1_1) { |