Version 2.12.0-260.0.dev
Merge commit '3541a4a12529d2b41e3818f17d8d1bfe279874ea' into 'dev'
diff --git a/pkg/compiler/lib/src/ssa/logging.dart b/pkg/compiler/lib/src/ssa/logging.dart
index d7c7e54..4eff5a9 100644
--- a/pkg/compiler/lib/src/ssa/logging.dart
+++ b/pkg/compiler/lib/src/ssa/logging.dart
@@ -21,7 +21,7 @@
void f(Features features)) {
if (converted == null) {
_unconverted ??= {};
- Set<HInstruction> set = _unconverted[tag] ??= new Set<HInstruction>();
+ Set<HInstruction> set = _unconverted[tag] ??= {};
if (!set.add(original)) {
return null;
}
@@ -44,6 +44,15 @@
entries.add(new OptimizationLogEntry('NullCheck', features));
}
+ void registerConditionValue(
+ HInstruction original, bool value, String where, int count) {
+ Features features = new Features();
+ features['value'] = '$value';
+ features['where'] = where;
+ features['count'] = '$count';
+ entries.add(OptimizationLogEntry('ConditionValue', features));
+ }
+
void registerFieldGet(HInvokeDynamicGetter original, HFieldGet converted) {
Features features = new Features();
if (converted.element != null) {
diff --git a/pkg/compiler/lib/src/ssa/optimize.dart b/pkg/compiler/lib/src/ssa/optimize.dart
index 5f0726f..5590b0d 100644
--- a/pkg/compiler/lib/src/ssa/optimize.dart
+++ b/pkg/compiler/lib/src/ssa/optimize.dart
@@ -223,6 +223,20 @@
return false;
}
+/// Returns `true` if the end of [block] is unreachable, e.g. due to a `throw`
+/// expression.
+bool hasUnreachableExit(HBasicBlock block) {
+ if (!block.isLive) return false;
+ HInstruction last = block.last;
+ if (last is HGoto) {
+ HInstruction previous = last.previous;
+ if (previous is HThrowExpression) return true;
+ // TODO(sra): Match other signs of unreachability, e.g. a call to a method
+ // that returns `[empty]`.
+ }
+ return false;
+}
+
/// If both inputs to known operations are available execute the operation at
/// compile-time.
class SsaInstructionSimplifier extends HBaseVisitor
@@ -1201,7 +1215,9 @@
}
void simplifyCondition(
- HBasicBlock block, HInstruction condition, bool value) {
+ HBasicBlock block, HInstruction condition, bool value, String tag) {
+ if (block == null) return;
+
// `excludePhiOutEdges: true` prevents replacing a partially dominated phi
// node input with a constant. This tends to add unnecessary assignments, by
// transforming the following, which has phi(false, x),
@@ -1225,6 +1241,7 @@
DominatedUses.of(condition, block.first, excludePhiOutEdges: true);
if (uses.isEmpty) return;
uses.replaceWith(_graph.addConstantBool(value, _closedWorld));
+ _log?.registerConditionValue(condition, value, tag, uses.length);
}
@override
@@ -1242,25 +1259,60 @@
node, _graph.addConstantBool(false, _closedWorld));
}
- bool isNegated = condition is HNot;
+ HBasicBlock thenBlock = node.thenBlock;
+ HBasicBlock elseBlock = node.elseBlock;
- if (isNegated) {
- condition = condition.inputs[0];
+ // For diamond control flow, if the end of the then- or else-block is not
+ // reachable, the other block dynamically dominates the join, so the join
+ // acts as a continuation of the else- or then- branch.
+ HBasicBlock thenContinuation = null;
+ HBasicBlock elseContinuation = null;
+ if (node.joinBlock != null) {
+ final joinPredecessors = node.joinBlock.predecessors;
+ if (joinPredecessors.length == 2) {
+ if (hasUnreachableExit(joinPredecessors[0])) {
+ elseContinuation = node.joinBlock;
+ } else if (hasUnreachableExit(joinPredecessors[1])) {
+ thenContinuation = node.joinBlock;
+ }
+ }
+ }
+
+ simplifyCondition(thenBlock, condition, true, 'then');
+ simplifyCondition(thenContinuation, condition, true, 'then-join');
+ simplifyCondition(elseBlock, condition, false, 'else');
+ simplifyCondition(elseContinuation, condition, false, 'else-join');
+
+ if (condition is HNot) {
+ // if (!t1) ... t1 ...
+ HInstruction negated = condition.inputs[0];
+ simplifyCondition(thenBlock, negated, false, 'then');
+ simplifyCondition(thenContinuation, negated, false, 'then-join');
+ simplifyCondition(elseBlock, negated, true, 'else');
+ simplifyCondition(elseContinuation, negated, true, 'else-join');
} else {
- // It is possible for LICM to move a negated version of the
- // condition out of the loop where it used. We still want to
- // simplify the nested use of the condition in that case, so
- // we look for all dominating negated conditions and replace
- // nested uses of them with true or false.
+ // It is possible for LICM to move a negated version of the condition out
+ // of the loop where it used. We still want to simplify the nested use of
+ // the condition in that case, so we look for all dominating negated
+ // conditions and replace nested uses of them with true or false.
+ //
+ // t1 = ...
+ // t2 = !t1
+ // loop
+ // if (t1)
+ // t2 // replace with `false`
+ //
Iterable<HInstruction> dominating = condition.usedBy
.where((user) => user is HNot && user.dominates(node));
dominating.forEach((hoisted) {
- simplifyCondition(node.thenBlock, hoisted, false);
- simplifyCondition(node.elseBlock, hoisted, true);
+ simplifyCondition(thenBlock, hoisted, false, 'hoisted-then');
+ simplifyCondition(
+ thenContinuation, hoisted, false, 'hoisted-then-join');
+ simplifyCondition(elseBlock, hoisted, true, 'hoisted-else');
+ simplifyCondition(elseContinuation, hoisted, true, 'hoisted-else-join');
});
}
- simplifyCondition(node.thenBlock, condition, !isNegated);
- simplifyCondition(node.elseBlock, condition, isNegated);
+
return node;
}
diff --git a/pkg/compiler/test/optimization/data/condition_value.dart b/pkg/compiler/test/optimization/data/condition_value.dart
new file mode 100644
index 0000000..e7d1904
--- /dev/null
+++ b/pkg/compiler/test/optimization/data/condition_value.dart
@@ -0,0 +1,215 @@
+// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart = 2.7
+
+// Tests for simplifying that we know a condition is true in the then-branch and
+// false in the else-branch, and sometimes after join when a diamond exits.
+
+/*member: check0:ConditionValue=[]*/
+check0(int x) {
+ if (x != 1) {
+ return 200;
+ } else {
+ return 400;
+ }
+}
+
+/*member: check1:ConditionValue=[count=1&value=false&where=else,count=1&value=true&where=then]*/
+check1(int x) {
+ if (x == 1) {
+ if (x == 1) return 100;
+ return 200;
+ } else {
+ if (x == 1) return 300;
+ return 400;
+ }
+}
+
+/*member: check2:ConditionValue=[count=1&value=false&where=then,count=1&value=true&where=else]*/
+check2(int x) {
+ if (x != 1) {
+ if (x == 1) return 100;
+ return 200;
+ } else {
+ if (x == 1) return 300;
+ return 400;
+ }
+}
+
+/*member: check3:ConditionValue=[count=1&value=false&where=else,count=1&value=true&where=then]*/
+check3(int x) {
+ if (x == 1) {
+ if (x != 1) return 100;
+ return 200;
+ } else {
+ if (x != 1) return 300;
+ return 400;
+ }
+}
+
+/*member: check4:ConditionValue=[count=1&value=false&where=else,count=1&value=true&where=then]*/
+check4(int x) {
+ if (x != 1) {
+ if (x != 1) return 100;
+ return 200;
+ } else {
+ if (x != 1) return 300;
+ return 400;
+ }
+}
+
+// The first throw is in statement position, so it has an exit edge, leaving the
+// rest of the method in the first branch 'else'.
+/*member: join0Else:ConditionValue=[count=2&value=false&where=else]*/
+join0Else(int x) {
+ int a = 0, b = 0, c = 0;
+ if (x == 1)
+ throw 'bad';
+ else
+ a = x + 1;
+ if (x == 1)
+ throw 'bad';
+ else
+ b = x + 2;
+ if (x == 1)
+ throw 'bad';
+ else
+ c = x + 3;
+ return a * b * c;
+}
+
+/*member: join1:ConditionValue=[count=2&value=false&where=else-join]*/
+join1(int x) {
+ int a = (x == 1 ? throw 'bad' : x) + 1;
+ int b = (x == 1 ? throw 'bad' : x) + 2;
+ int c = (x == 1 ? throw 'bad' : x) + 3;
+ return a * b * c;
+}
+
+/*member: join2:ConditionValue=[count=2&value=true&where=then-join]*/
+join2(int x) {
+ int a = (x == 1 ? x : throw 'bad') + 1;
+ int b = (x == 1 ? x : throw 'bad') + 2;
+ int c = (x == 1 ? x : throw 'bad') + 3;
+ return a * b * c;
+}
+
+/*member: join3:ConditionValue=[count=2&value=false&where=else-join]*/
+join3(int x) {
+ int a = (x != 1 ? throw 'bad' : x) + 1;
+ int b = (x != 1 ? throw 'bad' : x) + 2;
+ int c = (x != 1 ? throw 'bad' : x) + 3;
+ return a * b * c;
+}
+
+/*member: join4:ConditionValue=[count=2&value=true&where=then-join]*/
+join4(int x) {
+ int a = (x != 1 ? x : throw 'bad') + 1;
+ int b = (x != 1 ? x : throw 'bad') + 2;
+ int c = (x != 1 ? x : throw 'bad') + 3;
+ return a * b * c;
+}
+
+/*member: loop1:ConditionValue=[count=1&value=true&where=then]*/
+loop1(int x) {
+ for (int i = 0; i < 10; i++) {
+ if (x == 1) {
+ sink = x == 1;
+ }
+ }
+}
+
+/*member: loop2HoistedThen:ConditionValue=[count=1&value=false&where=hoisted-then]*/
+loop2HoistedThen(int x) {
+ // t1 = x == 1;
+ // t2 = !t1;
+ // loop:
+ // if (t1)
+ // sink = t2; // replaced with `false`.
+ for (int i = 0; i < 10; i++) {
+ if (x == 1) {
+ sink = x != 1;
+ }
+ }
+}
+
+/*member: loop3:ConditionValue=[count=1&value=false&where=then]*/
+loop3(int x) {
+ for (int i = 0; i < 10; i++) {
+ if (x != 1) {
+ sink = x == 1;
+ }
+ }
+}
+
+/*member: loop4:ConditionValue=[count=1&value=true&where=then]*/
+loop4(int x) {
+ for (int i = 0; i < 10; i++) {
+ if (x != 1) {
+ sink = x != 1;
+ }
+ }
+}
+
+/*member: loop5HoistedElseJoin:ConditionValue=[count=1&value=true&where=hoisted-else-join]*/
+loop5HoistedElseJoin(int x) {
+ for (int i = 0; i < 10; i++) {
+ sink = (x == 1 ? throw 'bad' : x) + 1;
+ sink = x != 1;
+ }
+}
+
+// Unlike loop5, this is not 'hoisted'. GVN is not required to match the
+// condition with its use, so the subsitution happens in a simplify pass before
+// GVN/LICM can hoist the negation.
+/*member: loop6ElseJoin:ConditionValue=[count=1&value=false&where=else-join]*/
+loop6ElseJoin(bool x) {
+ for (int i = 0; i < 10; i++) {
+ sink = (x ? throw 'bad' : i) + 1;
+ sink = !x;
+ }
+}
+
+dynamic sink;
+
+void main() {
+ check0(1);
+ check0(2);
+
+ check1(1);
+ check1(2);
+ check2(1);
+ check2(2);
+ check3(1);
+ check3(2);
+ check4(1);
+ check4(2);
+
+ join0Else(1);
+ join0Else(2);
+ join1(1);
+ join1(2);
+ join2(1);
+ join2(2);
+ join3(1);
+ join3(2);
+ join4(1);
+ join4(2);
+
+ loop1(1);
+ loop1(2);
+ loop2HoistedThen(1);
+ loop2HoistedThen(2);
+ loop3(1);
+ loop3(2);
+ loop4(1);
+ loop4(2);
+ loop5HoistedElseJoin(1);
+ loop5HoistedElseJoin(2);
+ loop6ElseJoin(true);
+ loop6ElseJoin(false);
+
+ print(sink);
+}
diff --git a/tools/VERSION b/tools/VERSION
index d2409964..1c187b9 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 12
PATCH 0
-PRERELEASE 259
+PRERELEASE 260
PRERELEASE_PATCH 0
\ No newline at end of file