[vm/compiler] Improve logic for combining types when both types have the same cid

Previously, a type with known cid was immediately selected, even
if another type also has a known cid. Now, if cids match, the static
types are also compared. This is useful for record types, because
static record type is more accurate than known kRecordCid.

TEST=vm/dart/records_field_operations_il_test

Issue: https://github.com/dart-lang/sdk/issues/51637
Cq-Include-Trybots: luci.dart.try:vm-aot-linux-release-x64-try,vm-aot-linux-debug-x64-try
Change-Id: I4e528d80a355a79d428bf3f03212c5a65af0b661
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/292983
Auto-Submit: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/tests/vm/dart/records_field_operations_il_test.dart b/runtime/tests/vm/dart/records_field_operations_il_test.dart
index 688753d..dd4369b 100644
--- a/runtime/tests/vm/dart/records_field_operations_il_test.dart
+++ b/runtime/tests/vm/dart/records_field_operations_il_test.dart
@@ -31,6 +31,12 @@
 @pragma('vm:never-inline')
 (double, double) staticCallD() => (d(1), d(2));
 
+@pragma('vm:prefer-inline')
+void inlinedCallD((double, double) xy) {
+  var (x, y) = xy;
+  print(x - y);
+}
+
 @pragma('vm:never-inline')
 @pragma('vm:testing:print-flow-graph')
 void testDouble(A obj, double a, double b, (double, double) param) {
@@ -64,6 +70,11 @@
     var (x, y) = obj.instanceCallD();
     print(x + y);
   }
+
+  {
+    final local = (a, b);
+    inlinedCallD(local);
+  }
 }
 
 void matchIL$testDouble(FlowGraph graph) {
@@ -124,6 +135,11 @@
       'v6' << match.BinaryDoubleOp('x6_unboxed', 'y6_unboxed'),
       'v6_boxed' << match.Box('v6'),
       match.MoveArgument('v6_boxed'),
+      match.StaticCall(),
+      'v7' << match.BinaryDoubleOp('a', 'b'),
+      'v7_boxed' << match.Box('v7'),
+      match.MoveArgument('v7_boxed'),
+      match.StaticCall(),
       match.Return(),
     ]),
   ]);
@@ -151,6 +167,12 @@
 @pragma('vm:never-inline')
 (int, int) staticCallI() => (i(1), i(2));
 
+@pragma('vm:prefer-inline')
+void inlinedCallI((int, int) xy) {
+  var (x, y) = xy;
+  print(x - y);
+}
+
 @pragma('vm:never-inline')
 @pragma('vm:testing:print-flow-graph')
 void testInt(B obj, int a, int b, (int, int) param) {
@@ -184,6 +206,11 @@
     var (x, y) = obj.instanceCallI();
     print(x + y);
   }
+
+  {
+    final local = (a, b);
+    inlinedCallI(local);
+  }
 }
 
 void matchIL$testInt(FlowGraph graph) {
@@ -245,6 +272,10 @@
       'v6_boxed' << match.BoxInt64('v6'),
       match.MoveArgument('v6_boxed'),
       match.StaticCall(),
+      'v7' << match.BinaryInt64Op('a', 'b'),
+      'v7_boxed' << match.BoxInt64('v7'),
+      match.MoveArgument('v7_boxed'),
+      match.StaticCall(),
       match.Return(),
     ]),
   ]);
diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc
index 7ee69f5..3e5daa2 100644
--- a/runtime/vm/compiler/backend/type_propagator.cc
+++ b/runtime/vm/compiler/backend/type_propagator.cc
@@ -627,30 +627,36 @@
     return old_type;
   }
 
-  // Prefer exact Cid if known.
-  if (new_type->ToCid() != kDynamicCid) {
-    return new_type;
-  }
-  if (old_type->ToCid() != kDynamicCid) {
-    return old_type;
-  }
-
-  const AbstractType* old_abstract_type = old_type->ToAbstractType();
-  const AbstractType* new_abstract_type = new_type->ToAbstractType();
   CompileType* preferred_type = nullptr;
 
-  // Prefer 'int' if known.
-  if (old_type->IsNullableInt()) {
-    preferred_type = old_type;
-  } else if (new_type->IsNullableInt()) {
-    preferred_type = new_type;
-  } else if (old_abstract_type->IsSubtypeOf(*new_abstract_type, Heap::kOld)) {
-    // Prefer old type, as it is clearly more specific.
-    preferred_type = old_type;
-  } else {
-    // Prefer new type as it is more recent, even though it might be
-    // no better than the old type.
-    preferred_type = new_type;
+  // Prefer exact Cid if known.
+  const intptr_t new_type_cid = new_type->ToCid();
+  const intptr_t old_type_cid = old_type->ToCid();
+  if (new_type_cid != old_type_cid) {
+    if (new_type_cid != kDynamicCid) {
+      preferred_type = new_type;
+    } else if (old_type_cid != kDynamicCid) {
+      preferred_type = old_type;
+    }
+  }
+
+  if (preferred_type == nullptr) {
+    const AbstractType* old_abstract_type = old_type->ToAbstractType();
+    const AbstractType* new_abstract_type = new_type->ToAbstractType();
+
+    // Prefer 'int' if known.
+    if (old_type->IsNullableInt()) {
+      preferred_type = old_type;
+    } else if (new_type->IsNullableInt()) {
+      preferred_type = new_type;
+    } else if (old_abstract_type->IsSubtypeOf(*new_abstract_type, Heap::kOld)) {
+      // Prefer old type, as it is clearly more specific.
+      preferred_type = old_type;
+    } else {
+      // Prefer new type as it is more recent, even though it might be
+      // no better than the old type.
+      preferred_type = new_type;
+    }
   }
 
   // Refine non-nullability and whether it can be sentinel.