[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>
This commit is contained in:
Alexander Markov 2022-09-19 22:05:04 +00:00 committed by Commit Bot
parent a1e7743a0a
commit ae2cebcee9
11 changed files with 130 additions and 51 deletions

View file

@ -81,8 +81,7 @@ DEFINE_NATIVE_ENTRY(Object_runtimeType, 0, 1) {
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 @@ static bool HaveSameRuntimeTypeHelper(Zone* zone,
}
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 @@ DEFINE_NATIVE_ENTRY(AbstractType_toString, 0, 1) {
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 @@ DEFINE_NATIVE_ENTRY(Type_equality, 0, 2) {
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));

View file

@ -26,11 +26,11 @@ namespace dart {
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) \

View file

@ -1324,7 +1324,7 @@ void AsmIntrinsifier::Type_equality(Assembler* assembler,
__ 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 @@ void AsmIntrinsifier::FunctionType_getHashCode(Assembler* assembler,
__ 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));

View file

@ -1495,7 +1495,7 @@ void AsmIntrinsifier::Type_equality(Assembler* assembler,
__ 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 @@ void AsmIntrinsifier::FunctionType_getHashCode(Assembler* assembler,
__ 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);

View file

@ -1460,7 +1460,7 @@ void AsmIntrinsifier::Type_equality(Assembler* assembler,
__ 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 @@ void AsmIntrinsifier::FunctionType_getHashCode(Assembler* assembler,
// 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));

View file

@ -1514,7 +1514,7 @@ void AsmIntrinsifier::Type_equality(Assembler* assembler,
__ 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 @@ void AsmIntrinsifier::FunctionType_getHashCode(Assembler* assembler,
__ 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));

View file

@ -1369,7 +1369,7 @@ void AsmIntrinsifier::Type_equality(Assembler* assembler,
__ 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 @@ void AsmIntrinsifier::FunctionType_getHashCode(Assembler* assembler,
// 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));

View file

@ -362,10 +362,10 @@ namespace dart {
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) \

View file

@ -21284,6 +21284,23 @@ AbstractTypePtr Type::InstantiateFrom(
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 @@ bool Type::IsEquivalent(const Instance& other,
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();

View file

@ -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 @@ class _FunctionType extends _AbstractType {
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")

View file

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