[vm] Support record types in special cases of type testing stubs for generic types

TEST=language/records/simple/type_checks2_test

Issue: https://github.com/dart-lang/sdk/issues/49719
Change-Id: I9117331d8e985675a8adba1aaa4d50a6b17758f4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/261182
Reviewed-by: Tess Strickland <sstrickl@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov 2022-09-28 19:01:11 +00:00 committed by Commit Queue
parent 72f574507e
commit 9c3e52aaf3
3 changed files with 213 additions and 75 deletions

View file

@ -972,44 +972,24 @@ bool TypeTestingStubGenerator::BuildLoadInstanceTypeArguments(
return !type_argument_checks.is_empty();
}
// Unwraps TypeRefs, jumping to the appropriate label for the unwrapped type
// if that label is not nullptr and otherwise falling through.
// Unwraps TypeRef in [type_reg] and loads class id of the unwrapped type to
// [class_id_reg].
//
// [type_reg] must contain an AbstractTypePtr, and [scratch] must be distinct
// from [type_reg]. Clobbers [type_reg] with the unwrapped type.
static void UnwrapAbstractType(compiler::Assembler* assembler,
Register type_reg,
Register scratch,
compiler::Label* is_type = nullptr,
compiler::Label* is_function_type = nullptr,
compiler::Label* is_type_parameter = nullptr) {
ASSERT(scratch != type_reg);
compiler::Label cid_checks, fall_through;
// [type_reg] must contain an AbstractType. Unwrapped TypeRef is written
// back to [type_reg]. [class_id_reg] must be distinct from [type_reg].
static void UnwrapTypeRefAndLoadClassId(compiler::Assembler* assembler,
Register type_reg,
Register class_id_reg) {
ASSERT(class_id_reg != type_reg);
compiler::Label done;
// TypeRefs never wrap other TypeRefs, so we only need to unwrap once.
__ LoadClassId(scratch, type_reg);
__ CompareImmediate(scratch, kTypeRefCid);
__ BranchIf(NOT_EQUAL, &cid_checks, compiler::Assembler::kNearJump);
__ LoadClassId(class_id_reg, type_reg);
__ CompareImmediate(class_id_reg, kTypeRefCid);
__ BranchIf(NOT_EQUAL, &done, compiler::Assembler::kNearJump);
__ LoadCompressedFieldFromOffset(type_reg, type_reg,
compiler::target::TypeRef::type_offset());
// Only load the class id of the unwrapped type if it will be checked below.
if (is_type != nullptr || is_function_type != nullptr ||
is_type_parameter != nullptr) {
__ LoadClassId(scratch, type_reg);
}
__ Bind(&cid_checks);
if (is_type != nullptr) {
__ CompareImmediate(scratch, kTypeCid);
__ BranchIf(EQUAL, is_type);
}
if (is_function_type != nullptr) {
__ CompareImmediate(scratch, kFunctionTypeCid);
__ BranchIf(EQUAL, is_function_type);
}
if (is_type_parameter != nullptr) {
__ CompareImmediate(scratch, kTypeParameterCid);
__ BranchIf(EQUAL, is_type_parameter);
}
// TODO(dartbug.com/49719)
__ LoadClassId(class_id_reg, type_reg);
__ Bind(&done);
}
void TypeTestingStubGenerator::BuildOptimizedTypeParameterArgumentValueCheck(
@ -1053,9 +1033,10 @@ void TypeTestingStubGenerator::BuildOptimizedTypeParameterArgumentValueCheck(
__ Comment("Checking instantiated type parameter for possible top types");
compiler::Label check_subtype_type_class_ids;
UnwrapAbstractType(assembler, TTSInternalRegs::kSuperTypeArgumentReg,
TTSInternalRegs::kScratchReg, /*is_type=*/nullptr,
&check_subtype_type_class_ids);
UnwrapTypeRefAndLoadClassId(assembler, TTSInternalRegs::kSuperTypeArgumentReg,
TTSInternalRegs::kScratchReg);
__ CompareImmediate(TTSInternalRegs::kScratchReg, kTypeCid);
__ BranchIf(NOT_EQUAL, &check_subtype_type_class_ids);
__ LoadTypeClassId(TTSInternalRegs::kScratchReg,
TTSInternalRegs::kSuperTypeArgumentReg);
__ CompareImmediate(TTSInternalRegs::kScratchReg, kDynamicCid);
@ -1072,16 +1053,6 @@ void TypeTestingStubGenerator::BuildOptimizedTypeParameterArgumentValueCheck(
TTSInternalRegs::kScratchReg);
__ BranchIf(NOT_EQUAL, &is_subtype);
__ Comment("Checking for legacy or non-nullable instance type argument");
compiler::Label subtype_is_type;
UnwrapAbstractType(assembler, TTSInternalRegs::kSubTypeArgumentReg,
TTSInternalRegs::kScratchReg, &subtype_is_type);
__ CompareAbstractTypeNullabilityWith(
TTSInternalRegs::kSubTypeArgumentReg,
static_cast<int8_t>(Nullability::kNullable),
TTSInternalRegs::kScratchReg);
__ BranchIf(EQUAL, check_failed);
__ Jump(&is_subtype);
__ Bind(&subtype_is_type);
__ CompareAbstractTypeNullabilityWith(
TTSInternalRegs::kSubTypeArgumentReg,
static_cast<int8_t>(Nullability::kNullable),
@ -1095,9 +1066,10 @@ void TypeTestingStubGenerator::BuildOptimizedTypeParameterArgumentValueCheck(
__ Bind(&check_subtype_type_class_ids);
__ Comment("Checking instance type argument for possible bottom types");
// Nothing else to check for non-Types, so fall back to the slow stub.
UnwrapAbstractType(assembler, TTSInternalRegs::kSubTypeArgumentReg,
TTSInternalRegs::kScratchReg, /*is_type=*/nullptr,
check_failed);
UnwrapTypeRefAndLoadClassId(assembler, TTSInternalRegs::kSubTypeArgumentReg,
TTSInternalRegs::kScratchReg);
__ CompareImmediate(TTSInternalRegs::kScratchReg, kTypeCid);
__ BranchIf(NOT_EQUAL, check_failed);
__ LoadTypeClassId(TTSInternalRegs::kScratchReg,
TTSInternalRegs::kSubTypeArgumentReg);
__ CompareImmediate(TTSInternalRegs::kScratchReg, kNeverCid);
@ -1108,16 +1080,6 @@ void TypeTestingStubGenerator::BuildOptimizedTypeParameterArgumentValueCheck(
if (strict_null_safety) {
// Only nullable or legacy types can be a supertype of Null.
__ Comment("Checking for legacy or nullable instantiated type parameter");
compiler::Label supertype_is_type;
UnwrapAbstractType(assembler, TTSInternalRegs::kSuperTypeArgumentReg,
TTSInternalRegs::kScratchReg, &supertype_is_type);
__ CompareAbstractTypeNullabilityWith(
TTSInternalRegs::kSuperTypeArgumentReg,
static_cast<int8_t>(Nullability::kNonNullable),
TTSInternalRegs::kScratchReg);
__ BranchIf(EQUAL, check_failed);
__ Jump(&is_subtype, compiler::Assembler::kNearJump);
__ Bind(&supertype_is_type);
__ CompareAbstractTypeNullabilityWith(
TTSInternalRegs::kSuperTypeArgumentReg,
static_cast<int8_t>(Nullability::kNonNullable),
@ -1153,22 +1115,29 @@ void TypeTestingStubGenerator::BuildOptimizedTypeArgumentValueCheck(
__ Comment("%s", buffer.buffer());
}
compiler::Label is_subtype, check_subtype_cid, sub_is_function_type,
sub_is_type;
compiler::Label is_subtype, sub_is_type;
__ LoadCompressedFieldFromOffset(
TTSInternalRegs::kSubTypeArgumentReg,
TTSInternalRegs::kInstanceTypeArgumentsReg,
compiler::target::TypeArguments::type_at_offset(
type_param_value_offset_i));
__ Bind(&check_subtype_cid);
UnwrapAbstractType(assembler, TTSInternalRegs::kSubTypeArgumentReg,
TTSInternalRegs::kScratchReg, &sub_is_type);
// TODO(dartbug.com/49719)
__ Comment("Checks for FunctionType");
__ EnsureHasClassIdInDEBUG(kFunctionTypeCid,
TTSInternalRegs::kSubTypeArgumentReg,
TTSInternalRegs::kScratchReg);
if (type.IsObjectType() || type.IsDartFunctionType()) {
UnwrapTypeRefAndLoadClassId(assembler, TTSInternalRegs::kSubTypeArgumentReg,
TTSInternalRegs::kScratchReg);
if (type.IsObjectType() || type.IsDartFunctionType() ||
type.IsDartRecordType()) {
__ CompareImmediate(TTSInternalRegs::kScratchReg, kTypeCid);
__ BranchIf(EQUAL, &sub_is_type);
if (type.IsDartFunctionType()) {
__ Comment("Checks for Function type");
__ CompareImmediate(TTSInternalRegs::kScratchReg, kFunctionTypeCid);
__ BranchIf(NOT_EQUAL, check_failed);
} else if (type.IsDartRecordType()) {
__ Comment("Checks for Record type");
__ CompareImmediate(TTSInternalRegs::kScratchReg, kRecordTypeCid);
__ BranchIf(NOT_EQUAL, check_failed);
} else {
__ Comment("Checks for Object type");
}
if (strict_null_safety && type.IsNonNullable()) {
// Nullable types cannot be a subtype of a non-nullable type.
__ CompareAbstractTypeNullabilityWith(
@ -1177,13 +1146,13 @@ void TypeTestingStubGenerator::BuildOptimizedTypeArgumentValueCheck(
TTSInternalRegs::kScratchReg);
__ BranchIf(EQUAL, check_failed);
}
// No further checks needed for non-nullable Object or Function.
// No further checks needed for non-nullable Object, Function or Record.
__ Jump(&is_subtype, compiler::Assembler::kNearJump);
} else {
// _Closure <: Function, and T <: Function for any FunctionType T, but
// T </: _Closure, so we _don't_ want to fall back to cid tests. Instead,
// Don't fall back to cid tests for record and function types. Instead,
// just let the STC/runtime handle any possible false negatives here.
__ Jump(check_failed);
__ CompareImmediate(TTSInternalRegs::kScratchReg, kTypeCid);
__ BranchIf(NOT_EQUAL, check_failed);
}
__ Comment("Checks for Type");

View file

@ -0,0 +1,90 @@
// 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
// VMOptions=--deterministic --optimization_counter_threshold=150
import "package:expect/expect.dart";
class A<T> {
}
class B<T> {
void foo(T x) {}
void bar(A<T> x) {}
}
class C {
void baz(Object? x) {}
}
class D1 implements C {
void baz(covariant A<int> x) {}
}
class D2 implements C {
void baz(covariant A<(int, int)> x) {}
}
B<(num, num)> b1 = (int.parse('1') == 1) ? B<(int, int)>() : B<(num, num)>();
B<({num foo})?> b2 = (int.parse('1') == 1) ? B<({int foo})>() : B<({num foo})?>();
B<(num?, {num? foo})?> b3 = (int.parse('1') == 1) ? B<(int, {int? foo})?>() : B<(num?, {num? foo})?>();
C d1 = (int.parse('1') == 1) ? D1() : C();
C d2 = (int.parse('1') == 1) ? D2() : C();
doTests() {
b1.foo((1, 2));
Expect.throwsTypeError(() => b1.foo((1.5, 2)));
Expect.throwsTypeError(() => b1.foo((1, 2.5)));
b1.bar(A<(int, int x)>());
b1.bar(A<Never>());
Expect.throwsTypeError(() => b1.bar(A<(int, double)>()));
Expect.throwsTypeError(() => b1.bar(A<(num, int)>()));
b2.foo((foo: 10));
Expect.throwsTypeError(() => b2.foo((foo: 10.5)));
if (hasSoundNullSafety) {
Expect.throwsTypeError(() => b2.foo(null));
}
b2.bar(A<({int foo})>());
b2.bar(A<Never>());
Expect.throwsTypeError(() => b2.bar(A<({num foo})>()));
if (hasSoundNullSafety) {
Expect.throwsTypeError(() => b2.bar(A<Null>()));
Expect.throwsTypeError(() => b2.bar(A<({int foo})?>()));
}
b3.foo((20, foo: 30));
b3.foo((20, foo: null));
b3.foo(null);
Expect.throwsTypeError(() => b3.foo((20.5, foo: 30)));
Expect.throwsTypeError(() => b3.foo((20, foo: 30.5)));
if (hasSoundNullSafety) {
Expect.throwsTypeError(() => b3.foo((null, foo: 30)));
}
b3.bar(A<(int, {int foo})>());
b3.bar(A<(int, {int? foo})>());
b3.bar(A<(int, {int? foo})?>());
b3.bar(A<Null>());
b3.bar(A<Never>());
Expect.throwsTypeError(() => b3.bar(A<(int, {double foo})>()));
Expect.throwsTypeError(() => b3.bar(A<(num, {int foo})>()));
if (hasSoundNullSafety) {
Expect.throwsTypeError(() => b3.bar(A<(int?, {int? foo})?>()));
}
d1.baz(A<int>());
Expect.throwsTypeError(() => d1.baz(A<(int, int)>()));
d2.baz(A<(int, int)>());
Expect.throwsTypeError(() => d2.baz(A<int>()));
}
main() {
for (int i = 0; i < 200; ++i) {
doTests();
}
}

View file

@ -0,0 +1,79 @@
// 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
// VMOptions=--deterministic --optimization_counter_threshold=150
import "package:expect/expect.dart";
class A<T> {
}
class B<T> {
void foo(T x) {}
void bar(A<T> x) {}
}
B<(num, num)> b1 = (int.parse('1') == 1) ? B<(int, int)>() : B<(num, num)>();
B<({num foo})?> b2 = (int.parse('1') == 1) ? B<({int foo})>() : B<({num foo})?>();
B<(num?, {num? foo})?> b3 = (int.parse('1') == 1) ? B<(int, {int? foo})?>() : B<(num?, {num? foo})?>();
doTests() {
b1 as B<(int, int)>;
b1 as B<(int, num)>;
b1 as B<(int, int?)>;
b1 as B<(int, int)>?;
b1 as B<Object>;
b1 as B<Record>;
Expect.throwsTypeError(() => b1 as B<(int, double)>);
Expect.throwsTypeError(() => b1 as B<(double, int)>);
Expect.throwsTypeError(() => b1 as B<Null>);
Expect.throwsTypeError(() => b1 as B<Never>);
Expect.throwsTypeError(() => b1 as B<Function>);
Expect.throwsTypeError(() => b1 as B<int>);
Expect.throwsTypeError(() => b1 as B<(int, {int foo})>);
Expect.throwsTypeError(() => b1 as B<({int foo})>);
b2 as B<({int foo})>;
b2 as B<({num foo})>;
b2 as B<({int foo})>?;
b2 as B<({int foo})?>;
b2 as B<({int? foo})>;
Expect.throwsTypeError(() => b2 as B<(int, int)>);
Expect.throwsTypeError(() => b2 as B<({int bar})>);
Expect.throwsTypeError(() => b2 as B<({double foo})>);
b3 as B<(int, {int? foo})?>;
if (hasSoundNullSafety) {
Expect.throwsTypeError(() => b3 as B<(int, {int? foo})>);
Expect.throwsTypeError(() => b3 as B<(int, {int foo})?>);
}
A<(int, int)>() as A<(int, num)>;
A<(int, int)>() as A<Object>;
A<(int, int)>() as A<Record>;
Expect.throwsTypeError(() => A<(int, int)>() as A<(double, num)>);
Expect.throwsTypeError(() => A<(int, int)>() as A<(num, double)>);
Expect.throwsTypeError(() => A<(int, int)>() as A<Function>);
Expect.throwsTypeError(() => A<(int, int)>() as A<int>);
Expect.throwsTypeError(() => A<(int, int)>() as A<String>);
A<({int foo})>() as A<({int? foo})>;
A<({int foo})>() as A<({int foo})?>;
A<({int foo})>() as A<({int foo})>?;
if (hasSoundNullSafety) {
Expect.throwsTypeError(() => A<({int? foo})>() as A<({int foo})>);
Expect.throwsTypeError(() => A<({int? foo})>() as A<({int foo})?>);
Expect.throwsTypeError(() => A<({int? foo})>() as A<({int foo})>?);
Expect.throwsTypeError(() => A<({int foo})?>() as A<({int? foo})>);
Expect.throwsTypeError(() => A<({int foo})?>() as A<({int foo})>);
Expect.throwsTypeError(() => A<({int foo})?>() as A<({int foo})>?);
}
}
main() {
for (int i = 0; i < 200; ++i) {
doTests();
}
}