[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]);
+}