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