[VM/nnbd] Implement syntactic type equality.

Legacy types are considered equal to their non-nullable variant.
Type hash is modified to be consistent with the new definition.

Change-Id: If90f7f13cf77e519d5b90b57d9dbf4988f71be13
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/126283
Commit-Queue: Régis Crelier <regis@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Régis Crelier 2019-12-09 18:18:59 +00:00 committed by commit-bot@chromium.org
parent cb94390c63
commit 8894b88467
16 changed files with 188 additions and 46 deletions

View file

@ -109,7 +109,9 @@ DEFINE_NATIVE_ENTRY(Object_haveSameRuntimeType, 0, 2) {
AbstractType::Handle(left.GetType(Heap::kNew));
const AbstractType& right_type =
AbstractType::Handle(right.GetType(Heap::kNew));
return Bool::Get(left_type.raw() == right_type.raw()).raw();
return Bool::Get(
left_type.IsEquivalent(right_type, /* syntactically = */ true))
.raw();
}
if (!cls.IsGeneric()) {
@ -127,7 +129,7 @@ DEFINE_NATIVE_ENTRY(Object_haveSameRuntimeType, 0, 2) {
const intptr_t num_type_params = cls.NumTypeParameters();
return Bool::Get(left_type_arguments.IsSubvectorEquivalent(
right_type_arguments, num_type_args - num_type_params,
num_type_params))
num_type_params, /* syntactically = */ true))
.raw();
}
@ -186,6 +188,16 @@ DEFINE_NATIVE_ENTRY(Type_getHashCode, 0, 1) {
return Smi::New(hash_val);
}
DEFINE_NATIVE_ENTRY(Type_equality, 0, 2) {
const Type& type = Type::CheckedHandle(zone, arguments->NativeArgAt(0));
const Instance& other =
Instance::CheckedHandle(zone, arguments->NativeArgAt(1));
if (type.raw() == other.raw()) {
return Bool::True().raw();
}
return Bool::Get(type.IsEquivalent(other, /* syntactically = */ true)).raw();
}
DEFINE_NATIVE_ENTRY(Internal_inquireIs64Bit, 0, 0) {
#if defined(ARCH_IS_64_BIT)
return Bool::True().raw();

View file

@ -30,6 +30,7 @@ namespace dart {
V(Closure_clone, 1) \
V(AbstractType_toString, 1) \
V(Type_getHashCode, 1) \
V(Type_equality, 2) \
V(Identical_comparison, 2) \
V(Integer_bitAndFromInteger, 2) \
V(Integer_bitOrFromInteger, 2) \

View file

@ -378,8 +378,9 @@ void ClassFinalizer::CheckRecursiveType(const Class& cls,
if ((pending_type.raw() != type.raw()) && pending_type.IsType() &&
(pending_type.type_class() == type_cls.raw())) {
pending_arguments = pending_type.arguments();
if (!pending_arguments.IsSubvectorEquivalent(arguments, first_type_param,
num_type_params) &&
if (!pending_arguments.IsSubvectorEquivalent(
arguments, first_type_param, num_type_params,
/* syntactically = */ true) &&
!pending_arguments.IsSubvectorInstantiated(first_type_param,
num_type_params)) {
const TypeArguments& instantiated_arguments = TypeArguments::Handle(
@ -394,7 +395,8 @@ void ClassFinalizer::CheckRecursiveType(const Class& cls,
Object::null_type_arguments(), kNoneFree, NULL,
Heap::kNew));
if (!instantiated_pending_arguments.IsSubvectorEquivalent(
instantiated_arguments, first_type_param, num_type_params)) {
instantiated_arguments, first_type_param, num_type_params,
/* syntactically = */ true)) {
const String& type_name = String::Handle(zone, type.Name());
ReportError(cls, type.token_pos(), "illegal recursive type '%s'",
type_name.ToCString());

View file

@ -1688,6 +1688,22 @@ void AsmIntrinsifier::Type_getHashCode(Assembler* assembler,
__ Bind(normal_ir_body);
}
void AsmIntrinsifier::Type_equality(Assembler* assembler,
Label* normal_ir_body) {
// TODO(regis): Add more fast cases. See ObjectHaveSameRuntimeType.
__ ldr(R0, Address(SP, 0 * target::kWordSize));
__ ldr(R1, Address(SP, 1 * target::kWordSize));
__ cmp(R0, Operand(R1));
__ b(normal_ir_body, NE);
// Types are strictly equal.
__ LoadObject(R0, CastHandle<Object>(TrueObject()));
__ Ret();
// Check if types are syntactically equal.
__ Bind(normal_ir_body);
}
void GenerateSubstringMatchesSpecialization(Assembler* assembler,
intptr_t receiver_cid,
intptr_t other_cid,

View file

@ -1758,6 +1758,22 @@ void AsmIntrinsifier::Type_getHashCode(Assembler* assembler,
__ Bind(normal_ir_body);
}
void AsmIntrinsifier::Type_equality(Assembler* assembler,
Label* normal_ir_body) {
// TODO(regis): Add more fast cases. See ObjectHaveSameRuntimeType.
__ ldr(R0, Address(SP, 0 * target::kWordSize));
__ ldr(R1, Address(SP, 1 * target::kWordSize));
__ cmp(R0, Operand(R1));
__ b(normal_ir_body, NE);
// Types are strictly equal.
__ LoadObject(R0, CastHandle<Object>(TrueObject()));
__ ret();
// Check if types are syntactically equal.
__ Bind(normal_ir_body);
}
void AsmIntrinsifier::Object_getHash(Assembler* assembler,
Label* normal_ir_body) {
__ ldr(R0, Address(SP, 0 * target::kWordSize));

View file

@ -1807,6 +1807,22 @@ void AsmIntrinsifier::Type_getHashCode(Assembler* assembler,
// Hash not yet computed.
}
void AsmIntrinsifier::Type_equality(Assembler* assembler,
Label* normal_ir_body) {
// TODO(regis): Add more fast cases. See ObjectHaveSameRuntimeType.
__ movl(EAX, Address(ESP, +1 * target::kWordSize));
__ movl(EBX, Address(ESP, +2 * target::kWordSize));
__ cmpl(EAX, EBX);
__ j(NOT_EQUAL, normal_ir_body, Assembler::kNearJump);
// Types are strictly equal.
__ LoadObject(EAX, CastHandle<Object>(TrueObject()));
__ ret();
// Check if types are syntactically equal.
__ Bind(normal_ir_body);
}
// bool _substringMatches(int start, String other)
void AsmIntrinsifier::StringBaseSubstringMatches(Assembler* assembler,
Label* normal_ir_body) {

View file

@ -1723,6 +1723,22 @@ void AsmIntrinsifier::Type_getHashCode(Assembler* assembler,
// Hash not yet computed.
}
void AsmIntrinsifier::Type_equality(Assembler* assembler,
Label* normal_ir_body) {
// TODO(regis): Add more fast cases. See ObjectHaveSameRuntimeType.
__ movq(RAX, Address(RSP, +1 * target::kWordSize));
__ movq(RCX, Address(RSP, +2 * target::kWordSize));
__ cmpq(RAX, RCX);
__ j(NOT_EQUAL, normal_ir_body);
// Types are strictly equal.
__ LoadObject(RAX, CastHandle<Object>(TrueObject()));
__ ret();
// Check if types are syntactically equal.
__ Bind(normal_ir_body);
}
void AsmIntrinsifier::Object_getHash(Assembler* assembler,
Label* normal_ir_body) {
__ movq(RAX, Address(RSP, +1 * target::kWordSize)); // Object.

View file

@ -387,7 +387,7 @@ bool HierarchyInfo::CanUseSubtypeRangeCheckFor(const AbstractType& type) {
// arguments are not "dynamic" but instantiated-to-bounds.
const Type& rare_type =
Type::Handle(zone, Type::RawCast(type_class.RareType()));
if (!rare_type.Equals(type)) {
if (!rare_type.IsEquivalent(type, /* syntactically = */ true)) {
return false;
}
}

View file

@ -380,7 +380,7 @@ bool SimpleInstanceOfType(const AbstractType& type) {
// type arguments, then we can still use the _simpleInstanceOf
// implementation (see also runtime/lib/object.cc:Object_SimpleInstanceOf).
const auto& rare_type = AbstractType::Handle(type_class.RareType());
return rare_type.Equals(type);
return rare_type.IsEquivalent(type, /* syntactically = */ true);
}
// Finally a simple class for instance of checking.

View file

@ -222,6 +222,7 @@ namespace dart {
V(_OneByteString, ==, OneByteString_equality, 0xe1ea0c11) \
V(_TwoByteString, ==, TwoByteString_equality, 0xe1ea0c11) \
V(_Type, get:hashCode, Type_getHashCode, 0x22a75237) \
V(_Type, ==, Type_equality, 0x91ead098) \
V(::, _getHash, Object_getHash, 0xb05aa13f) \
V(::, _setHash, Object_setHash, 0xcb404dd2) \

View file

@ -5252,6 +5252,7 @@ RawString* TypeArguments::SubvectorName(intptr_t from_index,
bool TypeArguments::IsSubvectorEquivalent(const TypeArguments& other,
intptr_t from_index,
intptr_t len,
bool syntactically,
TrailPtr trail) const {
if (this->raw() == other.raw()) {
return true;
@ -5269,7 +5270,7 @@ bool TypeArguments::IsSubvectorEquivalent(const TypeArguments& other,
type = TypeAt(i);
other_type = other.TypeAt(i);
// Still unfinalized vectors should not be considered equivalent.
if (type.IsNull() || !type.IsEquivalent(other_type, trail)) {
if (type.IsNull() || !type.IsEquivalent(other_type, syntactically, trail)) {
return false;
}
}
@ -17202,7 +17203,9 @@ void AbstractType::SetIsBeingFinalized() const {
UNREACHABLE();
}
bool AbstractType::IsEquivalent(const Instance& other, TrailPtr trail) const {
bool AbstractType::IsEquivalent(const Instance& other,
bool syntactically,
TrailPtr trail) const {
// AbstractType is an abstract class.
UNREACHABLE();
return false;
@ -17983,7 +17986,9 @@ RawAbstractType* Type::InstantiateFrom(
return instantiated_type.raw();
}
bool Type::IsEquivalent(const Instance& other, TrailPtr trail) const {
bool Type::IsEquivalent(const Instance& other,
bool syntactically,
TrailPtr trail) const {
ASSERT(!IsNull());
if (raw() == other.raw()) {
return true;
@ -17993,7 +17998,7 @@ bool Type::IsEquivalent(const Instance& other, TrailPtr trail) const {
const AbstractType& other_ref_type =
AbstractType::Handle(TypeRef::Cast(other).type());
ASSERT(!other_ref_type.IsTypeRef());
return IsEquivalent(other_ref_type, trail);
return IsEquivalent(other_ref_type, syntactically, trail);
}
if (!other.IsType()) {
return false;
@ -18005,7 +18010,17 @@ bool Type::IsEquivalent(const Instance& other, TrailPtr trail) const {
if (type_class_id() != other_type.type_class_id()) {
return false;
}
if (nullability() != other_type.nullability()) {
Nullability this_type_nullability = nullability();
Nullability other_type_nullability = other_type.nullability();
if (syntactically) {
if (this_type_nullability == Nullability::kLegacy) {
this_type_nullability = Nullability::kNonNullable;
}
if (other_type_nullability == Nullability::kLegacy) {
other_type_nullability = Nullability::kNonNullable;
}
}
if (this_type_nullability != other_type_nullability) {
return false;
}
if (!IsFinalized() || !other_type.IsFinalized()) {
@ -18038,7 +18053,8 @@ bool Type::IsEquivalent(const Instance& other, TrailPtr trail) const {
return false;
}
} else if (!type_args.IsSubvectorEquivalent(other_type_args, from_index,
num_type_params, trail)) {
num_type_params,
syntactically, trail)) {
return false;
}
#ifdef DEBUG
@ -18054,7 +18070,7 @@ bool Type::IsEquivalent(const Instance& other, TrailPtr trail) const {
for (intptr_t i = 0; i < from_index; i++) {
type_arg = type_args.TypeAt(i);
other_type_arg = other_type_args.TypeAt(i);
ASSERT(type_arg.IsEquivalent(other_type_arg, trail));
ASSERT(type_arg.IsEquivalent(other_type_arg, syntactically, trail));
}
}
#endif
@ -18117,7 +18133,7 @@ bool Type::IsEquivalent(const Instance& other, TrailPtr trail) const {
for (intptr_t i = 0; i < num_params; i++) {
param_type = sig_fun.ParameterTypeAt(i);
other_param_type = other_sig_fun.ParameterTypeAt(i);
if (!param_type.Equals(other_param_type)) {
if (!param_type.IsEquivalent(other_param_type, syntactically)) {
return false;
}
}
@ -18129,6 +18145,7 @@ bool Type::IsEquivalent(const Instance& other, TrailPtr trail) const {
if (sig_fun.ParameterNameAt(i) != other_sig_fun.ParameterNameAt(i)) {
return false;
}
// TODO(regis): Check 'required' annotation.
}
return true;
}
@ -18354,7 +18371,13 @@ intptr_t Type::ComputeHash() const {
ASSERT(IsFinalized());
uint32_t result = 1;
result = CombineHashes(result, type_class_id());
result = CombineHashes(result, static_cast<uint32_t>(nullability()));
// A legacy type should have the same hash as its non-nullable version to be
// consistent with the definition of type equality in Dart code.
Nullability type_nullability = nullability();
if (type_nullability == Nullability::kLegacy) {
type_nullability = Nullability::kNonNullable;
}
result = CombineHashes(result, static_cast<uint32_t>(type_nullability));
result = CombineHashes(result, TypeArguments::Handle(arguments()).Hash());
if (IsFunctionType()) {
const Function& sig_fun = Function::Handle(signature());
@ -18469,7 +18492,9 @@ bool TypeRef::IsInstantiated(Genericity genericity,
ref_type.IsInstantiated(genericity, num_free_fun_type_params, trail);
}
bool TypeRef::IsEquivalent(const Instance& other, TrailPtr trail) const {
bool TypeRef::IsEquivalent(const Instance& other,
bool syntactically,
TrailPtr trail) const {
if (raw() == other.raw()) {
return true;
}
@ -18480,7 +18505,8 @@ bool TypeRef::IsEquivalent(const Instance& other, TrailPtr trail) const {
return true;
}
const AbstractType& ref_type = AbstractType::Handle(type());
return !ref_type.IsNull() && ref_type.IsEquivalent(other, trail);
return !ref_type.IsNull() &&
ref_type.IsEquivalent(other, syntactically, trail);
}
RawTypeRef* TypeRef::InstantiateFrom(
@ -18644,7 +18670,9 @@ bool TypeParameter::IsInstantiated(Genericity genericity,
return (genericity == kCurrentClass) || (index() >= num_free_fun_type_params);
}
bool TypeParameter::IsEquivalent(const Instance& other, TrailPtr trail) const {
bool TypeParameter::IsEquivalent(const Instance& other,
bool syntactically,
TrailPtr trail) const {
if (raw() == other.raw()) {
return true;
}
@ -18653,7 +18681,7 @@ bool TypeParameter::IsEquivalent(const Instance& other, TrailPtr trail) const {
const AbstractType& other_ref_type =
AbstractType::Handle(TypeRef::Cast(other).type());
ASSERT(!other_ref_type.IsTypeRef());
return IsEquivalent(other_ref_type, trail);
return IsEquivalent(other_ref_type, syntactically, trail);
}
if (!other.IsTypeParameter()) {
return false;
@ -18666,7 +18694,17 @@ bool TypeParameter::IsEquivalent(const Instance& other, TrailPtr trail) const {
if (parameterized_function() != other_type_param.parameterized_function()) {
return false;
}
if (nullability() != other_type_param.nullability()) {
Nullability this_type_param_nullability = nullability();
Nullability other_type_param_nullability = other_type_param.nullability();
if (syntactically) {
if (this_type_param_nullability == Nullability::kLegacy) {
this_type_param_nullability = Nullability::kNonNullable;
}
if (other_type_param_nullability == Nullability::kLegacy) {
other_type_param_nullability = Nullability::kNonNullable;
}
}
if (this_type_param_nullability != other_type_param_nullability) {
return false;
}
if (IsFinalized() == other_type_param.IsFinalized()) {
@ -18792,7 +18830,13 @@ intptr_t TypeParameter::ComputeHash() const {
// No need to include the hash of the bound, since the type parameter is fully
// identified by its class and index.
result = CombineHashes(result, index());
result = CombineHashes(result, static_cast<uint32_t>(nullability()));
// A legacy type should have the same hash as its non-nullable version to be
// consistent with the definition of type equality in Dart code.
Nullability type_param_nullability = nullability();
if (type_param_nullability == Nullability::kLegacy) {
type_param_nullability = Nullability::kNonNullable;
}
result = CombineHashes(result, static_cast<uint32_t>(type_param_nullability));
result = FinalizeHash(result, kHashBits);
SetHash(result);
return result;

View file

@ -6763,15 +6763,20 @@ class TypeArguments : public Instance {
// Check if the vectors are equal (they may be null).
bool Equals(const TypeArguments& other) const {
return IsSubvectorEquivalent(other, 0, IsNull() ? 0 : Length());
return IsSubvectorEquivalent(other, 0, IsNull() ? 0 : Length(),
/* syntactically = */ false);
}
bool IsEquivalent(const TypeArguments& other, TrailPtr trail = NULL) const {
return IsSubvectorEquivalent(other, 0, IsNull() ? 0 : Length(), trail);
bool IsEquivalent(const TypeArguments& other,
bool syntactically,
TrailPtr trail = NULL) const {
return IsSubvectorEquivalent(other, 0, IsNull() ? 0 : Length(),
syntactically, trail);
}
bool IsSubvectorEquivalent(const TypeArguments& other,
intptr_t from_index,
intptr_t len,
bool syntactically,
TrailPtr trail = NULL) const;
// Check if the vector is instantiated (it must not be null).
@ -6943,9 +6948,11 @@ class AbstractType : public Instance {
}
virtual uint32_t CanonicalizeHash() const { return Hash(); }
virtual bool Equals(const Instance& other) const {
return IsEquivalent(other);
return IsEquivalent(other, /* syntactically = */ false);
}
virtual bool IsEquivalent(const Instance& other, TrailPtr trail = NULL) const;
virtual bool IsEquivalent(const Instance& other,
bool syntactically,
TrailPtr trail = NULL) const;
virtual bool IsRecursive() const;
// Check if this type represents a function type.
@ -7179,7 +7186,9 @@ class Type : public AbstractType {
virtual bool IsInstantiated(Genericity genericity = kAny,
intptr_t num_free_fun_type_params = kAllFree,
TrailPtr trail = NULL) const;
virtual bool IsEquivalent(const Instance& other, TrailPtr trail = NULL) const;
virtual bool IsEquivalent(const Instance& other,
bool syntactically,
TrailPtr trail = NULL) const;
virtual bool IsRecursive() const;
// If signature is not null, this type represents a function type. Note that
// the signature fully represents the type and type arguments can be ignored.
@ -7334,7 +7343,9 @@ class TypeRef : public AbstractType {
virtual bool IsInstantiated(Genericity genericity = kAny,
intptr_t num_free_fun_type_params = kAllFree,
TrailPtr trail = NULL) const;
virtual bool IsEquivalent(const Instance& other, TrailPtr trail = NULL) const;
virtual bool IsEquivalent(const Instance& other,
bool syntactically,
TrailPtr trail = NULL) const;
virtual bool IsRecursive() const { return true; }
virtual bool IsFunctionType() const {
const AbstractType& ref_type = AbstractType::Handle(type());
@ -7417,7 +7428,9 @@ class TypeParameter : public AbstractType {
virtual bool IsInstantiated(Genericity genericity = kAny,
intptr_t num_free_fun_type_params = kAllFree,
TrailPtr trail = NULL) const;
virtual bool IsEquivalent(const Instance& other, TrailPtr trail = NULL) const;
virtual bool IsEquivalent(const Instance& other,
bool syntactically,
TrailPtr trail = NULL) const;
virtual bool IsRecursive() const { return false; }
virtual RawAbstractType* InstantiateFrom(
NNBDMode mode,

View file

@ -95,30 +95,29 @@ const char* TypeTestingStubNamer::AssemblerSafeName(char* cname) {
RawCode* TypeTestingStubGenerator::DefaultCodeForType(
const AbstractType& type,
bool lazy_specialize /* = true */) {
if (type.IsTypeRef()) {
return StubCode::TypeRefTypeTest().raw();
}
const intptr_t cid = type.type_class_id();
// During bootstrapping we have no access to stubs yet, so we'll just return
// `null` and patch these later in `Object::FinishInit()`.
if (!StubCode::HasBeenInitialized()) {
ASSERT(type.IsType());
const intptr_t cid = Type::Cast(type).type_class_id();
ASSERT(cid == kDynamicCid || cid == kVoidCid | cid == kNeverCid);
return Code::null();
}
// TODO(regis): Revisit when type checking mode is not kLegacy anymore.
if (type.raw() == Type::ObjectType() || type.raw() == Type::DynamicType() ||
type.raw() == Type::VoidType()) {
if (cid == kDynamicCid || cid == kVoidCid || cid == kInstanceCid) {
return StubCode::TopTypeTypeTest().raw();
}
if (type.raw() == Type::NeverType()) {
if (cid == kNeverCid) {
// TODO(regis): Revisit.
return StubCode::StubCode::DefaultTypeTest().raw();
}
if (type.IsTypeRef()) {
return StubCode::TypeRefTypeTest().raw();
}
if (type.IsType() || type.IsTypeParameter()) {
const bool should_specialize = !FLAG_precompiled_mode && lazy_specialize;
return should_specialize ? StubCode::LazySpecializeTypeTest().raw()
@ -151,7 +150,8 @@ RawCode* TypeTestingStubGenerator::OptimizedCodeForType(
return StubCode::TypeRefTypeTest().raw();
}
if (type.raw() == Type::ObjectType() || type.raw() == Type::DynamicType()) {
const intptr_t cid = type.type_class_id();
if (cid == kDynamicCid || cid == kVoidCid || cid == kInstanceCid) {
return StubCode::TopTypeTypeTest().raw();
}
@ -245,11 +245,10 @@ void TypeTestingStubGenerator::BuildOptimizedTypeTestStubFastCases(
Register instance_reg,
Register class_id_reg) {
// These are handled via the TopTypeTypeTestStub!
ASSERT(
!(type.raw() == Type::ObjectType() || type.raw() == Type::DynamicType()));
ASSERT(!(type.IsDynamicType() || type.IsVoidType() || type.IsObjectType()));
// Fast case for 'int'.
if (type.raw() == Type::IntType()) {
if (type.IsIntType()) {
compiler::Label non_smi_value;
__ BranchIfNotSmi(instance_reg, &non_smi_value);
__ Ret();
@ -411,8 +410,8 @@ void TypeTestingStubGenerator::BuildOptimizedTypeArgumentValueCheck(
const Register function_type_args_reg,
const Register own_type_arg_reg,
compiler::Label* check_failed) {
if (type_arg.raw() != Type::ObjectType() &&
type_arg.raw() != Type::DynamicType()) {
const intptr_t cid = type_arg.type_class_id();
if (!(cid == kDynamicCid || cid == kVoidCid || cid == kInstanceCid)) {
// TODO(kustermann): Even though it should be safe to use TMP here, we
// should avoid using TMP outside the assembler. Try to find a free
// register to use here!

View file

@ -22,6 +22,9 @@ class _Type extends _AbstractType {
@pragma("vm:exact-result-type", "dart:core#_Smi")
int get hashCode native "Type_getHashCode";
@pragma("vm:exact-result-type", bool)
bool operator ==(other) native "Type_equality";
}
// Equivalent of RawTypeRef.

View file

@ -22,6 +22,9 @@ class _Type extends _AbstractType {
@pragma("vm:exact-result-type", "dart:core#_Smi")
int get hashCode native "Type_getHashCode";
@pragma("vm:exact-result-type", bool)
bool operator ==(other) native "Type_equality";
}
// Equivalent of RawTypeRef.

View file

@ -35,5 +35,5 @@ MINOR 8
PATCH 0
PRERELEASE 0
PRERELEASE_PATCH 0
ABI_VERSION 24
OLDEST_SUPPORTED_ABI_VERSION 23
ABI_VERSION 25
OLDEST_SUPPORTED_ABI_VERSION 25