[vm/compiler] Generate non-speculative Unbox instructions for Phis

TEST=runtime/tests/vm/dart/regress_flutter83094_test.dart
Issue https://github.com/flutter/flutter/issues/83094

Change-Id: Ib4eebc993e06f6925f11bd18e5f29f22ba3c6322
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/201363
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/tests/vm/dart/regress_flutter83094_test.dart b/runtime/tests/vm/dart/regress_flutter83094_test.dart
new file mode 100644
index 0000000..2572019
--- /dev/null
+++ b/runtime/tests/vm/dart/regress_flutter83094_test.dart
@@ -0,0 +1,27 @@
+// 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.
+
+// Verifies that compiler doesn't crash due to incompatible types
+// when unboxing input of a Phi.
+// Regression test for https://github.com/flutter/flutter/issues/83094.
+
+import 'package:expect/expect.dart';
+
+class A {
+  @pragma('vm:never-inline')
+  double getMaxIntrinsicWidth() => 1.toDouble();
+}
+
+A _leading = A();
+
+@pragma('vm:never-inline')
+double computeMaxIntrinsicWidth(double height, double horizontalPadding) {
+  final leadingWidth =
+      _leading == null ? 0 : _leading.getMaxIntrinsicWidth() as int;
+  return horizontalPadding + leadingWidth;
+}
+
+main() {
+  Expect.throws(() => computeMaxIntrinsicWidth(1, 2));
+}
diff --git a/runtime/tests/vm/dart_2/regress_flutter83094_test.dart b/runtime/tests/vm/dart_2/regress_flutter83094_test.dart
new file mode 100644
index 0000000..2572019
--- /dev/null
+++ b/runtime/tests/vm/dart_2/regress_flutter83094_test.dart
@@ -0,0 +1,27 @@
+// 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.
+
+// Verifies that compiler doesn't crash due to incompatible types
+// when unboxing input of a Phi.
+// Regression test for https://github.com/flutter/flutter/issues/83094.
+
+import 'package:expect/expect.dart';
+
+class A {
+  @pragma('vm:never-inline')
+  double getMaxIntrinsicWidth() => 1.toDouble();
+}
+
+A _leading = A();
+
+@pragma('vm:never-inline')
+double computeMaxIntrinsicWidth(double height, double horizontalPadding) {
+  final leadingWidth =
+      _leading == null ? 0 : _leading.getMaxIntrinsicWidth() as int;
+  return horizontalPadding + leadingWidth;
+}
+
+main() {
+  Expect.throws(() => computeMaxIntrinsicWidth(1, 2));
+}
diff --git a/runtime/vm/compiler/backend/flow_graph.cc b/runtime/vm/compiler/backend/flow_graph.cc
index a24af44..52c25a2 100644
--- a/runtime/vm/compiler/backend/flow_graph.cc
+++ b/runtime/vm/compiler/backend/flow_graph.cc
@@ -1869,7 +1869,6 @@
                                  bool is_environment_use) {
   ASSERT(from != to);
   Instruction* insert_before;
-  Instruction* deopt_target;
   PhiInstr* phi = use->instruction()->AsPhi();
   if (phi != NULL) {
     ASSERT(phi->is_alive());
@@ -1877,14 +1876,19 @@
     auto predecessor = phi->block()->PredecessorAt(use->use_index());
     insert_before = predecessor->last_instruction();
     ASSERT(insert_before->GetBlock() == predecessor);
-    deopt_target = NULL;
   } else {
-    deopt_target = insert_before = use->instruction();
+    insert_before = use->instruction();
+  }
+  const Instruction::SpeculativeMode speculative_mode =
+      use->instruction()->SpeculativeModeOfInput(use->use_index());
+  Instruction* deopt_target = nullptr;
+  if (speculative_mode == Instruction::kGuardInputs || to == kUnboxedInt32) {
+    deopt_target = insert_before;
   }
 
   Definition* converted = NULL;
   if (IsUnboxedInteger(from) && IsUnboxedInteger(to)) {
-    const intptr_t deopt_id = (to == kUnboxedInt32) && (deopt_target != NULL)
+    const intptr_t deopt_id = (to == kUnboxedInt32) && (deopt_target != nullptr)
                                   ? deopt_target->DeoptimizationTarget()
                                   : DeoptId::kNone;
     converted =
@@ -1893,18 +1897,17 @@
     converted = new Int32ToDoubleInstr(use->CopyWithType());
   } else if ((from == kUnboxedInt64) && (to == kUnboxedDouble) &&
              CanConvertInt64ToDouble()) {
-    const intptr_t deopt_id = (deopt_target != NULL)
+    const intptr_t deopt_id = (deopt_target != nullptr)
                                   ? deopt_target->DeoptimizationTarget()
                                   : DeoptId::kNone;
     ASSERT(CanUnboxDouble());
     converted = new Int64ToDoubleInstr(use->CopyWithType(), deopt_id);
   } else if ((from == kTagged) && Boxing::Supports(to)) {
-    const intptr_t deopt_id = (deopt_target != NULL)
+    const intptr_t deopt_id = (deopt_target != nullptr)
                                   ? deopt_target->DeoptimizationTarget()
                                   : DeoptId::kNone;
-    converted = UnboxInstr::Create(
-        to, use->CopyWithType(), deopt_id,
-        use->instruction()->SpeculativeModeOfInput(use->use_index()));
+    converted =
+        UnboxInstr::Create(to, use->CopyWithType(), deopt_id, speculative_mode);
   } else if ((to == kTagged) && Boxing::Supports(from)) {
     converted = BoxInstr::Create(from, use->CopyWithType());
   } else {
@@ -1912,30 +1915,32 @@
     // Insert two "dummy" conversion instructions with the correct
     // "from" and "to" representation. The inserted instructions will
     // trigger a deoptimization if executed. See #12417 for a discussion.
-    const intptr_t deopt_id = (deopt_target != NULL)
+    // If the use is not speculative, then this code should be unreachable.
+    // Insert Stop for a graceful error and aid unreachable code elimination.
+    if (speculative_mode == Instruction::kNotSpeculative) {
+      StopInstr* stop = new (Z) StopInstr("Incompatible conversion.");
+      InsertBefore(insert_before, stop, nullptr, FlowGraph::kEffect);
+    }
+    const intptr_t deopt_id = (deopt_target != nullptr)
                                   ? deopt_target->DeoptimizationTarget()
                                   : DeoptId::kNone;
     ASSERT(Boxing::Supports(from));
     ASSERT(Boxing::Supports(to));
     Definition* boxed = BoxInstr::Create(from, use->CopyWithType());
     use->BindTo(boxed);
-    InsertBefore(insert_before, boxed, NULL, FlowGraph::kValue);
-    converted = UnboxInstr::Create(to, new (Z) Value(boxed), deopt_id);
+    InsertBefore(insert_before, boxed, nullptr, FlowGraph::kValue);
+    converted = UnboxInstr::Create(to, new (Z) Value(boxed), deopt_id,
+                                   speculative_mode);
   }
-  ASSERT(converted != NULL);
-  InsertBefore(insert_before, converted, use->instruction()->env(),
+  ASSERT(converted != nullptr);
+  InsertBefore(insert_before, converted,
+               (deopt_target != nullptr) ? deopt_target->env() : nullptr,
                FlowGraph::kValue);
   if (is_environment_use) {
     use->BindToEnvironment(converted);
   } else {
     use->BindTo(converted);
   }
-
-  if ((to == kUnboxedInt32) && (phi != NULL)) {
-    // Int32 phis are unboxed optimistically. Ensure that unboxing
-    // has deoptimization target attached from the goto instruction.
-    CopyDeoptTarget(converted, insert_before);
-  }
 }
 
 void FlowGraph::InsertConversionsFor(Definition* def) {
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index a53501e..823d9da 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -2481,9 +2481,12 @@
 
   virtual void set_representation(Representation r) { representation_ = r; }
 
-  // In AOT mode Phi instructions do not check types of inputs when unboxing.
+  // Only Int32 phis in JIT mode are unboxed optimistically.
   virtual SpeculativeMode SpeculativeModeOfInput(intptr_t index) const {
-    return CompilerState::Current().is_aot() ? kNotSpeculative : kGuardInputs;
+    return (CompilerState::Current().is_aot() ||
+            (representation_ != kUnboxedInt32))
+               ? kNotSpeculative
+               : kGuardInputs;
   }
 
   virtual uword Hash() const {
@@ -3140,6 +3143,9 @@
     return true;
   }
 
+  // May require a deoptimization target for int32 Phi input conversions.
+  virtual intptr_t DeoptimizationTarget() const { return GetDeoptId(); }
+
   virtual bool ComputeCanDeoptimize() const { return false; }
 
   virtual bool HasUnknownSideEffects() const { return false; }
@@ -4097,6 +4103,7 @@
       }
       idx--;
     }
+    if (interface_target_.IsNull()) return kGuardInputs;
     return interface_target_.is_unboxed_parameter_at(idx) ? kNotSpeculative
                                                           : kGuardInputs;
   }