Skip to content

Commit

Permalink
[vm] Hide internal implementation List types and expose them as List
Browse files Browse the repository at this point in the history
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 #46893
Issue #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>
  • Loading branch information
alexmarkov authored and commit-bot@chromium.org committed Aug 13, 2021
1 parent 7760548 commit 824bec5
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 40 deletions.
14 changes: 9 additions & 5 deletions runtime/lib/object.cc
Expand Up @@ -101,14 +101,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) {
Expand Down
32 changes: 32 additions & 0 deletions runtime/tests/vm/dart/have_same_runtime_type_test.dart
@@ -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>[]);
}
34 changes: 34 additions & 0 deletions runtime/tests/vm/dart_2/have_same_runtime_type_test.dart
@@ -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>[]);
}
18 changes: 13 additions & 5 deletions runtime/vm/class_id.h
Expand Up @@ -78,7 +78,6 @@ typedef uint16_t ClassIdTagType;
V(Mint) \
V(Double) \
V(Bool) \
V(GrowableObjectArray) \
V(Float32x4) \
V(Int32x4) \
V(Float64x2) \
Expand Down Expand Up @@ -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) \
Expand Down Expand Up @@ -182,6 +185,7 @@ typedef uint16_t ClassIdTagType;
V(LinkedHashMap) \
V(LinkedHashSet) \
V(Array) \
V(GrowableObjectArray) \
V(String)

#define CLASS_LIST_NO_OBJECT(V) \
Expand Down Expand Up @@ -329,11 +333,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));
}

Expand Down
26 changes: 23 additions & 3 deletions runtime/vm/compiler/asm_intrinsifier_arm.cc
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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.
Expand Down
26 changes: 23 additions & 3 deletions runtime/vm/compiler/asm_intrinsifier_arm64.cc
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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.
Expand Down
37 changes: 30 additions & 7 deletions runtime/vm/compiler/asm_intrinsifier_ia32.cc
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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));
Expand All @@ -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);
Expand All @@ -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.
Expand Down

0 comments on commit 824bec5

Please sign in to comment.