[vm/compiler] Canonicalize v1 = Phi(v2, v1)

Previously, compiler canonicalized Phi instructions which reference
only one value (modulo redefinitions):

   v1 = Phi(v2, v2, ..., v2)

This change extends the canonicalization rule to also allow the Phi
to reference itself:

  v1 = Phi(v2, v2, ...., v2, v1, ...., v1)

Such Phi instructions may occur in loops when local variable is not
mutated (remains the same on the back-edge).

TEST=ci
Change-Id: I2838c4307a6dd3067d52624db66487ba4682538c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/195043
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index ccf3340..777e394 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -5851,8 +5851,9 @@
   bool look_for_redefinition = false;
   for (intptr_t i = 1; i < InputCount(); ++i) {
     Definition* def = InputAt(i)->definition();
-    if (def != first) {
-      if (def->OriginalDefinition() != first_origin) return nullptr;
+    if ((def != first) && (def != this)) {
+      Definition* origin = def->OriginalDefinition();
+      if ((origin != first_origin) && (origin != this)) return nullptr;
       look_for_redefinition = true;
     }
   }
@@ -5865,7 +5866,7 @@
       bool found = false;
       do {
         Definition* def = value->definition();
-        if (def == redef) {
+        if ((def == redef) || (def == this)) {
           found = true;
           break;
         }
@@ -5973,7 +5974,8 @@
   if (create_array == nullptr) {
     // Do not try to fold interpolate if array is an OSR argument.
     ASSERT(flow_graph->IsCompiledForOsr());
-    ASSERT(value()->definition()->IsPhi());
+    ASSERT(value()->definition()->IsPhi() ||
+           value()->definition()->IsParameter());
     return this;
   }
   // Check if the string interpolation has only constant inputs.
diff --git a/runtime/vm/compiler/backend/il_test.cc b/runtime/vm/compiler/backend/il_test.cc
index fa13d6a..3a0286c 100644
--- a/runtime/vm/compiler/backend/il_test.cc
+++ b/runtime/vm/compiler/backend/il_test.cc
@@ -261,4 +261,56 @@
                                                kUnboxedInt64, kUnboxedUint32));
 }
 
+ISOLATE_UNIT_TEST_CASE(IL_PhiCanonicalization) {
+  using compiler::BlockBuilder;
+
+  CompilerState S(thread, /*is_aot=*/false, /*is_optimizing=*/true);
+
+  FlowGraphBuilderHelper H;
+
+  auto normal_entry = H.flow_graph()->graph_entry()->normal_entry();
+  auto b2 = H.JoinEntry();
+  auto b3 = H.TargetEntry();
+  auto b4 = H.TargetEntry();
+
+  Definition* v0;
+  ReturnInstr* ret;
+  PhiInstr* phi;
+
+  {
+    BlockBuilder builder(H.flow_graph(), normal_entry);
+    v0 = builder.AddParameter(0, 0, /*with_frame=*/true, kTagged);
+    builder.AddInstruction(new GotoInstr(b2, S.GetNextDeoptId()));
+  }
+
+  {
+    BlockBuilder builder(H.flow_graph(), b2);
+    phi = new PhiInstr(b2, 2);
+    phi->SetInputAt(0, new Value(v0));
+    phi->SetInputAt(1, new Value(phi));
+    builder.AddPhi(phi);
+    builder.AddBranch(new StrictCompareInstr(
+                          InstructionSource(), Token::kEQ_STRICT,
+                          new Value(H.IntConstant(1)), new Value(phi),
+                          /*needs_number_check=*/false, S.GetNextDeoptId()),
+                      b3, b4);
+  }
+
+  {
+    BlockBuilder builder(H.flow_graph(), b3);
+    builder.AddInstruction(new GotoInstr(b2, S.GetNextDeoptId()));
+  }
+
+  {
+    BlockBuilder builder(H.flow_graph(), b4);
+    ret = builder.AddReturn(new Value(phi));
+  }
+
+  H.FinishGraph();
+
+  H.flow_graph()->Canonicalize();
+
+  EXPECT(ret->value()->definition() == v0);
+}
+
 }  // namespace dart