[vm] runtimeType for record instances and record type equality

TEST=language/records/simple/runtime_type_test

Issue: https://github.com/dart-lang/sdk/issues/49719
Change-Id: I031dff68241dfc62ebc3b6350b10ba7d352bab37
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/259621
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/lib/object.cc b/runtime/lib/object.cc
index 5f948e2..872713f 100644
--- a/runtime/lib/object.cc
+++ b/runtime/lib/object.cc
@@ -81,8 +81,7 @@
     return Type::IntType();
   } else if (instance.IsDouble()) {
     return Type::Double();
-  } else if (instance.IsType() || instance.IsFunctionType() ||
-             instance.IsRecordType()) {
+  } else if (instance.IsAbstractType()) {
     return Type::DartTypeType();
   } else if (IsArrayClassId(instance.GetClassId())) {
     const auto& cls = Class::Handle(
@@ -147,8 +146,23 @@
   }
 
   if (left_cid == kRecordCid) {
-    // TODO(dartbug.com/49719)
-    UNIMPLEMENTED();
+    const auto& left_record = Record::Cast(left);
+    const auto& right_record = Record::Cast(right);
+    const intptr_t num_fields = left_record.num_fields();
+    if ((num_fields != right_record.num_fields()) ||
+        (left_record.field_names() != right_record.field_names())) {
+      return false;
+    }
+    Instance& left_field = Instance::Handle(zone);
+    Instance& right_field = Instance::Handle(zone);
+    for (intptr_t i = 0; i < num_fields; ++i) {
+      left_field ^= left_record.FieldAt(i);
+      right_field ^= right_record.FieldAt(i);
+      if (!HaveSameRuntimeTypeHelper(zone, left_field, right_field)) {
+        return false;
+      }
+    }
+    return true;
   }
 
   const Class& cls = Class::Handle(zone, left.clazz());
@@ -224,6 +238,26 @@
   return type.UserVisibleName();
 }
 
+DEFINE_NATIVE_ENTRY(AbstractType_getHashCode, 0, 1) {
+  const AbstractType& type =
+      AbstractType::CheckedHandle(zone, arguments->NativeArgAt(0));
+  intptr_t hash_val = type.Hash();
+  ASSERT(hash_val > 0);
+  ASSERT(Smi::IsValid(hash_val));
+  return Smi::New(hash_val);
+}
+
+DEFINE_NATIVE_ENTRY(AbstractType_equality, 0, 2) {
+  const AbstractType& type =
+      AbstractType::CheckedHandle(zone, arguments->NativeArgAt(0));
+  const Instance& other =
+      Instance::CheckedHandle(zone, arguments->NativeArgAt(1));
+  if (type.ptr() == other.ptr()) {
+    return Bool::True().ptr();
+  }
+  return Bool::Get(type.IsEquivalent(other, TypeEquality::kSyntactical)).ptr();
+}
+
 DEFINE_NATIVE_ENTRY(Type_getHashCode, 0, 1) {
   const Type& type = Type::CheckedHandle(zone, arguments->NativeArgAt(0));
   intptr_t hash_val = type.Hash();
@@ -242,26 +276,6 @@
   return Bool::Get(type.IsEquivalent(other, TypeEquality::kSyntactical)).ptr();
 }
 
-DEFINE_NATIVE_ENTRY(FunctionType_getHashCode, 0, 1) {
-  const FunctionType& type =
-      FunctionType::CheckedHandle(zone, arguments->NativeArgAt(0));
-  intptr_t hash_val = type.Hash();
-  ASSERT(hash_val > 0);
-  ASSERT(Smi::IsValid(hash_val));
-  return Smi::New(hash_val);
-}
-
-DEFINE_NATIVE_ENTRY(FunctionType_equality, 0, 2) {
-  const FunctionType& type =
-      FunctionType::CheckedHandle(zone, arguments->NativeArgAt(0));
-  const Instance& other =
-      Instance::CheckedHandle(zone, arguments->NativeArgAt(1));
-  if (type.ptr() == other.ptr()) {
-    return Bool::True().ptr();
-  }
-  return Bool::Get(type.IsEquivalent(other, TypeEquality::kSyntactical)).ptr();
-}
-
 DEFINE_NATIVE_ENTRY(LibraryPrefix_isLoaded, 0, 1) {
   const LibraryPrefix& prefix =
       LibraryPrefix::CheckedHandle(zone, arguments->NativeArgAt(0));
diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h
index a901058..e3d6a6e 100644
--- a/runtime/vm/bootstrap_natives.h
+++ b/runtime/vm/bootstrap_natives.h
@@ -26,11 +26,11 @@
   V(Function_apply, 2)                                                         \
   V(Closure_equals, 2)                                                         \
   V(Closure_computeHash, 1)                                                    \
+  V(AbstractType_equality, 2)                                                  \
+  V(AbstractType_getHashCode, 1)                                               \
   V(AbstractType_toString, 1)                                                  \
-  V(Type_getHashCode, 1)                                                       \
   V(Type_equality, 2)                                                          \
-  V(FunctionType_getHashCode, 1)                                               \
-  V(FunctionType_equality, 2)                                                  \
+  V(Type_getHashCode, 1)                                                       \
   V(LibraryPrefix_isLoaded, 1)                                                 \
   V(LibraryPrefix_setLoaded, 1)                                                \
   V(LibraryPrefix_loadingUnit, 1)                                              \
diff --git a/runtime/vm/compiler/asm_intrinsifier_arm.cc b/runtime/vm/compiler/asm_intrinsifier_arm.cc
index 16edcd8..7890761 100644
--- a/runtime/vm/compiler/asm_intrinsifier_arm.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_arm.cc
@@ -1324,7 +1324,7 @@
   __ Bind(normal_ir_body);
 }
 
-void AsmIntrinsifier::FunctionType_getHashCode(Assembler* assembler,
+void AsmIntrinsifier::AbstractType_getHashCode(Assembler* assembler,
                                                Label* normal_ir_body) {
   __ ldr(R0, Address(SP, 0 * target::kWordSize));
   __ ldr(R0, FieldAddress(R0, target::FunctionType::hash_offset()));
@@ -1333,7 +1333,7 @@
   __ Bind(normal_ir_body);  // Hash not yet computed.
 }
 
-void AsmIntrinsifier::FunctionType_equality(Assembler* assembler,
+void AsmIntrinsifier::AbstractType_equality(Assembler* assembler,
                                             Label* normal_ir_body) {
   __ ldm(IA, SP, (1 << R1 | 1 << R2));
   __ cmp(R1, Operand(R2));
diff --git a/runtime/vm/compiler/asm_intrinsifier_arm64.cc b/runtime/vm/compiler/asm_intrinsifier_arm64.cc
index 1f305c9..d9ad060 100644
--- a/runtime/vm/compiler/asm_intrinsifier_arm64.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_arm64.cc
@@ -1495,7 +1495,7 @@
   __ Bind(normal_ir_body);
 }
 
-void AsmIntrinsifier::FunctionType_getHashCode(Assembler* assembler,
+void AsmIntrinsifier::AbstractType_getHashCode(Assembler* assembler,
                                                Label* normal_ir_body) {
   __ ldr(R0, Address(SP, 0 * target::kWordSize));
   __ LoadCompressed(R0, FieldAddress(R0, target::FunctionType::hash_offset()));
@@ -1505,7 +1505,7 @@
   __ Bind(normal_ir_body);
 }
 
-void AsmIntrinsifier::FunctionType_equality(Assembler* assembler,
+void AsmIntrinsifier::AbstractType_equality(Assembler* assembler,
                                             Label* normal_ir_body) {
   __ ldp(R1, R2, Address(SP, 0 * target::kWordSize, Address::PairOffset));
   __ CompareObjectRegisters(R1, R2);
diff --git a/runtime/vm/compiler/asm_intrinsifier_ia32.cc b/runtime/vm/compiler/asm_intrinsifier_ia32.cc
index 2f0181b..fa431d9 100644
--- a/runtime/vm/compiler/asm_intrinsifier_ia32.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_ia32.cc
@@ -1460,7 +1460,7 @@
   __ Bind(normal_ir_body);
 }
 
-void AsmIntrinsifier::FunctionType_getHashCode(Assembler* assembler,
+void AsmIntrinsifier::AbstractType_getHashCode(Assembler* assembler,
                                                Label* normal_ir_body) {
   __ movl(EAX, Address(ESP, +1 * target::kWordSize));  // FunctionType object.
   __ movl(EAX, FieldAddress(EAX, target::FunctionType::hash_offset()));
@@ -1471,7 +1471,7 @@
   // Hash not yet computed.
 }
 
-void AsmIntrinsifier::FunctionType_equality(Assembler* assembler,
+void AsmIntrinsifier::AbstractType_equality(Assembler* assembler,
                                             Label* normal_ir_body) {
   __ movl(EDI, Address(ESP, +1 * target::kWordSize));
   __ movl(EBX, Address(ESP, +2 * target::kWordSize));
diff --git a/runtime/vm/compiler/asm_intrinsifier_riscv.cc b/runtime/vm/compiler/asm_intrinsifier_riscv.cc
index 2f4d152..e14eb6d 100644
--- a/runtime/vm/compiler/asm_intrinsifier_riscv.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_riscv.cc
@@ -1514,7 +1514,7 @@
   __ Bind(normal_ir_body);
 }
 
-void AsmIntrinsifier::FunctionType_getHashCode(Assembler* assembler,
+void AsmIntrinsifier::AbstractType_getHashCode(Assembler* assembler,
                                                Label* normal_ir_body) {
   __ lx(A0, Address(SP, 0 * target::kWordSize));
   __ LoadCompressed(A0, FieldAddress(A0, target::FunctionType::hash_offset()));
@@ -1524,7 +1524,7 @@
   __ Bind(normal_ir_body);
 }
 
-void AsmIntrinsifier::FunctionType_equality(Assembler* assembler,
+void AsmIntrinsifier::AbstractType_equality(Assembler* assembler,
                                             Label* normal_ir_body) {
   __ lx(A0, Address(SP, 1 * target::kWordSize));
   __ lx(A1, Address(SP, 0 * target::kWordSize));
diff --git a/runtime/vm/compiler/asm_intrinsifier_x64.cc b/runtime/vm/compiler/asm_intrinsifier_x64.cc
index e93886b..24928d6 100644
--- a/runtime/vm/compiler/asm_intrinsifier_x64.cc
+++ b/runtime/vm/compiler/asm_intrinsifier_x64.cc
@@ -1369,7 +1369,7 @@
   __ Bind(normal_ir_body);
 }
 
-void AsmIntrinsifier::FunctionType_getHashCode(Assembler* assembler,
+void AsmIntrinsifier::AbstractType_getHashCode(Assembler* assembler,
                                                Label* normal_ir_body) {
   __ movq(RAX, Address(RSP, +1 * target::kWordSize));  // FunctionType object.
   __ LoadCompressed(RAX,
@@ -1383,7 +1383,7 @@
   // Hash not yet computed.
 }
 
-void AsmIntrinsifier::FunctionType_equality(Assembler* assembler,
+void AsmIntrinsifier::AbstractType_equality(Assembler* assembler,
                                             Label* normal_ir_body) {
   __ movq(RCX, Address(RSP, +1 * target::kWordSize));
   __ movq(RDX, Address(RSP, +2 * target::kWordSize));
diff --git a/runtime/vm/compiler/recognized_methods_list.h b/runtime/vm/compiler/recognized_methods_list.h
index 186ce59..d3241af 100644
--- a/runtime/vm/compiler/recognized_methods_list.h
+++ b/runtime/vm/compiler/recognized_methods_list.h
@@ -362,10 +362,10 @@
     OneByteString_substringUnchecked,  0x9b18195e)                             \
   V(_OneByteString, ==, OneByteString_equality, 0xb5003d69)                    \
   V(_TwoByteString, ==, TwoByteString_equality, 0xb5003d69)                    \
+  V(_AbstractType, get:hashCode, AbstractType_getHashCode, 0x75e0d454)         \
+  V(_AbstractType, ==, AbstractType_equality, 0x465868ae)                      \
   V(_Type, get:hashCode, Type_getHashCode, 0x75e0d454)                         \
   V(_Type, ==, Type_equality, 0x465868ae)                                      \
-  V(_FunctionType, get:hashCode, FunctionType_getHashCode, 0x75e0d454)         \
-  V(_FunctionType, ==, FunctionType_equality, 0x465868ae)                      \
   V(::, _getHash, Object_getHash, 0xc60ff758)                                  \
 
 #define CORE_INTEGER_LIB_INTRINSIC_LIST(V)                                     \
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index 9dd27fe..0326b1c 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -21284,6 +21284,23 @@
   return instantiated_type.NormalizeFutureOrType(space);
 }
 
+// Certain built-in classes are treated as syntactically equivalent.
+static classid_t NormalizeClassIdForSyntacticalTypeEquality(classid_t cid) {
+  if (IsIntegerClassId(cid)) {
+    return Type::Handle(Type::IntType()).type_class_id();
+  } else if (IsStringClassId(cid)) {
+    return Type::Handle(Type::StringType()).type_class_id();
+  } else if (cid == kDoubleCid) {
+    return Type::Handle(Type::Double()).type_class_id();
+  } else if (IsTypeClassId(cid)) {
+    return Type::Handle(Type::DartTypeType()).type_class_id();
+  } else if (IsArrayClassId(cid)) {
+    return Class::Handle(IsolateGroup::Current()->object_store()->list_class())
+        .id();
+  }
+  return cid;
+}
+
 bool Type::IsEquivalent(const Instance& other,
                         TypeEquality kind,
                         TrailPtr trail) const {
@@ -21302,8 +21319,14 @@
     return false;
   }
   const Type& other_type = Type::Cast(other);
-  if (type_class_id() != other_type.type_class_id()) {
-    return false;
+  const classid_t type_cid = type_class_id();
+  const classid_t other_type_cid = other_type.type_class_id();
+  if (type_cid != other_type_cid) {
+    if ((kind != TypeEquality::kSyntactical) ||
+        (NormalizeClassIdForSyntacticalTypeEquality(type_cid) !=
+         NormalizeClassIdForSyntacticalTypeEquality(other_type_cid))) {
+      return false;
+    }
   }
   Thread* thread = Thread::Current();
   Zone* zone = thread->zone();
diff --git a/sdk/lib/_internal/vm/lib/type_patch.dart b/sdk/lib/_internal/vm/lib/type_patch.dart
index 68247e8..66cffa6 100644
--- a/sdk/lib/_internal/vm/lib/type_patch.dart
+++ b/sdk/lib/_internal/vm/lib/type_patch.dart
@@ -9,6 +9,16 @@
 abstract class _AbstractType implements Type {
   @pragma("vm:external-name", "AbstractType_toString")
   external String toString();
+
+  @pragma("vm:recognized", "asm-intrinsic")
+  @pragma("vm:exact-result-type", "dart:core#_Smi")
+  @pragma("vm:external-name", "AbstractType_getHashCode")
+  external int get hashCode;
+
+  @pragma("vm:recognized", "asm-intrinsic")
+  @pragma("vm:exact-result-type", bool)
+  @pragma("vm:external-name", "AbstractType_equality")
+  external bool operator ==(other);
 }
 
 @pragma("vm:entry-point")
@@ -33,16 +43,6 @@
   factory _FunctionType._uninstantiable() {
     throw "Unreachable";
   }
-
-  @pragma("vm:recognized", "asm-intrinsic")
-  @pragma("vm:exact-result-type", "dart:core#_Smi")
-  @pragma("vm:external-name", "FunctionType_getHashCode")
-  external int get hashCode;
-
-  @pragma("vm:recognized", "asm-intrinsic")
-  @pragma("vm:exact-result-type", bool)
-  @pragma("vm:external-name", "FunctionType_equality")
-  external bool operator ==(other);
 }
 
 @pragma("vm:entry-point")
diff --git a/tests/language/records/simple/runtime_type_test.dart b/tests/language/records/simple/runtime_type_test.dart
new file mode 100644
index 0000000..d9aa715
--- /dev/null
+++ b/tests/language/records/simple/runtime_type_test.dart
@@ -0,0 +1,42 @@
+// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code as governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// SharedOptions=--enable-experiment=records
+
+import "package:expect/expect.dart";
+
+@pragma('vm:never-inline')
+Object getType1(Object obj) => obj.runtimeType;
+
+@pragma('vm:never-inline')
+Object getType2<T>() => T;
+
+@pragma('vm:never-inline')
+void testRuntimeTypeEquality(bool expected, Object a, Object b) {
+  bool result1 = getType1(a) == getType1(b);
+  Expect.equals(expected, result1);
+  // Test optimized 'a.runtimeType == b.runtimeType' pattern.
+  bool result2 = a.runtimeType == b.runtimeType;
+  Expect.equals(expected, result2);
+}
+
+main() {
+  Expect.equals(getType1(true), bool);
+  Expect.equals(getType1(false), getType2<bool>());
+  Expect.equals(getType1(1), getType2<int>());
+  Expect.equals(getType1((true, 3)), getType2<(bool, int)>());
+  Expect.equals(getType1((foo: true, bar: 2)), getType2<({int bar, bool foo})>());
+  Expect.equals(getType1((1, foo: true, false, bar: 2)), getType2<(int, bool, {int bar, bool foo})>());
+
+  testRuntimeTypeEquality(true, (1, 2), (3, 4));
+  testRuntimeTypeEquality(false, (1, 2), (1, 2, 3));
+  testRuntimeTypeEquality(false, (1, 2), (1, false));
+  testRuntimeTypeEquality(true, (1, 2, foo: true), (foo: false, 5, 4));
+  testRuntimeTypeEquality(false, (1, 2, foo: true), (bar: false, 5, 4));
+  testRuntimeTypeEquality(true, (foo: 1, bar: 2), (bar: 3, foo: 4));
+  testRuntimeTypeEquality(false, (foo: 1, bar: 2), (1, foo: 2));
+  testRuntimeTypeEquality(false, (1, 2), 3);
+  testRuntimeTypeEquality(false, (1, 2), 'hey');
+  testRuntimeTypeEquality(false, (1, 2), [1, 2]);
+}