Version 2.12.0-262.0.dev
Merge commit '0f371f2dba0dac9179c042cc46ce7ec6b1b90ab2' into 'dev'
diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc
index 8ad3ca9..5a0c2ea 100644
--- a/runtime/vm/runtime_entry.cc
+++ b/runtime/vm/runtime_entry.cc
@@ -920,6 +920,21 @@
TypeTestingStubGenerator::SpecializeStubFor(thread, dst_type);
#if defined(DEBUG)
ASSERT(old_code.ptr() != dst_type.type_test_stub());
+ const auto& old_instructions =
+ Instructions::Handle(old_code.instructions());
+ const auto& new_code = Code::Handle(dst_type.type_test_stub());
+ const auto& new_instructions =
+ Instructions::Handle(new_code.instructions());
+ // Check if specialization produced exactly the same sequence of
+ // instructions. If it did then we have a bug in the TTS code
+ // generation code: we know that old code could not handle src_instance
+ // which means new code would not be able to handle it either
+ // (because the code is exactly the same) and will fall through into
+ // runtime again given a similar instance and again ask compiler to
+ // specialize the stub, which would produce the same code again and
+ // so on - leading to a serious performance problem if this type check
+ // is hot.
+ ASSERT(!old_instructions.Equals(new_instructions));
#endif
// Only create the cache when we come from a normal stub.
should_update_cache = false;
diff --git a/runtime/vm/type_testing_stubs.cc b/runtime/vm/type_testing_stubs.cc
index 686b69f..5fe1f7c 100644
--- a/runtime/vm/type_testing_stubs.cc
+++ b/runtime/vm/type_testing_stubs.cc
@@ -275,8 +275,8 @@
// These are handled via the TopTypeTypeTestStub!
ASSERT(!type.IsTopTypeForSubtyping());
- // Fast case for 'int'.
- if (type.IsIntType()) {
+ // Fast case for 'int' and '_Smi' (which can appear in core libraries).
+ if (type.IsIntType() || type.IsSmiType()) {
compiler::Label non_smi_value;
__ BranchIfNotSmi(TypeTestABI::kInstanceReg, &non_smi_value);
__ Ret();
@@ -309,8 +309,8 @@
/*include_abstract=*/false,
/*exclude_null=*/!Instance::NullIsAssignableTo(type));
- const Type& int_type = Type::Handle(Type::IntType());
- const bool smi_is_ok = int_type.IsSubtypeOf(type, Heap::kNew);
+ const Type& smi_type = Type::Handle(Type::SmiType());
+ const bool smi_is_ok = smi_type.IsSubtypeOf(type, Heap::kNew);
BuildOptimizedSubtypeRangeCheck(assembler, ranges, smi_is_ok);
} else {
@@ -477,10 +477,11 @@
__ BranchIf(NOT_EQUAL, check_failed);
} else {
const Class& type_class = Class::Handle(type_arg.type_class());
- const CidRangeVector& ranges = hi->SubtypeRangesForClass(
- type_class,
- /*include_abstract=*/true,
- /*exclude_null=*/!Instance::NullIsAssignableTo(type_arg));
+ const bool null_is_assignable = Instance::NullIsAssignableTo(type_arg);
+ const CidRangeVector& ranges =
+ hi->SubtypeRangesForClass(type_class,
+ /*include_abstract=*/true,
+ /*exclude_null=*/!null_is_assignable);
__ LoadField(
TTSInternalRegs::kScratchReg,
@@ -494,6 +495,10 @@
compiler::Label is_subtype;
__ SmiUntag(TTSInternalRegs::kScratchReg);
+ if (null_is_assignable) {
+ __ CompareImmediate(TTSInternalRegs::kScratchReg, kNullCid);
+ __ BranchIf(EQUAL, &is_subtype);
+ }
FlowGraphCompiler::GenerateCidRangesCheck(
assembler, TTSInternalRegs::kScratchReg, ranges, &is_subtype,
check_failed, true);
diff --git a/runtime/vm/type_testing_stubs_test.cc b/runtime/vm/type_testing_stubs_test.cc
index bf9e154..04e4012 100644
--- a/runtime/vm/type_testing_stubs_test.cc
+++ b/runtime/vm/type_testing_stubs_test.cc
@@ -160,6 +160,11 @@
const SubtypeTestCache& stc,
const Smi& abi_regs_modified,
const Smi& rest_regs_modified)> nonlazy) {
+ THR_Print(
+ "TTS_Test(instance=%s, dst_type=%s, instantiator_tav=%s, "
+ "function_tav=%s)\n",
+ instance.ToCString(), dst_type.ToCString(), instantiator_tav.ToCString(),
+ function_tav.ToCString());
ASSERT(instantiator_tav.IsNull() || instantiator_tav.IsCanonical());
ASSERT(function_tav.IsNull() || function_tav.IsCanonical());
auto thread = Thread::Current();
@@ -401,6 +406,7 @@
createI() => I<int, String>();
createI2() => I2();
createBaseInt() => Base<int>();
+ createBaseNull() => Base<Null>();
createA() => A();
createA1() => A1();
createA2() => A2<int>();
@@ -423,8 +429,10 @@
const auto& obj_i = Object::Handle(Invoke(root_library, "createI"));
const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2"));
- const auto& obj_baseint =
+ const auto& obj_base_int =
Object::Handle(Invoke(root_library, "createBaseInt"));
+ const auto& obj_base_null =
+ Object::Handle(Invoke(root_library, "createBaseNull"));
const auto& obj_a = Object::Handle(Invoke(root_library, "createA"));
const auto& obj_a1 = Object::Handle(Invoke(root_library, "createA1"));
const auto& obj_a2 = Object::Handle(Invoke(root_library, "createA2"));
@@ -457,10 +465,10 @@
// where there are no type arguments or the type arguments are top
// types.
//
- // obj as A // Subclass ranges
- // obj as Base<Object> // Subclass ranges with top-type tav
- // obj as I2 // Subtype ranges
- // obj as I<Object, dynamic> // Subtype ranges with top-type tav
+ // obj as A // Subclass ranges
+ // obj as Base<Object?> // Subclass ranges with top-type tav
+ // obj as I2 // Subtype ranges
+ // obj as I<Object?, dynamic> // Subtype ranges with top-type tav
//
// <...> as A
@@ -469,7 +477,7 @@
ExpectFailedViaTTS);
RunTTSTest(obj_i2, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
- RunTTSTest(obj_baseint, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS,
+ RunTTSTest(obj_base_int, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_a, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
@@ -484,14 +492,16 @@
RunTTSTest(obj_b2, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
- // <...> as Base<Object>
+ // <...> as Base<Object?>
auto& type_base = AbstractType::Handle(Type::New(class_base, tav_object));
FinalizeAndCanonicalize(&type_base);
RunTTSTest(obj_i, type_base, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_i2, type_base, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
- RunTTSTest(obj_baseint, type_base, tav_null, tav_null,
+ RunTTSTest(obj_base_int, type_base, tav_null, tav_null,
+ ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
+ RunTTSTest(obj_base_null, type_base, tav_null, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_a, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
@@ -506,14 +516,29 @@
RunTTSTest(obj_b2, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
+ // Base<Null> as Base<int?>
+ // This is a regression test verifying that Null is included in
+ // class-id ranges for int?.
+ auto& type_int = Type::Handle(Type::IntType());
+ type_int = type_int.ToNullability(
+ TestCase::IsNNBD() ? Nullability::kNullable : Nullability::kLegacy,
+ Heap::kNew);
+ auto& tav_int = TypeArguments::Handle(TypeArguments::New(1));
+ tav_int.SetTypeAt(0, type_int);
+ CanonicalizeTAV(&tav_int);
+ auto& type_base_int = AbstractType::Handle(Type::New(class_base, tav_int));
+ FinalizeAndCanonicalize(&type_base_int);
+ RunTTSTest(obj_base_null, type_base_int, tav_null, tav_null,
+ ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
+
// <...> as I2
const auto& type_i2 = AbstractType::Handle(class_i2.RareType());
RunTTSTest(obj_i, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_i2, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
- RunTTSTest(obj_baseint, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS,
- ExpectFailedViaTTS);
+ RunTTSTest(obj_base_int, type_i2, tav_null, tav_null,
+ ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_a, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_a1, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS,
@@ -535,7 +560,7 @@
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_i2, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
- RunTTSTest(obj_baseint, type_i_object_dynamic, tav_null, tav_null,
+ RunTTSTest(obj_base_int, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_a, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
@@ -562,7 +587,7 @@
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
RunTTSTest(obj_i2, type_dynamic_t, tav_object, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
- RunTTSTest(obj_baseint, type_dynamic_t, tav_object, tav_null,
+ RunTTSTest(obj_base_int, type_dynamic_t, tav_object, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
RunTTSTest(obj_a, type_dynamic_t, tav_object, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
@@ -601,7 +626,7 @@
const auto& obj_i = Object::Handle(Invoke(root_library, "createI"));
const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2"));
- const auto& obj_baseint =
+ const auto& obj_base_int =
Object::Handle(Invoke(root_library, "createBaseInt"));
const auto& obj_a = Object::Handle(Invoke(root_library, "createA"));
const auto& obj_a1 = Object::Handle(Invoke(root_library, "createA1"));
@@ -703,7 +728,7 @@
0, TypeParameter::Handle(GetClassTypeParameter(class_base, "T")));
auto& type_base_t = AbstractType::Handle(Type::New(class_base, tav_baset));
FinalizeAndCanonicalize(&type_base_t);
- RunTTSTest(obj_baseint, type_base_t, tav_int, tav_null,
+ RunTTSTest(obj_base_int, type_base_t, tav_int, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_baseistringdouble, type_base_t, tav_int, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
@@ -715,12 +740,12 @@
auto& type_base_b = AbstractType::Handle(Type::New(class_base, tav_baseb));
FinalizeAndCanonicalize(&type_base_b);
// With B == int
- RunTTSTest(obj_baseint, type_base_b, tav_null, tav_dynamic_int,
+ RunTTSTest(obj_base_int, type_base_b, tav_null, tav_dynamic_int,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_baseistringdouble, type_base_b, tav_null, tav_dynamic_int,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
// With B == dynamic (null vector)
- RunTTSTest(obj_baseint, type_base_b, tav_null, tav_null,
+ RunTTSTest(obj_base_int, type_base_b, tav_null, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_i2, type_base_b, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
@@ -742,7 +767,7 @@
FinalizeAndCanonicalize(&type_i_dynamic_string);
RunTTSTest(obj_i, type_i_dynamic_string, tav_null, tav_null,
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
- RunTTSTest(obj_baseint, type_i_dynamic_string, tav_null, tav_null,
+ RunTTSTest(obj_base_int, type_i_dynamic_string, tav_null, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
// <...> as Base<A2<T>>
@@ -760,7 +785,7 @@
FinalizeAndCanonicalize(&type_base_a2_t);
RunTTSTest(obj_basea2int, type_base_a2_t, tav_null, tav_null,
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
- RunTTSTest(obj_baseint, type_base_a2_t, tav_null, tav_null,
+ RunTTSTest(obj_base_int, type_base_a2_t, tav_null, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
// <...> as Base<A2<A1>>
@@ -872,6 +897,20 @@
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
}
+// Check that we generate correct TTS for _Smi type.
+ISOLATE_UNIT_TEST_CASE(TTS_Smi) {
+ const auto& root_library = Library::Handle(Library::CoreLibrary());
+ const auto& smi_class = Class::Handle(GetClass(root_library, "_Smi"));
+ ClassFinalizer::FinalizeTypesInClass(smi_class);
+
+ const auto& dst_type = AbstractType::Handle(smi_class.RareType());
+ const auto& tav_null = TypeArguments::Handle(TypeArguments::null());
+
+ THR_Print("\nTesting that instance of _Smi is a subtype of _Smi\n");
+ RunTTSTest(Smi::Handle(Smi::New(0)), dst_type, tav_null, tav_null,
+ ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
+}
+
} // namespace dart
#endif // defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \
diff --git a/tools/VERSION b/tools/VERSION
index a69e0df..96e8a89 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 12
PATCH 0
-PRERELEASE 261
+PRERELEASE 262
PRERELEASE_PATCH 0
\ No newline at end of file