Reland "[vm] Hide internal implementation List types and expose them as List"

This is a reland of 824bec596f

Original change's description:
> [vm] Hide internal implementation List types and expose them as List
>
> When taking a type of an instance with x.runtimeType we can map
> internal classes _List, _ImmutableList and _GrowableList to a
> user-visible List class. This is similar to what we do for
> implementation classes of int, String and Type.
> After that, result of x.runtimeType for built-in lists would be
> compatible with List<T> type literals.
>
> Also, both intrinsic and native implementations of _haveSameRuntimeType
> are updated to agree with new semantic of runtimeType.
>
> TEST=co19/LanguageFeatures/Constructor-tear-offs/type_literal_A01_t01
> TEST=runtime/tests/vm/dart/have_same_runtime_type_test
>
> Fixes https://github.com/dart-lang/sdk/issues/46893
> Issue https://github.com/dart-lang/sdk/issues/46231
>
> Change-Id: Ie24a9f527f66a06118427b7a09e49c03dff93d8e
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/210066
> Commit-Queue: Alexander Markov <alexmarkov@google.com>
> Reviewed-by: Tess Strickland <sstrickl@google.com>

TEST=co19/LanguageFeatures/Constructor-tear-offs/type_literal_A01_t01
TEST=runtime/tests/vm/dart/have_same_runtime_type_test
TEST=lib/mirrors/regress_b196606044_test
Fixes https://github.com/dart-lang/sdk/issues/46893
Issue https://github.com/dart-lang/sdk/issues/46231

Change-Id: I79b587540338808bd73a6554f00a5eed042f4c26
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/210201
Commit-Queue: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Tess Strickland <sstrickl@google.com>
This commit is contained in:
Alexander Markov 2021-08-16 22:52:21 +00:00 committed by commit-bot@chromium.org
parent 6fdf8f1c78
commit cfb057ddca
14 changed files with 261 additions and 39 deletions

View file

@ -88,7 +88,18 @@ DEFINE_NATIVE_ENTRY(Object_runtimeType, 0, 1) {
return Type::Double();
} else if (instance.IsType() || instance.IsFunctionType()) {
return Type::DartTypeType();
} else if (IsArrayClassId(instance.GetClassId())) {
const auto& cls = Class::Handle(
zone, thread->isolate_group()->object_store()->list_class());
const auto& type_arguments =
TypeArguments::Handle(zone, instance.GetTypeArguments());
const auto& type = Type::Handle(
zone,
Type::New(cls, type_arguments, Nullability::kNonNullable, Heap::kNew));
type.SetIsFinalized();
return type.Canonicalize(thread, nullptr);
}
return instance.GetType(Heap::kNew);
}
@ -101,14 +112,18 @@ static bool HaveSameRuntimeTypeHelper(Zone* zone,
if (left_cid != right_cid) {
if (IsIntegerClassId(left_cid)) {
return IsIntegerClassId(right_cid);
}
if (IsStringClassId(left_cid)) {
} else if (IsStringClassId(left_cid)) {
return IsStringClassId(right_cid);
}
if (IsTypeClassId(left_cid)) {
} else if (IsTypeClassId(left_cid)) {
return IsTypeClassId(right_cid);
} else if (IsArrayClassId(left_cid)) {
if (!IsArrayClassId(right_cid)) {
return false;
}
// Still need to check type arguments.
} else {
return false;
}
return false;
}
if (left_cid == kClosureCid) {

View file

@ -0,0 +1,32 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Test for corner cases of 'a.runtimeType == b.runtimeType' pattern
// which is recognized and optimized in AOT mode.
import "package:expect/expect.dart";
@pragma('vm:never-inline')
Object getType(Object obj) => obj.runtimeType;
@pragma('vm:never-inline')
void test(bool expected, Object a, Object b) {
bool result1 = getType(a) == getType(b);
bool result2 = a.runtimeType == b.runtimeType;
Expect.equals(expected, result1);
Expect.equals(expected, result2);
}
typedef Func = void Function();
void main() {
test(true, 0x7fffffffffffffff, int.parse('42'));
test(true, 'hi', String.fromCharCode(1114111));
test(false, 'hi', 1);
test(true, List, Func);
test(true, <int>[1], const <int>[2]);
test(true, const <String>[], List<String>.filled(1, ''));
test(true, <String>[]..add('hi'), List<String>.filled(2, ''));
test(false, <int>[], <String>[]);
}

View file

@ -0,0 +1,34 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Test for corner cases of 'a.runtimeType == b.runtimeType' pattern
// which is recognized and optimized in AOT mode.
// @dart = 2.9
import "package:expect/expect.dart";
@pragma('vm:never-inline')
Object getType(Object obj) => obj.runtimeType;
@pragma('vm:never-inline')
void test(bool expected, Object a, Object b) {
bool result1 = getType(a) == getType(b);
bool result2 = a.runtimeType == b.runtimeType;
Expect.equals(expected, result1);
Expect.equals(expected, result2);
}
typedef Func = void Function();
void main() {
test(true, 0x7fffffffffffffff, int.parse('42'));
test(true, 'hi', String.fromCharCode(1114111));
test(false, 'hi', 1);
test(true, List, Func);
test(true, <int>[1], const <int>[2]);
test(true, const <String>[], List<String>.filled(1, ''));
test(true, <String>[]..add('hi'), List<String>.filled(2, ''));
test(false, <int>[], <String>[]);
}

View file

@ -78,7 +78,6 @@ typedef uint16_t ClassIdTagType;
V(Mint) \
V(Double) \
V(Bool) \
V(GrowableObjectArray) \
V(Float32x4) \
V(Int32x4) \
V(Float64x2) \
@ -108,10 +107,14 @@ typedef uint16_t ClassIdTagType;
// TODO(http://dartbug.com/45908): Add ImmutableLinkedHashSet.
#define CLASS_LIST_SETS(V) V(LinkedHashSet)
#define CLASS_LIST_ARRAYS(V) \
#define CLASS_LIST_FIXED_LENGTH_ARRAYS(V) \
V(Array) \
V(ImmutableArray)
#define CLASS_LIST_ARRAYS(V) \
CLASS_LIST_FIXED_LENGTH_ARRAYS(V) \
V(GrowableObjectArray)
#define CLASS_LIST_STRINGS(V) \
V(String) \
V(OneByteString) \
@ -182,6 +185,7 @@ typedef uint16_t ClassIdTagType;
V(LinkedHashMap) \
V(LinkedHashSet) \
V(Array) \
V(GrowableObjectArray) \
V(String)
#define CLASS_LIST_NO_OBJECT(V) \
@ -327,11 +331,15 @@ inline bool IsExternalStringClassId(intptr_t index) {
index == kExternalTwoByteStringCid);
}
inline bool IsArrayClassId(intptr_t index) {
COMPILE_ASSERT(kImmutableArrayCid == kArrayCid + 1);
COMPILE_ASSERT(kGrowableObjectArrayCid == kArrayCid + 2);
return (index >= kArrayCid && index <= kGrowableObjectArrayCid);
}
inline bool IsBuiltinListClassId(intptr_t index) {
// Make sure this function is updated when new builtin List types are added.
COMPILE_ASSERT(kImmutableArrayCid == kArrayCid + 1);
return ((index >= kArrayCid && index <= kImmutableArrayCid) ||
(index == kGrowableObjectArrayCid) || IsTypedDataBaseClassId(index) ||
return (IsArrayClassId(index) || IsTypedDataBaseClassId(index) ||
(index == kByteBufferCid));
}

View file

@ -1198,6 +1198,14 @@ static void JumpIfNotString(Assembler* assembler,
kIfNotInRange, target);
}
static void JumpIfNotList(Assembler* assembler,
Register cid,
Register tmp,
Label* target) {
RangeCheck(assembler, cid, tmp, kArrayCid, kGrowableObjectArrayCid,
kIfNotInRange, target);
}
static void JumpIfType(Assembler* assembler,
Register cid,
Register tmp,
@ -1284,7 +1292,7 @@ static void EquivalentClassIds(Assembler* assembler,
Register scratch,
bool testing_instance_cids) {
Label different_cids, equal_cids_but_generic, not_integer,
not_integer_or_string;
not_integer_or_string, not_integer_or_string_or_list;
// Check if left hand side is a closure. Closures are handled in the runtime.
__ CompareImmediate(cid1, kClosureCid);
@ -1310,7 +1318,8 @@ static void EquivalentClassIds(Assembler* assembler,
__ b(equal);
// Class ids are different. Check if we are comparing two string types (with
// different representations) or two integer types or two type types.
// different representations), two integer types, two list types or two type
// types.
__ Bind(&different_cids);
__ CompareImmediate(cid1, kNumPredefinedCids);
__ b(not_equal, HI);
@ -1335,9 +1344,20 @@ static void EquivalentClassIds(Assembler* assembler,
if (testing_instance_cids) {
__ Bind(&not_integer_or_string);
// Check if both are List types.
JumpIfNotList(assembler, cid1, scratch, &not_integer_or_string_or_list);
// First type is a List. Check if the second is a List too.
JumpIfNotList(assembler, cid2, scratch, not_equal);
ASSERT(compiler::target::Array::type_arguments_offset() ==
compiler::target::GrowableObjectArray::type_arguments_offset());
__ LoadImmediate(scratch, compiler::target::Array::type_arguments_offset());
__ b(&equal_cids_but_generic);
__ Bind(&not_integer_or_string_or_list);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
// or integer or List or Type.
JumpIfNotType(assembler, cid1, scratch, not_equal);
// First type is a Type. Check if the second is a Type too.

View file

@ -1342,6 +1342,14 @@ static void JumpIfNotString(Assembler* assembler,
kIfNotInRange, target);
}
static void JumpIfNotList(Assembler* assembler,
Register cid,
Register tmp,
Label* target) {
RangeCheck(assembler, cid, tmp, kArrayCid, kGrowableObjectArrayCid,
kIfNotInRange, target);
}
static void JumpIfType(Assembler* assembler,
Register cid,
Register tmp,
@ -1432,7 +1440,7 @@ static void EquivalentClassIds(Assembler* assembler,
Register scratch,
bool testing_instance_cids) {
Label different_cids, equal_cids_but_generic, not_integer,
not_integer_or_string;
not_integer_or_string, not_integer_or_string_or_list;
// Check if left hand side is a closure. Closures are handled in the runtime.
__ CompareImmediate(cid1, kClosureCid);
@ -1458,7 +1466,8 @@ static void EquivalentClassIds(Assembler* assembler,
__ b(equal);
// Class ids are different. Check if we are comparing two string types (with
// different representations) or two integer types or two type types.
// different representations), two integer types, two list types or two type
// types.
__ Bind(&different_cids);
__ CompareImmediate(cid1, kNumPredefinedCids);
__ b(not_equal, HI);
@ -1483,9 +1492,20 @@ static void EquivalentClassIds(Assembler* assembler,
if (testing_instance_cids) {
__ Bind(&not_integer_or_string);
// Check if both are List types.
JumpIfNotList(assembler, cid1, scratch, &not_integer_or_string_or_list);
// First type is a List. Check if the second is a List too.
JumpIfNotList(assembler, cid2, scratch, not_equal);
ASSERT(compiler::target::Array::type_arguments_offset() ==
compiler::target::GrowableObjectArray::type_arguments_offset());
__ LoadImmediate(scratch, compiler::target::Array::type_arguments_offset());
__ b(&equal_cids_but_generic);
__ Bind(&not_integer_or_string_or_list);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
// or integer or List or Type.
JumpIfNotType(assembler, cid1, scratch, not_equal);
// First type is a Type. Check if the second is a Type too.

View file

@ -1281,6 +1281,11 @@ static void JumpIfNotString(Assembler* assembler, Register cid, Label* target) {
kIfNotInRange, target);
}
static void JumpIfNotList(Assembler* assembler, Register cid, Label* target) {
RangeCheck(assembler, cid, kArrayCid, kGrowableObjectArrayCid, kIfNotInRange,
target);
}
static void JumpIfType(Assembler* assembler, Register cid, Label* target) {
RangeCheck(assembler, cid, kTypeCid, kFunctionTypeCid, kIfInRange, target);
}
@ -1370,7 +1375,7 @@ static void EquivalentClassIds(Assembler* assembler,
Register scratch,
bool testing_instance_cids) {
Label different_cids, equal_cids_but_generic, not_integer,
not_integer_or_string;
not_integer_or_string, not_integer_or_string_or_list;
// Check if left hand side is a closure. Closures are handled in the runtime.
__ cmpl(cid1, Immediate(kClosureCid));
@ -1392,11 +1397,12 @@ static void EquivalentClassIds(Assembler* assembler,
scratch,
target::Class::host_type_arguments_field_offset_in_words_offset()));
__ cmpl(scratch, Immediate(target::Class::kNoTypeArguments));
__ j(NOT_EQUAL, &equal_cids_but_generic, Assembler::kNearJump);
__ j(NOT_EQUAL, &equal_cids_but_generic);
__ jmp(equal);
// Class ids are different. Check if we are comparing two string types (with
// different representations) or two integer types or two type types.
// different representations), two integer types, two list types or two type
// types.
__ Bind(&different_cids);
__ cmpl(cid1, Immediate(kNumPredefinedCids));
__ j(ABOVE_EQUAL, not_equal);
@ -1406,25 +1412,42 @@ static void EquivalentClassIds(Assembler* assembler,
JumpIfNotInteger(assembler, scratch, &not_integer);
// First type is an integer. Check if the second is an integer too.
JumpIfInteger(assembler, cid2, equal);
__ movl(scratch, cid2);
JumpIfInteger(assembler, scratch, equal);
// Integer types are only equivalent to other integer types.
__ jmp(not_equal);
__ Bind(&not_integer);
// Check if both are String types.
JumpIfNotString(assembler, cid1,
__ movl(scratch, cid1);
JumpIfNotString(assembler, scratch,
testing_instance_cids ? &not_integer_or_string : not_equal);
// First type is a String. Check if the second is a String too.
JumpIfString(assembler, cid2, equal);
__ movl(scratch, cid2);
JumpIfString(assembler, scratch, equal);
// String types are only equivalent to other String types.
__ jmp(not_equal);
if (testing_instance_cids) {
__ Bind(&not_integer_or_string);
// Check if both are List types.
__ movl(scratch, cid1);
JumpIfNotList(assembler, scratch, &not_integer_or_string_or_list);
// First type is a List. Check if the second is a List too.
__ movl(scratch, cid2);
JumpIfNotList(assembler, scratch, not_equal);
ASSERT(compiler::target::Array::type_arguments_offset() ==
compiler::target::GrowableObjectArray::type_arguments_offset());
__ movl(scratch,
Immediate(compiler::target::Array::type_arguments_offset()));
__ jmp(&equal_cids_but_generic, Assembler::kNearJump);
__ Bind(&not_integer_or_string_or_list);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
// or integer or List or Type.
JumpIfNotType(assembler, cid1, not_equal);
// First type is a Type. Check if the second is a Type too.

View file

@ -1184,6 +1184,11 @@ static void JumpIfNotString(Assembler* assembler, Register cid, Label* target) {
kIfNotInRange, target);
}
static void JumpIfNotList(Assembler* assembler, Register cid, Label* target) {
RangeCheck(assembler, cid, kArrayCid, kGrowableObjectArrayCid, kIfNotInRange,
target);
}
static void JumpIfType(Assembler* assembler, Register cid, Label* target) {
RangeCheck(assembler, cid, kTypeCid, kFunctionTypeCid, kIfInRange, target);
}
@ -1275,7 +1280,7 @@ static void EquivalentClassIds(Assembler* assembler,
Register scratch,
bool testing_instance_cids) {
Label different_cids, equal_cids_but_generic, not_integer,
not_integer_or_string;
not_integer_or_string, not_integer_or_string_or_list;
// Check if left hand side is a closure. Closures are handled in the runtime.
__ cmpq(cid1, Immediate(kClosureCid));
@ -1297,11 +1302,12 @@ static void EquivalentClassIds(Assembler* assembler,
scratch,
target::Class::host_type_arguments_field_offset_in_words_offset()));
__ cmpl(scratch, Immediate(target::Class::kNoTypeArguments));
__ j(NOT_EQUAL, &equal_cids_but_generic, Assembler::kNearJump);
__ j(NOT_EQUAL, &equal_cids_but_generic);
__ jmp(equal);
// Class ids are different. Check if we are comparing two string types (with
// different representations) or two integer types or two type types.
// different representations), two integer types, two list types or two type
// types.
__ Bind(&different_cids);
__ cmpq(cid1, Immediate(kNumPredefinedCids));
__ j(ABOVE_EQUAL, not_equal);
@ -1311,25 +1317,42 @@ static void EquivalentClassIds(Assembler* assembler,
JumpIfNotInteger(assembler, scratch, &not_integer);
// First type is an integer. Check if the second is an integer too.
JumpIfInteger(assembler, cid2, equal);
__ movq(scratch, cid2);
JumpIfInteger(assembler, scratch, equal);
// Integer types are only equivalent to other integer types.
__ jmp(not_equal);
__ Bind(&not_integer);
// Check if both are String types.
JumpIfNotString(assembler, cid1,
__ movq(scratch, cid1);
JumpIfNotString(assembler, scratch,
testing_instance_cids ? &not_integer_or_string : not_equal);
// First type is a String. Check if the second is a String too.
JumpIfString(assembler, cid2, equal);
__ movq(scratch, cid2);
JumpIfString(assembler, scratch, equal);
// String types are only equivalent to other String types.
__ jmp(not_equal);
if (testing_instance_cids) {
__ Bind(&not_integer_or_string);
// Check if both are List types.
__ movq(scratch, cid1);
JumpIfNotList(assembler, scratch, &not_integer_or_string_or_list);
// First type is a List. Check if the second is a List too.
__ movq(scratch, cid2);
JumpIfNotList(assembler, scratch, not_equal);
ASSERT(compiler::target::Array::type_arguments_offset() ==
compiler::target::GrowableObjectArray::type_arguments_offset());
__ movq(scratch,
Immediate(compiler::target::Array::type_arguments_offset()));
__ jmp(&equal_cids_but_generic, Assembler::kNearJump);
__ Bind(&not_integer_or_string_or_list);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
// or integer or List or Type.
JumpIfNotType(assembler, cid1, not_equal);
// First type is a Type. Check if the second is a Type too.

View file

@ -1531,13 +1531,11 @@ void FlowGraphCompiler::GenerateListTypeCheck(
Register class_id_reg,
compiler::Label* is_instance_lbl) {
assembler()->Comment("ListTypeCheck");
compiler::Label unknown;
GrowableArray<intptr_t> args;
args.Add(kArrayCid);
args.Add(kGrowableObjectArrayCid);
args.Add(kImmutableArrayCid);
CheckClassIds(class_id_reg, args, is_instance_lbl, &unknown);
assembler()->Bind(&unknown);
COMPILE_ASSERT((kImmutableArrayCid == kArrayCid + 1) &&
(kGrowableObjectArrayCid == kArrayCid + 2));
CidRangeVector ranges;
ranges.Add({kArrayCid, kGrowableObjectArrayCid});
GenerateCidRangesCheck(assembler(), class_id_reg, ranges, is_instance_lbl);
}
void FlowGraphCompiler::EmitComment(Instruction* instr) {

View file

@ -607,6 +607,7 @@ void Object::InitVtables() {
builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \
}
CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(INIT_VTABLE)
INIT_VTABLE(GrowableObjectArray)
#undef INIT_VTABLE
#define INIT_VTABLE(clazz) \
@ -630,7 +631,7 @@ void Object::InitVtables() {
Array fake_handle; \
builtin_vtables_[k##clazz##Cid] = fake_handle.vtable(); \
}
CLASS_LIST_ARRAYS(INIT_VTABLE)
CLASS_LIST_FIXED_LENGTH_ARRAYS(INIT_VTABLE)
#undef INIT_VTABLE
#define INIT_VTABLE(clazz) \

View file

@ -1009,6 +1009,7 @@ class ObjectCopy : public Base {
CLASS_LIST_NO_OBJECT_NOR_STRING_NOR_ARRAY_NOR_MAP(COPY_TO)
COPY_TO(Array)
COPY_TO(GrowableObjectArray)
COPY_TO(LinkedHashMap)
COPY_TO(LinkedHashSet)
#undef COPY_TO

View file

@ -5208,7 +5208,6 @@ static void GetDefaultClassesAliases(Thread* thread, JSONStream* js) {
{
JSONArray internals(&map, "List");
CLASS_LIST_ARRAYS(DEFINE_ADD_VALUE_F_CID)
DEFINE_ADD_VALUE_F_CID(GrowableObjectArray)
DEFINE_ADD_VALUE_F_CID(ByteBuffer)
}
{

View file

@ -0,0 +1,23 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Regression test for b/196606044.
//
// Verifies that instance.type.instanceMembers contains 'isNotEmpty'
// member for a List literal.
import 'package:expect/expect.dart';
import 'dart:mirrors';
dynamic object = <int>[1, 2, 3];
String name = 'isNotEmpty';
main() {
var instance = reflect(object);
var member = instance.type.instanceMembers[new Symbol(name)];
Expect.isNotNull(member);
var invocation = instance.getField(member!.simpleName);
Expect.isNotNull(invocation);
Expect.equals(true, invocation.reflectee);
}

View file

@ -0,0 +1,25 @@
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart = 2.9
// Regression test for b/196606044.
//
// Verifies that instance.type.instanceMembers contains 'isNotEmpty'
// member for a List literal.
import 'package:expect/expect.dart';
import 'dart:mirrors';
dynamic object = <int>[1, 2, 3];
String name = 'isNotEmpty';
main() {
var instance = reflect(object);
var member = instance.type.instanceMembers[new Symbol(name)];
Expect.isNotNull(member);
var invocation = instance.getField(member.simpleName);
Expect.isNotNull(invocation);
Expect.equals(true, invocation.reflectee);
}