Version 2.15.0-19.0.dev
Merge commit 'f4ddc8dc6b246cf915bb8777ee3e0efd04d20221' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79e0b5b..4f6512f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -57,6 +57,11 @@
- Added `void unawaited(Future)` top-level function to deal with the
`unawaited_futures` lint.
+#### `dart:cli`
+
+- The experimental `waitFor` functionality, and the library containing only that
+ function, are now deprecated.
+
#### `dart:core`
- Introduce `Enum` interface implemented by all `enum` declarations.
diff --git a/runtime/vm/BUILD.gn b/runtime/vm/BUILD.gn
index 55db0a7..ab184be 100644
--- a/runtime/vm/BUILD.gn
+++ b/runtime/vm/BUILD.gn
@@ -109,6 +109,12 @@
sources = vm_sources + rebase_path(compiler_api_sources, ".", "./compiler/") +
rebase_path(disassembler_sources, ".", "./compiler/") +
rebase_path(heap_sources, ".", "./heap/")
+ if (is_android) {
+ # Android specific workaround for a kernel bug. This source file can't
+ # go into vm_sources list because it will break Windows build which
+ # uses different assembler syntax.
+ sources += [ "thread_interrupter_android_arm.S" ]
+ }
include_dirs = [ ".." ]
}
diff --git a/runtime/vm/compiler/assembler/assembler_base.cc b/runtime/vm/compiler/assembler/assembler_base.cc
index 45e6a6f..9dbd0ff 100644
--- a/runtime/vm/compiler/assembler/assembler_base.cc
+++ b/runtime/vm/compiler/assembler/assembler_base.cc
@@ -299,7 +299,8 @@
}
bool AssemblerBase::EmittingComments() {
- return FLAG_code_comments || FLAG_disassemble || FLAG_disassemble_optimized;
+ return FLAG_code_comments || FLAG_disassemble || FLAG_disassemble_optimized ||
+ FLAG_disassemble_stubs;
}
void AssemblerBase::Stop(const char* message) {
diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc
index 5ecdd9e..4473e81 100644
--- a/runtime/vm/compiler/backend/flow_graph_compiler.cc
+++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc
@@ -2349,7 +2349,7 @@
// If there are no valid class ranges, the check will fail. If we are
// supposed to fall-through in the positive case, we'll explicitly jump to
// the [outside_range_lbl].
- if (cid_ranges.length() == 1 && cid_ranges[0].IsIllegalRange()) {
+ if (cid_ranges.is_empty()) {
if (fall_through_if_inside) {
assembler->Jump(outside_range_lbl);
}
diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc
index d2bf1c4..0224242 100644
--- a/runtime/vm/compiler/backend/il.cc
+++ b/runtime/vm/compiler/backend/il.cc
@@ -54,31 +54,16 @@
DECLARE_FLAG(bool, inline_alloc);
DECLARE_FLAG(bool, use_slow_path);
-class SubclassFinder {
+class SubtypeFinder {
public:
- SubclassFinder(Zone* zone,
- GrowableArray<intptr_t>* cids,
- bool include_abstract)
+ SubtypeFinder(Zone* zone,
+ GrowableArray<intptr_t>* cids,
+ bool include_abstract)
: array_handles_(zone),
class_handles_(zone),
cids_(cids),
include_abstract_(include_abstract) {}
- void ScanSubClasses(const Class& klass) {
- if (include_abstract_ || !klass.is_abstract()) {
- cids_->Add(klass.id());
- }
- ScopedHandle<GrowableObjectArray> array(&array_handles_);
- ScopedHandle<Class> subclass(&class_handles_);
- *array = klass.direct_subclasses();
- if (!array->IsNull()) {
- for (intptr_t i = 0; i < array->Length(); ++i) {
- *subclass ^= array->At(i);
- ScanSubClasses(*subclass);
- }
- }
- }
-
void ScanImplementorClasses(const Class& klass) {
// An implementor of [klass] is
// * the [klass] itself.
@@ -134,36 +119,9 @@
CidRangeVector& ranges = (*cid_ranges)[klass.id()];
if (ranges.length() == 0) {
if (!FLAG_precompiled_mode) {
- BuildRangesForJIT(table, &ranges, klass, /*use_subtype_test=*/true,
- include_abstract, exclude_null);
+ BuildRangesForJIT(table, &ranges, klass, include_abstract, exclude_null);
} else {
- BuildRangesFor(table, &ranges, klass, /*use_subtype_test=*/true,
- include_abstract, exclude_null);
- }
- }
- return ranges;
-}
-
-const CidRangeVector& HierarchyInfo::SubclassRangesForClass(
- const Class& klass) {
- ClassTable* table = thread()->isolate_group()->class_table();
- const intptr_t cid_count = table->NumCids();
- if (cid_subclass_ranges_ == nullptr) {
- cid_subclass_ranges_.reset(new CidRangeVector[cid_count]);
- }
-
- CidRangeVector& ranges = cid_subclass_ranges_[klass.id()];
- if (ranges.length() == 0) {
- if (!FLAG_precompiled_mode) {
- BuildRangesForJIT(table, &ranges, klass,
- /*use_subtype_test=*/true,
- /*include_abstract=*/false,
- /*exclude_null=*/false);
- } else {
- BuildRangesFor(table, &ranges, klass,
- /*use_subtype_test=*/false,
- /*include_abstract=*/false,
- /*exclude_null=*/false);
+ BuildRangesFor(table, &ranges, klass, include_abstract, exclude_null);
}
}
return ranges;
@@ -175,18 +133,12 @@
void HierarchyInfo::BuildRangesFor(ClassTable* table,
CidRangeVector* ranges,
const Class& klass,
- bool use_subtype_test,
bool include_abstract,
bool exclude_null) {
Zone* zone = thread()->zone();
- ClassTable* class_table = thread()->isolate_group()->class_table();
-
- // Only really used if `use_subtype_test == true`.
const Type& dst_type = Type::Handle(zone, Type::RawCast(klass.RareType()));
AbstractType& cls_type = AbstractType::Handle(zone);
-
Class& cls = Class::Handle(zone);
- AbstractType& super_type = AbstractType::Handle(zone);
const intptr_t cid_count = table->NumCids();
// Iterate over all cids to find the ones to be included in the ranges.
@@ -210,24 +162,14 @@
if (!include_abstract && cls.is_abstract()) continue;
if (cls.IsTopLevel()) continue;
- // We are either interested in [CidRange]es of subclasses or subtypes.
+ // We are interested in [CidRange]es of subtypes.
bool test_succeeded = false;
if (cid == kNullCid) {
ASSERT(exclude_null);
test_succeeded = false;
- } else if (use_subtype_test) {
+ } else {
cls_type = cls.RareType();
test_succeeded = cls_type.IsSubtypeOf(dst_type, Heap::kNew);
- } else {
- while (!cls.IsObjectClass()) {
- if (cls.ptr() == klass.ptr()) {
- test_succeeded = true;
- break;
- }
- super_type = cls.super_type();
- const intptr_t type_class_id = super_type.type_class_id();
- cls = class_table->At(type_class_id);
- }
}
if (test_succeeded) {
@@ -245,41 +187,31 @@
}
}
- // Construct last range (either close open one, or add invalid).
+ // Construct last range if there is a open one.
if (start != -1) {
ASSERT(start <= end);
CidRange range(start, end);
ranges->Add(range);
- } else if (ranges->length() == 0) {
- CidRange range;
- ASSERT(range.IsIllegalRange());
- ranges->Add(range);
}
}
void HierarchyInfo::BuildRangesForJIT(ClassTable* table,
CidRangeVector* ranges,
const Class& dst_klass,
- bool use_subtype_test,
bool include_abstract,
bool exclude_null) {
if (dst_klass.InVMIsolateHeap()) {
- BuildRangesFor(table, ranges, dst_klass, use_subtype_test, include_abstract,
- exclude_null);
+ BuildRangesFor(table, ranges, dst_klass, include_abstract, exclude_null);
return;
}
Zone* zone = thread()->zone();
GrowableArray<intptr_t> cids;
- SubclassFinder finder(zone, &cids, include_abstract);
+ SubtypeFinder finder(zone, &cids, include_abstract);
{
SafepointReadRwLocker ml(thread(),
thread()->isolate_group()->program_lock());
- if (use_subtype_test) {
- finder.ScanImplementorClasses(dst_klass);
- } else {
- finder.ScanSubClasses(dst_klass);
- }
+ finder.ScanImplementorClasses(dst_klass);
}
// Sort all collected cids.
@@ -429,17 +361,6 @@
ASSERT(type_class.NumTypeParameters() > 0 &&
type.arguments() != TypeArguments::null());
- // If the type class is implemented the different implementations might have
- // their type argument vector stored at different offsets and we can therefore
- // not perform our optimized [CidRange]-based implementation.
- //
- // TODO(kustermann): If the class is implemented but all implementations
- // store the instantator type argument vector at the same offset we can
- // still do it!
- if (type_class.is_implemented()) {
- return false;
- }
-
const TypeArguments& ta =
TypeArguments::Handle(zone, Type::Cast(type).arguments());
ASSERT(ta.Length() == num_type_arguments);
@@ -477,11 +398,10 @@
/*exclude_null=*/true);
if (ranges.length() == 1) {
const CidRangeValue& range = ranges[0];
- if (!range.IsIllegalRange()) {
- *lower_limit = range.cid_start;
- *upper_limit = range.cid_end;
- return true;
- }
+ ASSERT(!range.IsIllegalRange());
+ *lower_limit = range.cid_start;
+ *upper_limit = range.cid_end;
+ return true;
}
}
return false;
diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h
index 40658e5..e4d883f 100644
--- a/runtime/vm/compiler/backend/il.h
+++ b/runtime/vm/compiler/backend/il.h
@@ -5,6 +5,7 @@
#ifndef RUNTIME_VM_COMPILER_BACKEND_IL_H_
#define RUNTIME_VM_COMPILER_BACKEND_IL_H_
+#include "vm/hash_map.h"
#if defined(DART_PRECOMPILED_RUNTIME)
#error "AOT runtime should not use compiler sources (including header files)"
#endif // defined(DART_PRECOMPILED_RUNTIME)
@@ -247,17 +248,19 @@
cid_subtype_ranges_nullable_(),
cid_subtype_ranges_abstract_nullable_(),
cid_subtype_ranges_nonnullable_(),
- cid_subtype_ranges_abstract_nonnullable_(),
- cid_subclass_ranges_() {
+ cid_subtype_ranges_abstract_nonnullable_() {
thread->set_hierarchy_info(this);
}
~HierarchyInfo() { thread()->set_hierarchy_info(NULL); }
+ // Returned from FindBestTAVOffset and SplitOnConsistentTypeArguments
+ // to denote a failure to find a compatible concrete, finalized class.
+ static const intptr_t kNoCompatibleTAVOffset = 0;
+
const CidRangeVector& SubtypeRangesForClass(const Class& klass,
bool include_abstract,
bool exclude_null);
- const CidRangeVector& SubclassRangesForClass(const Class& klass);
bool InstanceOfHasClassRange(const AbstractType& type,
intptr_t* lower_limit,
@@ -284,13 +287,11 @@
private:
// Does not use any hierarchy information available in the system but computes
// it via O(n) class table traversal. The boolean parameters denote:
- // use_subtype_test : if set, IsSubtypeOf() is used to compute inclusion
// include_abstract : if set, include abstract types (don't care otherwise)
// exclude_null : if set, exclude null types (don't care otherwise)
void BuildRangesFor(ClassTable* table,
CidRangeVector* ranges,
const Class& klass,
- bool use_subtype_test,
bool include_abstract,
bool exclude_null);
@@ -299,7 +300,6 @@
void BuildRangesForJIT(ClassTable* table,
CidRangeVector* ranges,
const Class& klass,
- bool use_subtype_test,
bool include_abstract,
bool exclude_null);
@@ -307,7 +307,6 @@
std::unique_ptr<CidRangeVector[]> cid_subtype_ranges_abstract_nullable_;
std::unique_ptr<CidRangeVector[]> cid_subtype_ranges_nonnullable_;
std::unique_ptr<CidRangeVector[]> cid_subtype_ranges_abstract_nonnullable_;
- std::unique_ptr<CidRangeVector[]> cid_subclass_ranges_;
};
// An embedded container with N elements of type T. Used (with partial
diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc
index 42a9839..7adbe17 100644
--- a/runtime/vm/compiler/backend/type_propagator.cc
+++ b/runtime/vm/compiler/backend/type_propagator.cc
@@ -256,9 +256,7 @@
CompileType result = CompileType::None();
for (intptr_t i = 0, n = cids.length(); i < n; i++) {
CidRange* cid_range = cids.At(i);
- if (cid_range->IsIllegalRange()) {
- return;
- }
+ ASSERT(!cid_range->IsIllegalRange());
for (intptr_t cid = cid_range->cid_start; cid <= cid_range->cid_end;
cid++) {
CompileType tp = CompileType::FromCid(cid);
diff --git a/runtime/vm/compiler/compiler_sources.gni b/runtime/vm/compiler/compiler_sources.gni
index 05fd213..c63846e 100644
--- a/runtime/vm/compiler/compiler_sources.gni
+++ b/runtime/vm/compiler/compiler_sources.gni
@@ -146,9 +146,6 @@
"stub_code_compiler_arm64.cc",
"stub_code_compiler_ia32.cc",
"stub_code_compiler_x64.cc",
- "type_testing_stubs_arm.cc",
- "type_testing_stubs_arm64.cc",
- "type_testing_stubs_x64.cc",
"write_barrier_elimination.cc",
"write_barrier_elimination.h",
]
diff --git a/runtime/vm/compiler/type_testing_stubs_arm.cc b/runtime/vm/compiler/type_testing_stubs_arm.cc
deleted file mode 100644
index ccd9d36..0000000
--- a/runtime/vm/compiler/type_testing_stubs_arm.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2019, 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.
-
-#include "vm/globals.h"
-
-#if defined(TARGET_ARCH_ARM) && !defined(DART_PRECOMPILED_RUNTIME)
-
-#include "vm/type_testing_stubs.h"
-
-#define __ assembler->
-
-namespace dart {
-
-void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
- compiler::Assembler* assembler,
- compiler::UnresolvedPcRelativeCalls* unresolved_calls,
- const Code& slow_type_test_stub,
- HierarchyInfo* hi,
- const Type& type,
- const Class& type_class) {
- BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
- __ Branch(compiler::Address(
- THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
-}
-
-} // namespace dart
-
-#endif // defined(TARGET_ARCH_ARM) && !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/compiler/type_testing_stubs_arm64.cc b/runtime/vm/compiler/type_testing_stubs_arm64.cc
deleted file mode 100644
index 850b198..0000000
--- a/runtime/vm/compiler/type_testing_stubs_arm64.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2019, 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.
-
-#include "vm/globals.h"
-
-#if defined(TARGET_ARCH_ARM64) && !defined(DART_PRECOMPILED_RUNTIME)
-
-#include "vm/type_testing_stubs.h"
-
-#define __ assembler->
-
-namespace dart {
-
-void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
- compiler::Assembler* assembler,
- compiler::UnresolvedPcRelativeCalls* unresolved_calls,
- const Code& slow_type_test_stub,
- HierarchyInfo* hi,
- const Type& type,
- const Class& type_class) {
- BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
- __ ldr(
- TMP,
- compiler::Address(
- THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
- __ br(TMP);
-}
-
-} // namespace dart
-
-#endif // defined(TARGET_ARCH_ARM64) && !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/compiler/type_testing_stubs_x64.cc b/runtime/vm/compiler/type_testing_stubs_x64.cc
deleted file mode 100644
index 7722cee..0000000
--- a/runtime/vm/compiler/type_testing_stubs_x64.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2019, 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.
-
-#include "vm/globals.h"
-
-#if defined(TARGET_ARCH_X64) && !defined(DART_PRECOMPILED_RUNTIME)
-
-#include "vm/type_testing_stubs.h"
-
-#define __ assembler->
-
-namespace dart {
-
-void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
- compiler::Assembler* assembler,
- compiler::UnresolvedPcRelativeCalls* unresolved_calls,
- const Code& slow_type_test_stub,
- HierarchyInfo* hi,
- const Type& type,
- const Class& type_class) {
- BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
- __ jmp(compiler::Address(
- THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
-}
-
-} // namespace dart
-
-#endif // defined(TARGET_ARCH_X64) && !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/ffi_callback_trampolines.cc b/runtime/vm/ffi_callback_trampolines.cc
index 25057e0..8cc1946 100644
--- a/runtime/vm/ffi_callback_trampolines.cc
+++ b/runtime/vm/ffi_callback_trampolines.cc
@@ -14,8 +14,6 @@
namespace dart {
-DECLARE_FLAG(bool, disassemble_stubs);
-
#if !defined(DART_PRECOMPILED_RUNTIME)
uword NativeCallbackTrampolines::TrampolineForId(int32_t callback_id) {
#if defined(DART_PRECOMPILER)
diff --git a/runtime/vm/flag_list.h b/runtime/vm/flag_list.h
index 543b601..1e3544d 100644
--- a/runtime/vm/flag_list.h
+++ b/runtime/vm/flag_list.h
@@ -35,6 +35,7 @@
P(disassemble, bool, false, "Disassemble dart code.") \
P(disassemble_optimized, bool, false, "Disassemble optimized code.") \
P(disassemble_relative, bool, false, "Use offsets instead of absolute PCs") \
+ P(disassemble_stubs, bool, false, "Disassemble generated stubs.") \
P(support_disassembler, bool, true, "Support the disassembler.")
#else
#define DISASSEMBLE_FLAGS(P, R, C, D) \
@@ -42,6 +43,7 @@
R(disassemble_optimized, false, bool, false, "Disassemble optimized code.") \
R(disassemble_relative, false, bool, false, \
"Use offsets instead of absolute PCs") \
+ R(disassemble_stubs, false, bool, false, "Disassemble generated stubs.") \
R(support_disassembler, false, bool, true, "Support the disassembler.")
#endif
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index a716109..5c3adc4 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -11397,48 +11397,93 @@
return true;
}
-// Given the type G<T0, ..., Tn> and class C<U0, ..., Un> find path to C at G.
-// This path can be used to compute type arguments of C at G.
-//
-// Note: we are relying on the restriction that the same class can only occur
-// once among the supertype.
-static bool FindInstantiationOf(const Type& type,
+bool Class::FindInstantiationOf(Zone* zone,
const Class& cls,
GrowableArray<const AbstractType*>* path,
- bool consider_only_super_classes) {
- if (type.type_class() == cls.ptr()) {
+ bool consider_only_super_classes) const {
+ ASSERT(cls.is_type_finalized());
+ if (cls.ptr() == ptr()) {
return true; // Found instantiation.
}
- Class& cls2 = Class::Handle();
- AbstractType& super_type = AbstractType::Handle();
- super_type = cls.super_type();
- if (!super_type.IsNull() && !super_type.IsObjectType()) {
- cls2 = super_type.type_class();
- path->Add(&super_type);
- if (FindInstantiationOf(type, cls2, path, consider_only_super_classes)) {
+ Class& cls2 = Class::Handle(zone);
+ AbstractType& super = AbstractType::Handle(zone, super_type());
+ if (!super.IsNull() && !super.IsObjectType()) {
+ cls2 = super.type_class();
+ if (path != nullptr) {
+ path->Add(&super);
+ }
+ if (cls2.FindInstantiationOf(zone, cls, path,
+ consider_only_super_classes)) {
return true; // Found instantiation.
}
- path->RemoveLast();
- }
-
- if (!consider_only_super_classes) {
- Array& super_interfaces = Array::Handle(cls.interfaces());
- for (intptr_t i = 0; i < super_interfaces.Length(); i++) {
- super_type ^= super_interfaces.At(i);
- cls2 = super_type.type_class();
- path->Add(&super_type);
- if (FindInstantiationOf(type, cls2, path,
- /*consider_only_supertypes=*/false)) {
- return true; // Found instantiation.
- }
+ if (path != nullptr) {
path->RemoveLast();
}
}
+ if (!consider_only_super_classes) {
+ Array& super_interfaces = Array::Handle(zone, interfaces());
+ for (intptr_t i = 0; i < super_interfaces.Length(); i++) {
+ super ^= super_interfaces.At(i);
+ cls2 = super.type_class();
+ if (path != nullptr) {
+ path->Add(&super);
+ }
+ if (cls2.FindInstantiationOf(zone, cls, path)) {
+ return true; // Found instantiation.
+ }
+ if (path != nullptr) {
+ path->RemoveLast();
+ }
+ }
+ }
+
return false; // Not found.
}
+bool Class::FindInstantiationOf(Zone* zone,
+ const Type& type,
+ GrowableArray<const AbstractType*>* path,
+ bool consider_only_super_classes) const {
+ return FindInstantiationOf(zone, Class::Handle(zone, type.type_class()), path,
+ consider_only_super_classes);
+}
+
+TypePtr Class::GetInstantiationOf(Zone* zone, const Class& cls) const {
+ if (ptr() == cls.ptr()) {
+ return DeclarationType();
+ }
+ if (FindInstantiationOf(zone, cls, /*consider_only_super_classes=*/true)) {
+ // Since [cls] is a superclass of [this], use [cls]'s declaration type.
+ return cls.DeclarationType();
+ }
+ const auto& decl_type = Type::Handle(zone, DeclarationType());
+ GrowableArray<const AbstractType*> path(zone, 0);
+ if (!FindInstantiationOf(zone, cls, &path)) {
+ return Type::null();
+ }
+ ASSERT(!path.is_empty());
+ auto& calculated_type = Type::Handle(zone, decl_type.ptr());
+ auto& calculated_type_args =
+ TypeArguments::Handle(zone, calculated_type.arguments());
+ for (auto* const type : path) {
+ calculated_type ^= type->ptr();
+ if (!calculated_type.IsInstantiated()) {
+ calculated_type ^= calculated_type.InstantiateFrom(
+ calculated_type_args, Object::null_type_arguments(), kAllFree,
+ Heap::kNew);
+ }
+ calculated_type_args = calculated_type.arguments();
+ }
+ ASSERT_EQUAL(calculated_type.type_class_id(), cls.id());
+ return calculated_type.ptr();
+}
+
+TypePtr Class::GetInstantiationOf(Zone* zone, const Type& type) const {
+ return GetInstantiationOf(zone, Class::Handle(zone, type.type_class()));
+}
+
void Field::SetStaticValue(const Object& value) const {
auto thread = Thread::Current();
ASSERT(thread->IsMutatorThread());
@@ -11476,21 +11521,22 @@
ASSERT(value.ptr() != Object::sentinel().ptr());
ASSERT(value.ptr() != Object::transition_sentinel().ptr());
+ Zone* const zone = Thread::Current()->zone();
const TypeArguments& static_type_args =
- TypeArguments::Handle(static_type.arguments());
+ TypeArguments::Handle(zone, static_type.arguments());
- TypeArguments& args = TypeArguments::Handle();
+ TypeArguments& args = TypeArguments::Handle(zone);
ASSERT(static_type.IsFinalized());
- const Class& cls = Class::Handle(value.clazz());
+ const Class& cls = Class::Handle(zone, value.clazz());
GrowableArray<const AbstractType*> path(10);
bool is_super_class = true;
- if (!FindInstantiationOf(static_type, cls, &path,
- /*consider_only_super_classes=*/true)) {
+ if (!cls.FindInstantiationOf(zone, static_type, &path,
+ /*consider_only_super_classes=*/true)) {
is_super_class = false;
- bool found_super_interface = FindInstantiationOf(
- static_type, cls, &path, /*consider_only_super_classes=*/false);
+ bool found_super_interface =
+ cls.FindInstantiationOf(zone, static_type, &path);
ASSERT(found_super_interface);
}
@@ -11522,7 +11568,7 @@
// To compute C<X0, ..., Xn> at G we walk the chain backwards and
// instantiate Si using type parameters of S{i-1} which gives us a type
// depending on type parameters of S{i-2}.
- AbstractType& type = AbstractType::Handle(path.Last()->ptr());
+ AbstractType& type = AbstractType::Handle(zone, path.Last()->ptr());
for (intptr_t i = path.length() - 2; (i >= 0) && !type.IsInstantiated();
i--) {
args = path[i]->arguments();
@@ -11560,19 +11606,19 @@
const intptr_t num_type_params = cls.NumTypeParameters();
bool trivial_case =
(num_type_params ==
- Class::Handle(static_type.type_class()).NumTypeParameters()) &&
+ Class::Handle(zone, static_type.type_class()).NumTypeParameters()) &&
(value.GetTypeArguments() == static_type.arguments());
if (!trivial_case && FLAG_trace_field_guards) {
THR_Print("Not a simple case: %" Pd " vs %" Pd
" type parameters, %s vs %s type arguments\n",
num_type_params,
- Class::Handle(static_type.type_class()).NumTypeParameters(),
+ Class::Handle(zone, static_type.type_class()).NumTypeParameters(),
SafeTypeArgumentsToCString(
- TypeArguments::Handle(value.GetTypeArguments())),
+ TypeArguments::Handle(zone, value.GetTypeArguments())),
SafeTypeArgumentsToCString(static_type_args));
}
- AbstractType& type_arg = AbstractType::Handle();
+ AbstractType& type_arg = AbstractType::Handle(zone);
args = type.arguments();
for (intptr_t i = 0; (i < num_type_params) && trivial_case; i++) {
type_arg = args.TypeAt(i);
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 112efdd..3b9d8ec 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -1270,6 +1270,71 @@
}
void set_interfaces(const Array& value) const;
+ // Returns whether a path from [this] to [cls] can be found, where the first
+ // element is a direct supertype of [this], each following element is a direct
+ // supertype of the previous element and the final element has [cls] as its
+ // type class. If [this] and [cls] are the same class, then the path is empty.
+ //
+ // If [path] is not nullptr, then the elements of the path are added to it.
+ // This path can then be used to compute type arguments of [cls] given type
+ // arguments for an instance of [this].
+ //
+ // Note: There may be multiple paths to [cls], but the result of applying each
+ // path must be equal to the other results.
+ bool FindInstantiationOf(Zone* zone,
+ const Class& cls,
+ GrowableArray<const AbstractType*>* path,
+ bool consider_only_super_classes = false) const;
+ bool FindInstantiationOf(Zone* zone,
+ const Class& cls,
+ bool consider_only_super_classes = false) const {
+ return FindInstantiationOf(zone, cls, /*path=*/nullptr,
+ consider_only_super_classes);
+ }
+
+ // Returns whether a path from [this] to [type] can be found, where the first
+ // element is a direct supertype of [this], each following element is a direct
+ // supertype of the previous element and the final element has the same type
+ // class as [type]. If [this] is the type class of [type], then the path is
+ // empty.
+ //
+ // If [path] is not nullptr, then the elements of the path are added to it.
+ // This path can then be used to compute type arguments of [type]'s type
+ // class given type arguments for an instance of [this].
+ //
+ // Note: There may be multiple paths to [type]'s type class, but the result of
+ // applying each path must be equal to the other results.
+ bool FindInstantiationOf(Zone* zone,
+ const Type& type,
+ GrowableArray<const AbstractType*>* path,
+ bool consider_only_super_classes = false) const;
+ bool FindInstantiationOf(Zone* zone,
+ const Type& type,
+ bool consider_only_super_classes = false) const {
+ return FindInstantiationOf(zone, type, /*path=*/nullptr,
+ consider_only_super_classes);
+ }
+
+ // If [this] is a subtype of a type with type class [cls], then this
+ // returns [cls]<X_0, ..., X_n>, where n is the number of type arguments for
+ // [cls] and where each type argument X_k is either instantiated or has free
+ // class type parameters corresponding to the type parameters of [this].
+ // Thus, given an instance of [this], the result can be instantiated
+ // with the instance type arguments to get the type of the instance.
+ //
+ // If [this] is not a subtype of a type with type class [cls], returns null.
+ TypePtr GetInstantiationOf(Zone* zone, const Class& cls) const;
+
+ // If [this] is a subtype of [type], then this returns [cls]<X_0, ..., X_n>,
+ // where [cls] is the type class of [type], n is the number of type arguments
+ // for [cls], and where each type argument X_k is either instantiated or has
+ // free class type parameters corresponding to the type parameters of [this].
+ // Thus, given an instance of [this], the result can be instantiated with the
+ // instance type arguments to get the type of the instance.
+ //
+ // If [this] is not a subtype of a type with type class [cls], returns null.
+ TypePtr GetInstantiationOf(Zone* zone, const Type& type) const;
+
#if !defined(PRODUCT) || !defined(DART_PRECOMPILED_RUNTIME)
// Returns the list of classes directly implementing this class.
GrowableObjectArrayPtr direct_implementors() const {
diff --git a/runtime/vm/object_test.cc b/runtime/vm/object_test.cc
index 93cfc12..a2e7385 100644
--- a/runtime/vm/object_test.cc
+++ b/runtime/vm/object_test.cc
@@ -5304,4 +5304,97 @@
EXPECT(!m.IsSubtypeOf(t, Heap::kNew));
}
+TEST_CASE(Class_GetInstantiationOf) {
+ const char* kScript = R"(
+ class B<T> {}
+ class A1<X, Y> implements B<List<Y>> {}
+ class A2<X, Y> extends A1<Y, X> {}
+ )";
+ Dart_Handle api_lib = TestCase::LoadTestScript(kScript, nullptr);
+ EXPECT_VALID(api_lib);
+ TransitionNativeToVM transition(thread);
+ Zone* const zone = thread->zone();
+
+ const auto& root_lib =
+ Library::CheckedHandle(zone, Api::UnwrapHandle(api_lib));
+ EXPECT(!root_lib.IsNull());
+ const auto& class_b = Class::Handle(zone, GetClass(root_lib, "B"));
+ const auto& class_a1 = Class::Handle(zone, GetClass(root_lib, "A1"));
+ const auto& class_a2 = Class::Handle(zone, GetClass(root_lib, "A2"));
+
+ const auto& core_lib = Library::Handle(zone, Library::CoreLibrary());
+ const auto& class_list = Class::Handle(zone, GetClass(core_lib, "List"));
+
+ auto expect_type_equal = [](const AbstractType& expected,
+ const AbstractType& got) {
+ if (got.Equals(expected)) return;
+ TextBuffer buffer(128);
+ buffer.AddString("Expected type ");
+ expected.PrintName(Object::kScrubbedName, &buffer);
+ buffer.AddString(", got ");
+ got.PrintName(Object::kScrubbedName, &buffer);
+ dart::Expect(__FILE__, __LINE__).Fail("%s", buffer.buffer());
+ };
+
+ const auto& decl_type_b = Type::Handle(zone, class_b.DeclarationType());
+ const auto& decl_type_list = Type::Handle(zone, class_list.DeclarationType());
+ const auto& null_tav = Object::null_type_arguments();
+
+ // Test that A1.GetInstantiationOf(B) returns B<List<A1::Y>>.
+ {
+ const auto& decl_type_a1 = Type::Handle(zone, class_a1.DeclarationType());
+ const auto& decl_type_args_a1 =
+ TypeArguments::Handle(zone, decl_type_a1.arguments());
+ const auto& type_arg_a1_y =
+ TypeParameter::CheckedHandle(zone, decl_type_args_a1.TypeAt(1));
+ auto& tav_a1_y = TypeArguments::Handle(TypeArguments::New(1));
+ tav_a1_y.SetTypeAt(0, type_arg_a1_y);
+ tav_a1_y = tav_a1_y.Canonicalize(thread, nullptr);
+ auto& type_list_a1_y = Type::CheckedHandle(
+ zone, decl_type_list.InstantiateFrom(tav_a1_y, null_tav, kAllFree,
+ Heap::kNew));
+ type_list_a1_y ^= type_list_a1_y.Canonicalize(thread, nullptr);
+ auto& tav_list_a1_y = TypeArguments::Handle(TypeArguments::New(1));
+ tav_list_a1_y.SetTypeAt(0, type_list_a1_y);
+ tav_list_a1_y = tav_list_a1_y.Canonicalize(thread, nullptr);
+ auto& type_b_list_a1_y = Type::CheckedHandle(
+ zone, decl_type_b.InstantiateFrom(tav_list_a1_y, null_tav, kAllFree,
+ Heap::kNew));
+ type_b_list_a1_y ^= type_b_list_a1_y.Canonicalize(thread, nullptr);
+
+ const auto& inst_b_a1 =
+ Type::Handle(zone, class_a1.GetInstantiationOf(zone, class_b));
+ EXPECT(!inst_b_a1.IsNull());
+ expect_type_equal(type_b_list_a1_y, inst_b_a1);
+ }
+
+ // Test that A2.GetInstantiationOf(B) returns B<List<A2::X>>.
+ {
+ const auto& decl_type_a2 = Type::Handle(zone, class_a2.DeclarationType());
+ const auto& decl_type_args_a2 =
+ TypeArguments::Handle(zone, decl_type_a2.arguments());
+ const auto& type_arg_a2_x =
+ TypeParameter::CheckedHandle(zone, decl_type_args_a2.TypeAt(1));
+ auto& tav_a2_x = TypeArguments::Handle(TypeArguments::New(1));
+ tav_a2_x.SetTypeAt(0, type_arg_a2_x);
+ tav_a2_x = tav_a2_x.Canonicalize(thread, nullptr);
+ auto& type_list_a2_x = Type::CheckedHandle(
+ zone, decl_type_list.InstantiateFrom(tav_a2_x, null_tav, kAllFree,
+ Heap::kNew));
+ type_list_a2_x ^= type_list_a2_x.Canonicalize(thread, nullptr);
+ auto& tav_list_a2_x = TypeArguments::Handle(TypeArguments::New(1));
+ tav_list_a2_x.SetTypeAt(0, type_list_a2_x);
+ tav_list_a2_x = tav_list_a2_x.Canonicalize(thread, nullptr);
+ auto& type_b_list_a2_x = Type::CheckedHandle(
+ zone, decl_type_b.InstantiateFrom(tav_list_a2_x, null_tav, kAllFree,
+ Heap::kNew));
+ type_b_list_a2_x ^= type_b_list_a2_x.Canonicalize(thread, nullptr);
+
+ const auto& inst_b_a2 =
+ Type::Handle(zone, class_a2.GetInstantiationOf(zone, class_b));
+ EXPECT(!inst_b_a2.IsNull());
+ expect_type_equal(type_b_list_a2_x, inst_b_a2);
+ }
+}
+
} // namespace dart
diff --git a/runtime/vm/signal_handler.h b/runtime/vm/signal_handler.h
index 22821d3..cabe149 100644
--- a/runtime/vm/signal_handler.h
+++ b/runtime/vm/signal_handler.h
@@ -38,84 +38,19 @@
#include <ucontext.h> // NOLINT
#endif
-// Old linux kernels on ARM might require a trampoline to
-// work around incorrect Thumb -> ARM transitions. See SignalHandlerTrampoline
-// below for more details.
-#if defined(HOST_ARCH_ARM) && \
- (defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)) && \
- !defined(__thumb__)
-#define USE_SIGNAL_HANDLER_TRAMPOLINE
-#endif
-
namespace dart {
typedef void (*SignalAction)(int signal, siginfo_t* info, void* context);
class SignalHandler : public AllStatic {
public:
- template <SignalAction action>
- static void Install() {
-#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
- InstallImpl(SignalHandlerTrampoline<action>);
-#else
- InstallImpl(action);
-#endif // defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
- }
+ static void Install(SignalAction action);
static void Remove();
static uintptr_t GetProgramCounter(const mcontext_t& mcontext);
static uintptr_t GetFramePointer(const mcontext_t& mcontext);
static uintptr_t GetCStackPointer(const mcontext_t& mcontext);
static uintptr_t GetDartStackPointer(const mcontext_t& mcontext);
static uintptr_t GetLinkRegister(const mcontext_t& mcontext);
-
- private:
- static void InstallImpl(SignalAction action);
-
-#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
- // Work around for a bug in old kernels (only fixed in 3.18 Android kernel):
- //
- // Kernel does not clear If-Then execution state bits when entering ARM signal
- // handler which violates requirements imposed by ARM architecture reference.
- // Some CPUs look at these bits even while in ARM mode which causes them
- // to skip some instructions in the prologue of the signal handler.
- //
- // To work around the issue we insert enough NOPs in the prologue to ensure
- // that no actual instructions are skipped and then branch to the actual
- // signal handler.
- //
- // For the kernel patch that fixes the issue see:
- // http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=6ecf830e5029598732e04067e325d946097519cb
- //
- // Note: this function is marked "naked" because we must guarantee that
- // our NOPs occur before any compiler generated prologue.
- template <SignalAction action>
- static __attribute__((naked)) void SignalHandlerTrampoline(int signal,
- siginfo_t* info,
- void* context_) {
- // IT (If-Then) instruction makes up to four instructions that follow it
- // conditional.
- // Note: clobber all register so that compiler does not attempt to hoist
- // anything from the next assembly block past this one.
- asm volatile("nop; nop; nop; nop;"
- :
- :
- : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9",
- "r10", "r11", "r12", "r13", "r14", "memory");
-
- // Tail-call into the actual signal handler.
- //
- // Note: this code is split into a separate inline assembly block because
- // any code that compiler generates to satisfy register constraints must
- // be generated after four NOPs.
- //
- // Note: there is no portable way to specify that we want to have
- // signal, info and context_ in r0 - r2 respectively. So we just mark them
- // as clobbered and hope that compiler does not emit any code that uses
- // these registers to satisfy action constraint (we tested on clang and
- // the generated code looks like one would expect).
- asm volatile("bx %0;" : : "r"(action) : "r0", "r1", "r2", "memory");
- }
-#endif // defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
};
#undef USE_SIGNAL_HANDLER_TRAMPOLINE
diff --git a/runtime/vm/signal_handler_android.cc b/runtime/vm/signal_handler_android.cc
index 47c2ded..2fba54a 100644
--- a/runtime/vm/signal_handler_android.cc
+++ b/runtime/vm/signal_handler_android.cc
@@ -94,7 +94,7 @@
return lr;
}
-void SignalHandler::InstallImpl(SignalAction action) {
+void SignalHandler::Install(SignalAction action) {
// Bionic implementation of setjmp temporary mangles SP register
// in place which breaks signal delivery on the thread stack - when
// kernel tries to deliver SIGPROF and we are in the middle of
diff --git a/runtime/vm/signal_handler_fuchsia.cc b/runtime/vm/signal_handler_fuchsia.cc
index 3a2f161..9e1ddee 100644
--- a/runtime/vm/signal_handler_fuchsia.cc
+++ b/runtime/vm/signal_handler_fuchsia.cc
@@ -36,7 +36,7 @@
return 0;
}
-void SignalHandler::InstallImpl(SignalAction action) {
+void SignalHandler::Install(SignalAction action) {
UNIMPLEMENTED();
}
diff --git a/runtime/vm/signal_handler_linux.cc b/runtime/vm/signal_handler_linux.cc
index ae75730..1979da4 100644
--- a/runtime/vm/signal_handler_linux.cc
+++ b/runtime/vm/signal_handler_linux.cc
@@ -94,7 +94,7 @@
return lr;
}
-void SignalHandler::InstallImpl(SignalAction action) {
+void SignalHandler::Install(SignalAction action) {
struct sigaction act = {};
act.sa_handler = NULL;
act.sa_sigaction = action;
diff --git a/runtime/vm/signal_handler_macos.cc b/runtime/vm/signal_handler_macos.cc
index 71ad84f..70e5319 100644
--- a/runtime/vm/signal_handler_macos.cc
+++ b/runtime/vm/signal_handler_macos.cc
@@ -90,7 +90,7 @@
return lr;
}
-void SignalHandler::InstallImpl(SignalAction action) {
+void SignalHandler::Install(SignalAction action) {
struct sigaction act = {};
act.sa_handler = NULL;
act.sa_sigaction = action;
diff --git a/runtime/vm/signal_handler_win.cc b/runtime/vm/signal_handler_win.cc
index 254164c..4463eb9 100644
--- a/runtime/vm/signal_handler_win.cc
+++ b/runtime/vm/signal_handler_win.cc
@@ -33,7 +33,7 @@
return 0;
}
-void SignalHandler::InstallImpl(SignalAction action) {
+void SignalHandler::Install(SignalAction action) {
UNIMPLEMENTED();
}
diff --git a/runtime/vm/stub_code.cc b/runtime/vm/stub_code.cc
index 8a54e72..90576ec 100644
--- a/runtime/vm/stub_code.cc
+++ b/runtime/vm/stub_code.cc
@@ -22,7 +22,6 @@
namespace dart {
-DEFINE_FLAG(bool, disassemble_stubs, false, "Disassemble generated stubs.");
DECLARE_FLAG(bool, precompiled_mode);
StubCode::StubCodeEntry StubCode::entries_[kNumStubEntries] = {
diff --git a/runtime/vm/stub_code.h b/runtime/vm/stub_code.h
index 3eac0b6..66d5212e 100644
--- a/runtime/vm/stub_code.h
+++ b/runtime/vm/stub_code.h
@@ -22,8 +22,6 @@
class Isolate;
class ObjectPointerVisitor;
-DECLARE_FLAG(bool, disassemble_stubs);
-
// Is it permitted for the stubs above to refer to Object::null(), which is
// allocated in the VM isolate and shared across all isolates.
// However, in cases where a simple GC-safe placeholder is needed on the stack,
diff --git a/runtime/vm/thread_interrupter_android.cc b/runtime/vm/thread_interrupter_android.cc
index 367fa41..dbdd331 100644
--- a/runtime/vm/thread_interrupter_android.cc
+++ b/runtime/vm/thread_interrupter_android.cc
@@ -18,36 +18,48 @@
#ifndef PRODUCT
+// Old linux kernels on ARM might require a trampoline to
+// work around incorrect Thumb -> ARM transitions.
+// See thread_interrupted_android_arm.S for more details.
+#if defined(HOST_ARCH_ARM) && \
+ (defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)) && \
+ !defined(__thumb__)
+#define USE_SIGNAL_HANDLER_TRAMPOLINE
+#endif
+
DECLARE_FLAG(bool, trace_thread_interrupter);
-class ThreadInterrupterAndroid : public AllStatic {
- public:
- static void ThreadInterruptSignalHandler(int signal,
- siginfo_t* info,
- void* context_) {
- if (signal != SIGPROF) {
- return;
- }
- Thread* thread = Thread::Current();
- if (thread == NULL) {
- return;
- }
- ThreadInterrupter::SampleBufferWriterScope scope;
- if (!scope.CanSample()) {
- return;
- }
- // Extract thread state.
- ucontext_t* context = reinterpret_cast<ucontext_t*>(context_);
- mcontext_t mcontext = context->uc_mcontext;
- InterruptedThreadState its;
- its.pc = SignalHandler::GetProgramCounter(mcontext);
- its.fp = SignalHandler::GetFramePointer(mcontext);
- its.csp = SignalHandler::GetCStackPointer(mcontext);
- its.dsp = SignalHandler::GetDartStackPointer(mcontext);
- its.lr = SignalHandler::GetLinkRegister(mcontext);
- Profiler::SampleThread(thread, its);
+namespace {
+#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
+extern "C" {
+#endif
+void ThreadInterruptSignalHandler(int signal, siginfo_t* info, void* context_) {
+ if (signal != SIGPROF) {
+ return;
}
-};
+ Thread* thread = Thread::Current();
+ if (thread == NULL) {
+ return;
+ }
+ ThreadInterrupter::SampleBufferWriterScope scope;
+ if (!scope.CanSample()) {
+ return;
+ }
+ // Extract thread state.
+ ucontext_t* context = reinterpret_cast<ucontext_t*>(context_);
+ mcontext_t mcontext = context->uc_mcontext;
+ InterruptedThreadState its;
+ its.pc = SignalHandler::GetProgramCounter(mcontext);
+ its.fp = SignalHandler::GetFramePointer(mcontext);
+ its.csp = SignalHandler::GetCStackPointer(mcontext);
+ its.dsp = SignalHandler::GetDartStackPointer(mcontext);
+ its.lr = SignalHandler::GetLinkRegister(mcontext);
+ Profiler::SampleThread(thread, its);
+}
+#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
+} // extern "C"
+#endif
+} // namespace
bool ThreadInterrupter::IsDebuggerAttached() {
return false;
@@ -62,9 +74,19 @@
ASSERT((result == 0) || (result == ESRCH));
}
+#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
+// Defined in thread_interrupted_android_arm.S
+extern "C" void ThreadInterruptSignalHandlerTrampoline(int signal,
+ siginfo_t* info,
+ void* context_);
+#endif
+
void ThreadInterrupter::InstallSignalHandler() {
- SignalHandler::Install<
- ThreadInterrupterAndroid::ThreadInterruptSignalHandler>();
+#if defined(USE_SIGNAL_HANDLER_TRAMPOLINE)
+ SignalHandler::Install(&ThreadInterruptSignalHandlerTrampoline);
+#else
+ SignalHandler::Install(&ThreadInterruptSignalHandler);
+#endif
}
void ThreadInterrupter::RemoveSignalHandler() {
diff --git a/runtime/vm/thread_interrupter_android_arm.S b/runtime/vm/thread_interrupter_android_arm.S
new file mode 100644
index 0000000..7c4a200
--- /dev/null
+++ b/runtime/vm/thread_interrupter_android_arm.S
@@ -0,0 +1,43 @@
+// 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.
+
+// Work around for a bug in old kernels (only fixed in 3.18 Android kernel):
+//
+// Kernel does not clear If-Then execution state bits when entering ARM signal
+// handler which violates requirements imposed by ARM architecture reference.
+// Some CPUs look at these bits even while in ARM mode which causes them
+// to skip some instructions in the prologue of the signal handler.
+//
+// To work around the issue we insert enough NOPs in the prologue to ensure
+// that no actual instructions are skipped and then branch to the actual
+// signal handler.
+//
+// For the kernel patch that fixes the issue see:
+// http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=6ecf830e5029598732e04067e325d946097519cb
+//
+
+// Note: can't use DART_* defines here because this file does not include
+// globals.h.
+#if !defined(PRODUCT) && defined(__ARMEL__) && defined(__ANDROID__) && \
+ !defined(__thumb__)
+
+.text
+
+.globl ThreadInterruptSignalHandlerTrampoline
+.hidden ThreadInterruptSignalHandlerTrampoline
+.type ThreadInterruptSignalHandlerTrampoline, %function
+ThreadInterruptSignalHandlerTrampoline:
+ // IT (If-Then) instruction makes up to four instructions that follow it
+ // conditional.
+ nop
+ nop
+ nop
+ nop
+ // Tail-call the actual handler.
+ // Note: no need to use interworking because we know that we are not
+ // compiling for Thumb.
+ b ThreadInterruptSignalHandler
+.size ThreadInterruptSignalHandlerTrampoline,.-ThreadInterruptSignalHandlerTrampoline
+
+#endif
diff --git a/runtime/vm/thread_interrupter_linux.cc b/runtime/vm/thread_interrupter_linux.cc
index 300a3dd..ee623db 100644
--- a/runtime/vm/thread_interrupter_linux.cc
+++ b/runtime/vm/thread_interrupter_linux.cc
@@ -62,8 +62,7 @@
}
void ThreadInterrupter::InstallSignalHandler() {
- SignalHandler::Install<
- ThreadInterrupterLinux::ThreadInterruptSignalHandler>();
+ SignalHandler::Install(&ThreadInterrupterLinux::ThreadInterruptSignalHandler);
}
void ThreadInterrupter::RemoveSignalHandler() {
diff --git a/runtime/vm/thread_interrupter_macos.cc b/runtime/vm/thread_interrupter_macos.cc
index bc81f93..ba9ce5a 100644
--- a/runtime/vm/thread_interrupter_macos.cc
+++ b/runtime/vm/thread_interrupter_macos.cc
@@ -83,8 +83,7 @@
}
void ThreadInterrupter::InstallSignalHandler() {
- SignalHandler::Install<
- ThreadInterrupterMacOS::ThreadInterruptSignalHandler>();
+ SignalHandler::Install(&ThreadInterrupterMacOS::ThreadInterruptSignalHandler);
}
void ThreadInterrupter::RemoveSignalHandler() {
diff --git a/runtime/vm/type_testing_stubs.cc b/runtime/vm/type_testing_stubs.cc
index 48c073e..47598e4 100644
--- a/runtime/vm/type_testing_stubs.cc
+++ b/runtime/vm/type_testing_stubs.cc
@@ -5,6 +5,7 @@
#include <functional>
#include "vm/compiler/assembler/disassembler.h"
+#include "vm/hash_map.h"
#include "vm/longjump.h"
#include "vm/object_store.h"
#include "vm/stub_code.h"
@@ -20,8 +21,6 @@
namespace dart {
-DECLARE_FLAG(bool, disassemble_stubs);
-
TypeTestingStubNamer::TypeTestingStubNamer()
: lib_(Library::Handle()),
klass_(Class::Handle()),
@@ -308,6 +307,18 @@
return code.ptr();
}
+void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
+ compiler::Assembler* assembler,
+ compiler::UnresolvedPcRelativeCalls* unresolved_calls,
+ const Code& slow_type_test_stub,
+ HierarchyInfo* hi,
+ const Type& type,
+ const Class& type_class) {
+ BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
+ __ Jump(compiler::Address(
+ THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
+}
+
void TypeTestingStubGenerator::BuildOptimizedTypeTestStubFastCases(
compiler::Assembler* assembler,
HierarchyInfo* hi,
@@ -316,21 +327,24 @@
// These are handled via the TopTypeTypeTestStub!
ASSERT(!type.IsTopTypeForSubtyping());
+ if (type.IsObjectType()) {
+ ASSERT(type.IsNonNullable() &&
+ IsolateGroup::Current()->use_strict_null_safety_checks());
+ compiler::Label is_null;
+ __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
+ __ BranchIf(EQUAL, &is_null, compiler::Assembler::kNearJump);
+ __ Ret();
+ __ Bind(&is_null);
+ return; // No further checks needed.
+ }
+
// 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);
+ __ BranchIfNotSmi(TypeTestABI::kInstanceReg, &non_smi_value,
+ compiler::Assembler::kNearJump);
__ Ret();
__ Bind(&non_smi_value);
- } else if (type.IsObjectType()) {
- ASSERT(type.IsNonNullable() &&
- IsolateGroup::Current()->use_strict_null_safety_checks());
- compiler::Label continue_checking;
- __ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
- __ BranchIf(EQUAL, &continue_checking);
- __ Ret();
- __ Bind(&continue_checking);
-
} else {
// TODO(kustermann): Make more fast cases, e.g. Type::Number()
// is implemented by Smi.
@@ -343,52 +357,226 @@
/*include_abstract=*/false,
/*exclude_null=*/!Instance::NullIsAssignableTo(type));
- 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);
+ compiler::Label is_subtype, is_not_subtype;
+ const bool smi_is_ok =
+ Type::Handle(Type::SmiType()).IsSubtypeOf(type, Heap::kNew);
+ if (smi_is_ok) {
+ __ LoadClassIdMayBeSmi(TTSInternalRegs::kScratchReg,
+ TypeTestABI::kInstanceReg);
+ } else {
+ __ BranchIfSmi(TypeTestABI::kInstanceReg, &is_not_subtype);
+ __ LoadClassId(TTSInternalRegs::kScratchReg, TypeTestABI::kInstanceReg);
+ }
+ BuildOptimizedSubtypeRangeCheck(assembler, ranges,
+ TTSInternalRegs::kScratchReg, &is_subtype,
+ &is_not_subtype);
+ __ Bind(&is_subtype);
+ __ Ret();
+ __ Bind(&is_not_subtype);
} else {
- ASSERT(hi->CanUseGenericSubtypeRangeCheckFor(type));
-
- const intptr_t num_type_arguments = type_class.NumTypeArguments();
-
- const TypeArguments& ta = TypeArguments::Handle(type.arguments());
- ASSERT(ta.Length() == num_type_arguments);
-
BuildOptimizedSubclassRangeCheckWithTypeArguments(assembler, hi, type,
- type_class, ta);
+ type_class);
}
if (Instance::NullIsAssignableTo(type)) {
// Fast case for 'null'.
compiler::Label non_null;
__ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
- __ BranchIf(NOT_EQUAL, &non_null);
+ __ BranchIf(NOT_EQUAL, &non_null, compiler::Assembler::kNearJump);
__ Ret();
__ Bind(&non_null);
}
}
+static void CommentCheckedClasses(compiler::Assembler* assembler,
+ const CidRangeVector& ranges) {
+ if (!assembler->EmittingComments()) return;
+ Thread* const thread = Thread::Current();
+ ClassTable* const class_table = thread->isolate_group()->class_table();
+ Zone* const zone = thread->zone();
+ if (ranges.is_empty()) {
+ __ Comment("No valid cids to check");
+ return;
+ }
+ if ((ranges.length() == 1) && ranges[0].IsSingleCid()) {
+ const auto& cls = Class::Handle(zone, class_table->At(ranges[0].cid_start));
+ __ Comment("Checking for cid %" Pd " (%s)", cls.id(),
+ cls.ScrubbedNameCString());
+ return;
+ }
+ __ Comment("Checking for concrete finalized classes:");
+ auto& cls = Class::Handle(zone);
+ for (const auto& range : ranges) {
+ ASSERT(!range.IsIllegalRange());
+ for (classid_t cid = range.cid_start; cid <= range.cid_end; cid++) {
+ // Invalid entries can be included to keep range count low.
+ if (!class_table->HasValidClassAt(cid)) continue;
+ cls = class_table->At(cid);
+ if (cls.is_abstract()) continue; // Only output concrete classes.
+ __ Comment(" * %" Pd32 " (%s)", cid, cls.ScrubbedNameCString());
+ }
+ }
+}
+
+// Represents the following needs for runtime checks to see if an instance of
+// [cls] is a subtype of [type] that has type class [type_class]:
+//
+// * kCannotBeChecked: Instances of [cls] cannot be checked with any of the
+// currently implemented runtime checks, so must fall back on the runtime.
+//
+// * kNotSubtype: A [cls] instance is guaranteed to not be a subtype of [type]
+// regardless of any instance type arguments.
+//
+// * kCidCheckOnly: A [cls] instance is guaranteed to be a subtype of [type]
+// regardless of any instance type arguments.
+//
+// * kNeedsFinalization: Checking that an instance of [cls] is a subtype of
+// [type] requires instance type arguments, but [cls] is not finalized, and
+// so the appropriate type arguments field offset cannot be determined.
+//
+// * kInstanceTypeArgumentsAreSubtypes: [cls] implements a fully uninstantiated
+// type with type class [type_class] which can be directly instantiated with
+// the instance type arguments. Thus, each type argument of [type] should be
+// compared with the corresponding (index-wise) instance type argument.
+enum class CheckType {
+ kCannotBeChecked,
+ kNotSubtype,
+ kCidCheckOnly,
+ kNeedsFinalization,
+ kInstanceTypeArgumentsAreSubtypes,
+};
+
+// Returns a CheckType describing how to check instances of [to_check] as
+// subtypes of [type].
+static CheckType SubtypeChecksForClass(Zone* zone,
+ const Type& type,
+ const Class& type_class,
+ const Class& to_check) {
+ ASSERT_EQUAL(type.type_class_id(), type_class.id());
+ ASSERT(type_class.is_type_finalized());
+ ASSERT(!to_check.is_abstract());
+ ASSERT(to_check.is_type_finalized());
+ ASSERT(AbstractType::Handle(zone, to_check.RareType())
+ .IsSubtypeOf(AbstractType::Handle(zone, type_class.RareType()),
+ Heap::kNew));
+ if (!type_class.IsGeneric()) {
+ // All instances of [to_check] are subtypes of [type].
+ return CheckType::kCidCheckOnly;
+ }
+ if (to_check.FindInstantiationOf(zone, type_class,
+ /*only_super_classes=*/true)) {
+ // No need to check for type argument consistency, as [to_check] is the same
+ // as or a subclass of [type_class].
+ return to_check.is_finalized()
+ ? CheckType::kInstanceTypeArgumentsAreSubtypes
+ : CheckType::kCannotBeChecked;
+ }
+ auto& calculated_type =
+ AbstractType::Handle(zone, to_check.GetInstantiationOf(zone, type_class));
+ if (calculated_type.IsInstantiated()) {
+ if (type.IsInstantiated()) {
+ return calculated_type.IsSubtypeOf(type, Heap::kNew)
+ ? CheckType::kCidCheckOnly
+ : CheckType::kNotSubtype;
+ }
+ // TODO(dartbug.com/46920): Requires walking both types, checking
+ // corresponding instantiated parts at compile time (assuming uninstantiated
+ // parts check successfully) and then creating appropriate runtime checks
+ // for uninstantiated parts of [type].
+ return CheckType::kCannotBeChecked;
+ }
+ if (!to_check.is_finalized()) {
+ return CheckType::kNeedsFinalization;
+ }
+ ASSERT(to_check.NumTypeArguments() > 0);
+ ASSERT(compiler::target::Class::TypeArgumentsFieldOffset(to_check) !=
+ compiler::target::Class::kNoTypeArguments);
+ // If the calculated type arguments are a prefix of the declaration type
+ // arguments, then we can just treat the instance type arguments as if they
+ // were used to instantiate the type class during checking.
+ const auto& decl_type_args = TypeArguments::Handle(
+ zone, Type::Handle(zone, to_check.DeclarationType()).arguments());
+ const auto& calculated_type_args =
+ TypeArguments::Handle(zone, calculated_type.arguments());
+ const bool type_args_consistent = calculated_type_args.IsSubvectorEquivalent(
+ decl_type_args, 0, type_class.NumTypeArguments(),
+ TypeEquality::kCanonical);
+ // TODO(dartbug.com/46920): Currently we require subtyping to be checkable
+ // by comparing the instance type arguments against the type arguments of
+ // [type] piecewise, but we could check other cases as well.
+ return type_args_consistent ? CheckType::kInstanceTypeArgumentsAreSubtypes
+ : CheckType::kCannotBeChecked;
+}
+
+static void CommentSkippedClasses(compiler::Assembler* assembler,
+ const Type& type,
+ const Class& type_class,
+ const CidRangeVector& ranges) {
+ if (!assembler->EmittingComments() || ranges.is_empty()) return;
+ if (ranges.is_empty()) return;
+ ASSERT(type_class.is_implemented());
+ __ Comment("Not checking the following concrete implementors of %s:",
+ type_class.ScrubbedNameCString());
+ Thread* const thread = Thread::Current();
+ auto* const class_table = thread->isolate_group()->class_table();
+ Zone* const zone = thread->zone();
+ auto& cls = Class::Handle(zone);
+ auto& calculated_type = Type::Handle(zone);
+ for (const auto& range : ranges) {
+ ASSERT(!range.IsIllegalRange());
+ for (classid_t cid = range.cid_start; cid <= range.cid_end; cid++) {
+ // Invalid entries can be included to keep range count low.
+ if (!class_table->HasValidClassAt(cid)) continue;
+ cls = class_table->At(cid);
+ if (cls.is_abstract()) continue; // Only output concrete classes.
+ ASSERT(cls.is_type_finalized());
+ TextBuffer buffer(128);
+ buffer.Printf(" * %" Pd32 "(%s): ", cid, cls.ScrubbedNameCString());
+ switch (SubtypeChecksForClass(zone, type, type_class, cls)) {
+ case CheckType::kCannotBeChecked:
+ calculated_type = cls.GetInstantiationOf(zone, type_class);
+ buffer.AddString("cannot check that ");
+ calculated_type.PrintName(Object::kScrubbedName, &buffer);
+ buffer.AddString(" is a subtype of ");
+ type.PrintName(Object::kScrubbedName, &buffer);
+ break;
+ case CheckType::kNotSubtype:
+ calculated_type = cls.GetInstantiationOf(zone, type_class);
+ calculated_type.PrintName(Object::kScrubbedName, &buffer);
+ buffer.AddString(" is not a subtype of ");
+ type.PrintName(Object::kScrubbedName, &buffer);
+ break;
+ case CheckType::kNeedsFinalization:
+ buffer.AddString("is not finalized");
+ break;
+ case CheckType::kInstanceTypeArgumentsAreSubtypes:
+ buffer.AddString("was not finalized during class splitting");
+ break;
+ default:
+ // Either the CheckType was kCidCheckOnly, which should never happen
+ // since it only requires type finalization, or a new CheckType has
+ // been added.
+ UNREACHABLE();
+ break;
+ }
+ __ Comment("%s", buffer.buffer());
+ }
+ }
+}
+
+// Builds a cid range check for the concrete subclasses and implementors of
+// type. Assumes cid to check is already in TTSInternalRegs::kScratchReg. Falls
+// through or jumps to check_succeeded if the range contains the cid, else
+// jumps to check_failed.
void TypeTestingStubGenerator::BuildOptimizedSubtypeRangeCheck(
compiler::Assembler* assembler,
const CidRangeVector& ranges,
- bool smi_is_ok) {
- compiler::Label cid_range_failed, is_subtype;
-
- if (smi_is_ok) {
- __ LoadClassIdMayBeSmi(TTSInternalRegs::kScratchReg,
- TypeTestABI::kInstanceReg);
- } else {
- __ BranchIfSmi(TypeTestABI::kInstanceReg, &cid_range_failed);
- __ LoadClassId(TTSInternalRegs::kScratchReg, TypeTestABI::kInstanceReg);
- }
-
+ Register class_id_reg,
+ compiler::Label* check_succeeded,
+ compiler::Label* check_failed) {
+ CommentCheckedClasses(assembler, ranges);
FlowGraphCompiler::GenerateCidRangesCheck(
- assembler, TTSInternalRegs::kScratchReg, ranges, &is_subtype,
- &cid_range_failed, true);
- __ Bind(&is_subtype);
- __ Ret();
- __ Bind(&cid_range_failed);
+ assembler, class_id_reg, ranges, check_succeeded, check_failed, true);
}
void TypeTestingStubGenerator::
@@ -396,73 +584,343 @@
compiler::Assembler* assembler,
HierarchyInfo* hi,
const Type& type,
- const Class& type_class,
- const TypeArguments& ta) {
- // a) First we make a quick sub*class* cid-range check.
+ const Class& type_class) {
+ ASSERT(hi->CanUseGenericSubtypeRangeCheckFor(type));
compiler::Label check_failed;
- ASSERT(!type_class.is_implemented());
- const CidRangeVector& ranges = hi->SubclassRangesForClass(type_class);
- BuildOptimizedSubclassRangeCheck(assembler, ranges, &check_failed);
- // fall through to continue
+ // a) First we perform subtype cid-range checks and load the instance type
+ // arguments based on which check succeeded.
+ __ LoadClassIdMayBeSmi(TTSInternalRegs::kScratchReg,
+ TypeTestABI::kInstanceReg);
+ compiler::Label load_succeeded;
+ if (BuildLoadInstanceTypeArguments(assembler, hi, type, type_class,
+ TTSInternalRegs::kScratchReg,
+ TTSInternalRegs::kInstanceTypeArgumentsReg,
+ &load_succeeded, &check_failed)) {
+ // Only build type argument checking if any checked cid ranges require it.
+ __ Bind(&load_succeeded);
- // b) Then we'll load the values for the type parameters.
- __ LoadCompressedFieldFromOffset(
- TTSInternalRegs::kInstanceTypeArgumentsReg, TypeTestABI::kInstanceReg,
- compiler::target::Class::TypeArgumentsFieldOffset(type_class));
+ // b) We check for "rare" types, where the instance type arguments are null.
+ //
+ // The kernel frontend should fill in any non-assigned type parameters on
+ // construction with dynamic/Object, so we should never get the null type
+ // argument vector in created instances.
+ //
+ // TODO(kustermann): We could consider not using "null" as type argument
+ // vector representing all-dynamic to avoid this extra check (which will be
+ // uncommon because most Dart code in 2.0 will be strongly typed)!
+ __ CompareObject(TTSInternalRegs::kInstanceTypeArgumentsReg,
+ Object::null_object());
+ const Type& rare_type = Type::Handle(Type::RawCast(type_class.RareType()));
+ if (rare_type.IsSubtypeOf(type, Heap::kNew)) {
+ compiler::Label process_done;
+ __ BranchIf(NOT_EQUAL, &process_done, compiler::Assembler::kNearJump);
+ __ Ret();
+ __ Bind(&process_done);
+ } else {
+ __ BranchIf(EQUAL, &check_failed);
+ }
- // The kernel frontend should fill in any non-assigned type parameters on
- // construction with dynamic/Object, so we should never get the null type
- // argument vector in created instances.
- //
- // TODO(kustermann): We could consider not using "null" as type argument
- // vector representing all-dynamic to avoid this extra check (which will be
- // uncommon because most Dart code in 2.0 will be strongly typed)!
- __ CompareObject(TTSInternalRegs::kInstanceTypeArgumentsReg,
- Object::null_object());
- const Type& rare_type = Type::Handle(Type::RawCast(type_class.RareType()));
- if (rare_type.IsSubtypeOf(type, Heap::kNew)) {
- compiler::Label process_done;
- __ BranchIf(NOT_EQUAL, &process_done);
+ // c) Then we'll check each value of the type argument.
+ AbstractType& type_arg = AbstractType::Handle();
+ const TypeArguments& ta = TypeArguments::Handle(type.arguments());
+ const intptr_t num_type_parameters = type_class.NumTypeParameters();
+ const intptr_t num_type_arguments = type_class.NumTypeArguments();
+ ASSERT(ta.Length() >= num_type_arguments);
+ for (intptr_t i = 0; i < num_type_parameters; ++i) {
+ const intptr_t type_param_value_offset_i =
+ num_type_arguments - num_type_parameters + i;
+
+ type_arg = ta.TypeAt(type_param_value_offset_i);
+ ASSERT(type_arg.IsTypeParameter() ||
+ hi->CanUseSubtypeRangeCheckFor(type_arg));
+
+ BuildOptimizedTypeArgumentValueCheck(
+ assembler, hi, type_arg, type_param_value_offset_i, &check_failed);
+ }
__ Ret();
- __ Bind(&process_done);
- } else {
- __ BranchIf(EQUAL, &check_failed);
}
- // c) Then we'll check each value of the type argument.
- AbstractType& type_arg = AbstractType::Handle();
-
- const intptr_t num_type_parameters = type_class.NumTypeParameters();
- const intptr_t num_type_arguments = type_class.NumTypeArguments();
- for (intptr_t i = 0; i < num_type_parameters; ++i) {
- const intptr_t type_param_value_offset_i =
- num_type_arguments - num_type_parameters + i;
-
- type_arg = ta.TypeAt(type_param_value_offset_i);
- ASSERT(type_arg.IsTypeParameter() ||
- hi->CanUseSubtypeRangeCheckFor(type_arg));
-
- BuildOptimizedTypeArgumentValueCheck(
- assembler, hi, type_arg, type_param_value_offset_i, &check_failed);
- }
- __ Ret();
-
// If anything fails.
__ Bind(&check_failed);
}
-void TypeTestingStubGenerator::BuildOptimizedSubclassRangeCheck(
- compiler::Assembler* assembler,
+// Splits [ranges] into multiple ranges in [output], where the concrete,
+// finalized classes in each range share the same type arguments field offset.
+//
+// The first range in [output] contains [type_class], if any do, and otherwise
+// prioritizes ranges that include predefined cids before ranges that only
+// contain user-defined classes.
+//
+// Any cids that do not have valid class table entries, correspond to abstract
+// or unfinalized classes, or have no TAV field offset are treated as don't
+// cares, in that the cid may appear in any of the CidRangeVectors as needed to
+// reduce the number of ranges.
+//
+// Note that CidRangeVectors are MallocGrowableArrays, so the elements in
+// output must be freed after use!
+static void SplitByTypeArgumentsFieldOffset(
+ Thread* T,
+ const Class& type_class,
const CidRangeVector& ranges,
- compiler::Label* check_failed) {
- __ LoadClassIdMayBeSmi(TTSInternalRegs::kScratchReg,
- TypeTestABI::kInstanceReg);
+ GrowableArray<CidRangeVector*>* output) {
+ ASSERT(output != nullptr);
+ ASSERT(!ranges.is_empty());
- compiler::Label is_subtype;
- FlowGraphCompiler::GenerateCidRangesCheck(
- assembler, TTSInternalRegs::kScratchReg, ranges, &is_subtype,
- check_failed, true);
- __ Bind(&is_subtype);
+ Zone* const Z = T->zone();
+ ClassTable* const class_table = T->isolate_group()->class_table();
+ IntMap<CidRangeVector*> offset_map(Z);
+ IntMap<intptr_t> predefined_offsets(Z);
+ IntMap<intptr_t> user_defined_offsets(Z);
+
+ auto add_to_vector = [&](intptr_t tav_offset, const CidRange& range) {
+ if (range.cid_start == -1) return;
+ ASSERT(tav_offset != compiler::target::Class::kNoTypeArguments);
+ if (CidRangeVector* vector = offset_map.Lookup(tav_offset)) {
+ vector->Add(range);
+ } else {
+ vector = new CidRangeVector(1);
+ vector->Add(range);
+ offset_map.Insert(tav_offset, vector);
+ }
+ };
+
+ auto increment_count = [&](intptr_t cid, intptr_t tav_offset) {
+ if (cid <= kNumPredefinedCids) {
+ predefined_offsets.Update(
+ {tav_offset, predefined_offsets.Lookup(tav_offset) + 1});
+ } else if (auto* const kv = predefined_offsets.LookupPair(tav_offset)) {
+ predefined_offsets.Update({kv->key, kv->value + 1});
+ } else {
+ user_defined_offsets.Update(
+ {tav_offset, user_defined_offsets.Lookup(tav_offset) + 1});
+ }
+ };
+
+ // First populate offset_map.
+ auto& cls = Class::Handle(Z);
+ for (const auto& range : ranges) {
+ intptr_t last_offset = compiler::target::Class::kNoTypeArguments;
+ intptr_t cid_start = -1;
+ intptr_t cid_end = -1;
+ for (intptr_t cid = range.cid_start; cid <= range.cid_end; cid++) {
+ if (!class_table->HasValidClassAt(cid)) continue;
+ cls = class_table->At(cid);
+ if (cls.is_abstract()) continue;
+ // Only finalized concrete classes are present due to the conditions on
+ // returning kInstanceTypeArgumentsAreSubtypes in SubtypeChecksForClass.
+ ASSERT(cls.is_finalized());
+ const intptr_t tav_offset =
+ compiler::target::Class::TypeArgumentsFieldOffset(cls);
+ if (tav_offset == compiler::target::Class::kNoTypeArguments) continue;
+ if (tav_offset == last_offset && cid_start >= 0) {
+ cid_end = cid;
+ increment_count(cid, tav_offset);
+ continue;
+ }
+ add_to_vector(last_offset, {cid_start, cid_end});
+ last_offset = tav_offset;
+ cid_start = cid_end = cid;
+ increment_count(cid, tav_offset);
+ }
+ add_to_vector(last_offset, {cid_start, cid_end});
+ }
+
+ ASSERT(!offset_map.IsEmpty());
+
+ // Add the CidRangeVector for the type_class's offset, if it has one.
+ if (!type_class.is_abstract() && type_class.is_finalized()) {
+ const intptr_t type_class_offset =
+ compiler::target::Class::TypeArgumentsFieldOffset(type_class);
+ ASSERT(predefined_offsets.LookupPair(type_class_offset) != nullptr ||
+ user_defined_offsets.LookupPair(type_class_offset) != nullptr);
+ CidRangeVector* const vector = offset_map.Lookup(type_class_offset);
+ ASSERT(vector != nullptr);
+ output->Add(vector);
+ // Remove this CidRangeVector from consideration in the following loops.
+ predefined_offsets.Remove(type_class_offset);
+ user_defined_offsets.Remove(type_class_offset);
+ }
+ // Now add CidRangeVectors that include predefined cids.
+ // For now, we do this in an arbitrary order, but we could use the counts
+ // to prioritize offsets that are more shared if desired.
+ auto predefined_it = predefined_offsets.GetIterator();
+ while (auto* const kv = predefined_it.Next()) {
+ CidRangeVector* const vector = offset_map.Lookup(kv->key);
+ ASSERT(vector != nullptr);
+ output->Add(vector);
+ }
+ // Finally, add CidRangeVectors that only include user-defined cids.
+ // For now, we do this in an arbitrary order, but we could use the counts
+ // to prioritize offsets that are more shared if desired.
+ auto user_defined_it = user_defined_offsets.GetIterator();
+ while (auto* const kv = user_defined_it.Next()) {
+ CidRangeVector* const vector = offset_map.Lookup(kv->key);
+ ASSERT(vector != nullptr);
+ output->Add(vector);
+ }
+ ASSERT(output->length() > 0);
+}
+
+// Given [type], its type class [type_class], and a CidRangeVector [ranges],
+// populates the output CidRangeVectors from cids in [ranges], based on what
+// runtime checks are needed to determine whether the runtime type of
+// an instance is a subtype of [type].
+//
+// Concrete, type finalized classes whose cids are added to [cid_check_only]
+// implement a particular instantiation of [type_class] that is guaranteed to
+// be a subtype of [type]. Thus, these instances do not require any checking
+// of type arguments.
+//
+// Concrete, finalized classes whose cids are added to [type_argument_checks]
+// implement a fully uninstantiated version of [type_class] that can be directly
+// instantiated with the type arguments of the class's instance. Thus, each
+// type argument of [type] should be checked against the corresponding
+// instance type argument.
+//
+// Classes whose cids are in [not_checked]:
+// * Instances of the class are guaranteed to not be a subtype of [type].
+// * The class is not finalized.
+// * The subtype relation cannot be checked with our current approach and
+// thus the stub must fall back to the STC/VM runtime.
+//
+// Any cids that do not have valid class table entries or correspond to
+// abstract classes are treated as don't cares, in that the cid may or may not
+// appear as needed to reduce the number of ranges.
+static void SplitOnTypeArgumentTests(HierarchyInfo* hi,
+ const Type& type,
+ const Class& type_class,
+ const CidRangeVector& ranges,
+ CidRangeVector* cid_check_only,
+ CidRangeVector* type_argument_checks,
+ CidRangeVector* not_checked) {
+ ASSERT(type_class.is_implemented()); // No need to split if not implemented.
+ ASSERT(cid_check_only->is_empty());
+ ASSERT(type_argument_checks->is_empty());
+ ASSERT(not_checked->is_empty());
+ ClassTable* const class_table = hi->thread()->isolate_group()->class_table();
+ Zone* const zone = hi->thread()->zone();
+ auto& to_check = Class::Handle(zone);
+ auto add_cid_range = [&](CheckType check, const CidRange& range) {
+ if (range.cid_start == -1) return;
+ switch (check) {
+ case CheckType::kCidCheckOnly:
+ cid_check_only->Add(range);
+ break;
+ case CheckType::kInstanceTypeArgumentsAreSubtypes:
+ type_argument_checks->Add(range);
+ break;
+ default:
+ not_checked->Add(range);
+ }
+ };
+ for (const auto& range : ranges) {
+ CheckType last_check = CheckType::kCannotBeChecked;
+ classid_t cid_start = -1, cid_end = -1;
+ for (classid_t cid = range.cid_start; cid <= range.cid_end; cid++) {
+ // Invalid entries can be included to keep range count low.
+ if (!class_table->HasValidClassAt(cid)) continue;
+ to_check = class_table->At(cid);
+ if (to_check.is_abstract()) continue;
+ const CheckType current_check =
+ SubtypeChecksForClass(zone, type, type_class, to_check);
+ ASSERT(current_check != CheckType::kInstanceTypeArgumentsAreSubtypes ||
+ to_check.is_finalized());
+ if (last_check == current_check && cid_start >= 0) {
+ cid_end = cid;
+ continue;
+ }
+ add_cid_range(last_check, {cid_start, cid_end});
+ last_check = current_check;
+ cid_start = cid_end = cid;
+ }
+ add_cid_range(last_check, {cid_start, cid_end});
+ }
+}
+
+bool TypeTestingStubGenerator::BuildLoadInstanceTypeArguments(
+ compiler::Assembler* assembler,
+ HierarchyInfo* hi,
+ const Type& type,
+ const Class& type_class,
+ const Register class_id_reg,
+ const Register instance_type_args_reg,
+ compiler::Label* load_succeeded,
+ compiler::Label* load_failed) {
+ const CidRangeVector& ranges =
+ hi->SubtypeRangesForClass(type_class, /*include_abstract=*/false,
+ !Instance::NullIsAssignableTo(type));
+ if (!type_class.is_implemented()) {
+ ASSERT(type_class.is_finalized());
+ const intptr_t tav_offset =
+ compiler::target::Class::TypeArgumentsFieldOffset(type_class);
+ compiler::Label is_subtype;
+ BuildOptimizedSubtypeRangeCheck(assembler, ranges, class_id_reg,
+ &is_subtype, load_failed);
+ __ Bind(&is_subtype);
+ if (tav_offset != compiler::target::Class::kNoTypeArguments) {
+ // The class and its subclasses have trivially consistent type arguments.
+ __ LoadCompressedFieldFromOffset(instance_type_args_reg,
+ TypeTestABI::kInstanceReg, tav_offset);
+ return true;
+ } else {
+ // Not a generic type, so cid checks are sufficient.
+ __ Ret();
+ return false;
+ }
+ }
+ Thread* const T = hi->thread();
+ Zone* const Z = T->zone();
+ CidRangeVector cid_checks_only, type_argument_checks, not_checked;
+ SplitOnTypeArgumentTests(hi, type, type_class, ranges, &cid_checks_only,
+ &type_argument_checks, ¬_checked);
+ if (!cid_checks_only.is_empty()) {
+ compiler::Label is_subtype, keep_looking;
+ compiler::Label* check_failed =
+ type_argument_checks.is_empty() ? load_failed : &keep_looking;
+ BuildOptimizedSubtypeRangeCheck(assembler, cid_checks_only, class_id_reg,
+ &is_subtype, check_failed);
+ __ Bind(&is_subtype);
+ __ Ret();
+ __ Bind(&keep_looking);
+ }
+ if (!type_argument_checks.is_empty()) {
+ GrowableArray<CidRangeVector*> vectors;
+ SplitByTypeArgumentsFieldOffset(T, type_class, type_argument_checks,
+ &vectors);
+ ASSERT(vectors.length() > 0);
+ ClassTable* const class_table = T->isolate_group()->class_table();
+ auto& cls = Class::Handle(Z);
+ for (intptr_t i = 0; i < vectors.length(); i++) {
+ CidRangeVector* const vector = vectors[i];
+ ASSERT(!vector->is_empty());
+ const intptr_t first_cid = vector->At(0).cid_start;
+ ASSERT(class_table->HasValidClassAt(first_cid));
+ cls = class_table->At(first_cid);
+ ASSERT(cls.is_finalized());
+ const intptr_t tav_offset =
+ compiler::target::Class::TypeArgumentsFieldOffset(cls);
+ compiler::Label load_tav, keep_looking;
+ // For the last vector, just jump to load_failed if the check fails
+ // and avoid emitting a jump to load_succeeded.
+ compiler::Label* check_failed =
+ i < vectors.length() - 1 ? &keep_looking : load_failed;
+ BuildOptimizedSubtypeRangeCheck(assembler, *vector, class_id_reg,
+ &load_tav, check_failed);
+ __ Bind(&load_tav);
+ __ LoadCompressedFieldFromOffset(instance_type_args_reg,
+ TypeTestABI::kInstanceReg, tav_offset);
+ if (i < vectors.length() - 1) {
+ __ Jump(load_succeeded);
+ __ Bind(&keep_looking);
+ }
+ // Free the CidRangeVector allocated by SplitByTypeArgumentsFieldOffset.
+ delete vector;
+ }
+ }
+ if (!not_checked.is_empty()) {
+ CommentSkippedClasses(assembler, type, type_class, not_checked);
+ }
+ return !type_argument_checks.is_empty();
}
// Generate code to verify that instance's type argument is a subtype of
@@ -477,9 +935,13 @@
return;
}
- // If the upper bound is a type parameter and its value is "dynamic"
- // we always succeed.
- compiler::Label is_dynamic;
+ if (assembler->EmittingComments()) {
+ TextBuffer buffer(128);
+ buffer.Printf("Generating check for type argument %" Pd ": ",
+ type_param_value_offset_i);
+ type_arg.PrintName(Object::kScrubbedName, &buffer);
+ __ Comment("%s", buffer.buffer());
+ }
if (type_arg.IsTypeParameter()) {
const TypeParameter& type_param = TypeParameter::Cast(type_arg);
const Register kTypeArgumentsReg =
@@ -487,9 +949,12 @@
? TypeTestABI::kInstantiatorTypeArgumentsReg
: TypeTestABI::kFunctionTypeArgumentsReg;
+ compiler::Label is_dynamic;
__ CompareObject(kTypeArgumentsReg, Object::null_object());
- __ BranchIf(EQUAL, &is_dynamic);
+ __ BranchIf(EQUAL, &is_dynamic, compiler::Assembler::kNearJump);
+ // TODO(dartbug.com/46920): Currently only canonical equality (identity)
+ // is checked.
__ LoadCompressedFieldFromOffset(
TTSInternalRegs::kScratchReg, kTypeArgumentsReg,
compiler::target::TypeArguments::type_at_offset(type_param.index()));
@@ -499,6 +964,7 @@
compiler::target::TypeArguments::type_at_offset(
type_param_value_offset_i));
__ BranchIf(NOT_EQUAL, check_failed);
+ __ Bind(&is_dynamic);
} else {
const Class& type_class = Class::Handle(type_arg.type_class());
const bool null_is_assignable = Instance::NullIsAssignableTo(type_arg);
@@ -525,9 +991,9 @@
// Never is a bottom type.
__ CompareImmediate(TTSInternalRegs::kScratchReg, kNeverCid);
__ BranchIf(EQUAL, &is_subtype);
- FlowGraphCompiler::GenerateCidRangesCheck(
- assembler, TTSInternalRegs::kScratchReg, ranges, &is_subtype,
- check_failed, true);
+ BuildOptimizedSubtypeRangeCheck(assembler, ranges,
+ TTSInternalRegs::kScratchReg, &is_subtype,
+ check_failed);
__ Bind(&is_subtype);
// Weak NNBD mode uses LEGACY_SUBTYPE which ignores nullability.
@@ -548,8 +1014,6 @@
__ BranchIf(EQUAL, check_failed);
}
}
-
- __ Bind(&is_dynamic);
}
void RegisterTypeArgumentsUse(const Function& function,
diff --git a/runtime/vm/type_testing_stubs.h b/runtime/vm/type_testing_stubs.h
index eaa75c4..74cc0c0 100644
--- a/runtime/vm/type_testing_stubs.h
+++ b/runtime/vm/type_testing_stubs.h
@@ -73,27 +73,29 @@
static void BuildOptimizedSubtypeRangeCheck(compiler::Assembler* assembler,
const CidRangeVector& ranges,
- bool smi_is_ok);
+ Register class_id_reg,
+ compiler::Label* check_succeeded,
+ compiler::Label* check_failed);
static void BuildOptimizedSubclassRangeCheckWithTypeArguments(
compiler::Assembler* assembler,
HierarchyInfo* hi,
const Type& type,
- const Class& type_class,
- const TypeArguments& type_arguments);
+ const Class& type_class);
- static void BuildOptimizedSubclassRangeCheckWithTypeArguments(
+ // Falls through or jumps to load_succeeded if load succeeds, otherwise jumps
+ // to load_failed. Returns from the stub for checked cid ranges which do not
+ // require checking the instance type arguments. Returns whether any cid
+ // ranges require type argument checking.
+ static bool BuildLoadInstanceTypeArguments(
compiler::Assembler* assembler,
HierarchyInfo* hi,
const Type& type,
const Class& type_class,
- const TypeArguments& type_arguments,
const Register class_id_reg,
- const Register instance_type_args_reg);
-
- static void BuildOptimizedSubclassRangeCheck(compiler::Assembler* assembler,
- const CidRangeVector& ranges,
- compiler::Label* check_failed);
+ const Register instance_type_args_reg,
+ compiler::Label* load_succeeded,
+ compiler::Label* load_failed);
static void BuildOptimizedTypeArgumentValueCheck(
compiler::Assembler* assembler,
diff --git a/runtime/vm/type_testing_stubs_test.cc b/runtime/vm/type_testing_stubs_test.cc
index 9e807ad..480c1d0 100644
--- a/runtime/vm/type_testing_stubs_test.cc
+++ b/runtime/vm/type_testing_stubs_test.cc
@@ -33,23 +33,32 @@
class TraceStubInvocationScope : public ValueObject {
public:
- TraceStubInvocationScope() : old_flag_value_(FLAG_trace_type_checks) {
+ TraceStubInvocationScope()
+ : old_trace_type_checks_(FLAG_trace_type_checks),
+ old_disassemble_stubs_(FLAG_disassemble_stubs) {
if (FLAG_trace_type_testing_stub_tests) {
#if defined(DEBUG)
FLAG_trace_type_checks = true;
#endif
+#if defined(FORCE_INCLUDE_DISASSEMBLER) || !defined(PRODUCT)
+ FLAG_disassemble_stubs = true;
+#endif
}
}
~TraceStubInvocationScope() {
if (FLAG_trace_type_testing_stub_tests) {
#if defined(DEBUG)
- FLAG_trace_type_checks = old_flag_value_;
+ FLAG_trace_type_checks = old_trace_type_checks_;
+#endif
+#if defined(FORCE_INCLUDE_DISASSEMBLER) || !defined(PRODUCT)
+ FLAG_disassemble_stubs = old_disassemble_stubs_;
#endif
}
}
private:
- const bool old_flag_value_;
+ const bool old_trace_type_checks_;
+ const bool old_disassemble_stubs_;
};
#define __ assembler->
@@ -184,7 +193,7 @@
*tav = tav->Canonicalize(Thread::Current(), nullptr);
}
-struct TTSTestCase : public ValueObject {
+struct TTSTestCase {
const Instance& instance;
const TypeArguments& instantiator_tav;
const TypeArguments& function_tav;
@@ -255,7 +264,7 @@
if (cls.NumTypeArguments() == 0) {
return true;
}
- return instance.GetTypeArguments() != other.instance.GetTypeArguments();
+ return instance.GetTypeArguments() == other.instance.GetTypeArguments();
}
bool HasSTCEntry(const SubtypeTestCache& cache,
@@ -291,6 +300,9 @@
Object::null_type_arguments(),
Object::null_type_arguments(), out_index, out_result);
}
+
+ private:
+ DISALLOW_ALLOCATION();
};
// Inherits should_specialize from original.
@@ -392,8 +404,10 @@
last_tested_type_.SetTypeTestingStub(specializing_stub);
InvokeStubHelper(test_case,
/*is_lazy_specialization=*/test_case.should_specialize);
- if (test_case.should_fail) {
- // We only respecialize on successful checks.
+ if (test_case.should_fail || test_case.instance.IsNull()) {
+ // We only specialize if we go to runtime and the runtime check
+ // succeeds. The lazy specialization stub for nullable types has a
+ // special fast case for null that skips the runtime.
EXPECT(new_tts_stub_.ptr() == specializing_stub.ptr());
} else if (test_case.should_specialize) {
// Specializing test cases should never result in a default TTS.
@@ -512,9 +526,11 @@
if (test_case.should_fail) {
EXPECT(last_result_.IsError());
EXPECT(last_result_.IsUnhandledException());
- const auto& error =
- Instance::Handle(UnhandledException::Cast(last_result_).exception());
- EXPECT(strstr(error.ToCString(), "_TypeError"));
+ if (last_result_.IsUnhandledException()) {
+ const auto& error = Instance::Handle(
+ UnhandledException::Cast(last_result_).exception());
+ EXPECT(strstr(error.ToCString(), "_TypeError"));
+ }
} else {
EXPECT(new_tts_stub_.ptr() != StubCode::LazySpecializeTypeTest().ptr());
ReportModifiedRegisters(modified_abi_regs());
@@ -543,6 +559,39 @@
}
}
+ void ReportMissingOrChangedEntries(const SubtypeTestCache& old_cache,
+ const SubtypeTestCache& new_cache) {
+ auto& cid_or_sig = Object::Handle(zone());
+ auto& type = AbstractType::Handle(zone());
+ auto& instance_type_args = TypeArguments::Handle(zone());
+ auto& instantiator_type_args = TypeArguments::Handle(zone());
+ auto& function_type_args = TypeArguments::Handle(zone());
+ auto& instance_parent_type_args = TypeArguments::Handle(zone());
+ auto& instance_delayed_type_args = TypeArguments::Handle(zone());
+ auto& old_result = Bool::Handle(zone());
+ auto& new_result = Bool::Handle(zone());
+ SafepointMutexLocker ml(
+ thread_->isolate_group()->subtype_test_cache_mutex());
+ for (intptr_t i = 0; i < old_cache.NumberOfChecks(); i++) {
+ old_cache.GetCheck(0, &cid_or_sig, &type, &instance_type_args,
+ &instantiator_type_args, &function_type_args,
+ &instance_parent_type_args,
+ &instance_delayed_type_args, &old_result);
+ intptr_t new_index;
+ if (!new_cache.HasCheck(
+ cid_or_sig, type, instance_type_args, instantiator_type_args,
+ function_type_args, instance_parent_type_args,
+ instance_delayed_type_args, &new_index, &new_result)) {
+ dart::Expect(__FILE__, __LINE__)
+ .Fail("New STC is missing check in old STC");
+ }
+ if (old_result.value() != new_result.value()) {
+ dart::Expect(__FILE__, __LINE__)
+ .Fail("New STC has different result from old STC");
+ }
+ }
+ }
+
void ReportUnexpectedSTCChanges(const TTSTestCase& test_case,
bool is_lazy_specialization = false) {
ASSERT(!test_case.should_be_false_negative ||
@@ -552,47 +601,27 @@
!is_lazy_specialization && test_case.should_be_false_negative;
if (should_update_stc && !had_stc_entry) {
// We should have changed the STC to include the new entry.
- EXPECT((previous_stc_.IsNull() && !last_stc_.IsNull()) ||
- previous_stc_.cache() != last_stc_.cache());
- // We only should have added one check.
- EXPECT_EQ(previous_stc_.IsNull() ? 1 : previous_stc_.NumberOfChecks() + 1,
- last_stc_.NumberOfChecks());
- if (!previous_stc_.IsNull()) {
- // Make sure all the checks in the previous STC are still there.
- auto& cid_or_sig = Object::Handle(zone());
- auto& type = AbstractType::Handle(zone());
- auto& instance_type_args = TypeArguments::Handle(zone());
- auto& instantiator_type_args = TypeArguments::Handle(zone());
- auto& function_type_args = TypeArguments::Handle(zone());
- auto& instance_parent_type_args = TypeArguments::Handle(zone());
- auto& instance_delayed_type_args = TypeArguments::Handle(zone());
- auto& old_result = Bool::Handle(zone());
- auto& new_result = Bool::Handle(zone());
- SafepointMutexLocker ml(
- thread_->isolate_group()->subtype_test_cache_mutex());
- for (intptr_t i = 0; i < previous_stc_.NumberOfChecks(); i++) {
- previous_stc_.GetCheck(0, &cid_or_sig, &type, &instance_type_args,
- &instantiator_type_args, &function_type_args,
- &instance_parent_type_args,
- &instance_delayed_type_args, &old_result);
- intptr_t new_index;
- if (!last_stc_.HasCheck(
- cid_or_sig, type, instance_type_args, instantiator_type_args,
- function_type_args, instance_parent_type_args,
- instance_delayed_type_args, &new_index, &new_result)) {
- dart::Expect(__FILE__, __LINE__)
- .Fail("New STC is missing check in old STC");
- }
- if (old_result.value() != new_result.value()) {
- dart::Expect(__FILE__, __LINE__)
- .Fail("New STC has different result from old STC");
- }
+ EXPECT(!last_stc_.IsNull());
+ if (!last_stc_.IsNull()) {
+ EXPECT(previous_stc_.IsNull() ||
+ previous_stc_.cache() != last_stc_.cache());
+ // We only should have added one check.
+ EXPECT_EQ(
+ previous_stc_.IsNull() ? 1 : previous_stc_.NumberOfChecks() + 1,
+ last_stc_.NumberOfChecks());
+ if (!previous_stc_.IsNull()) {
+ // Make sure all the checks in the previous STC are still there.
+ ReportMissingOrChangedEntries(previous_stc_, last_stc_);
}
}
} else {
// Whatever STC existed before, if any, should be unchanged.
- EXPECT((previous_stc_.IsNull() && last_stc_.IsNull()) ||
- previous_stc_.cache() == last_stc_.cache());
+ if (previous_stc_.IsNull()) {
+ EXPECT(last_stc_.IsNull());
+ } else {
+ EXPECT(!last_stc_.IsNull() &&
+ previous_stc_.cache() == last_stc_.cache());
+ }
}
// False negatives should always be an STC hit when not lazily
@@ -602,12 +631,18 @@
if ((!should_update_stc && has_stc_entry) ||
(should_update_stc && !has_stc_entry)) {
TextBuffer buffer(128);
- buffer.Printf("%s entry for %s, got:\n",
- should_update_stc ? "Expected" : "Did not expect",
- type_.ToCString());
- for (intptr_t i = 0; i < last_stc_.NumberOfChecks(); i++) {
- last_stc_.WriteCurrentEntryToBuffer(zone(), &buffer, i);
+ buffer.Printf(
+ "%s entry for %s, got:",
+ test_case.should_be_false_negative ? "Expected" : "Did not expect",
+ type_.ToCString());
+ if (last_stc_.IsNull()) {
+ buffer.AddString(" null");
+ } else {
buffer.AddString("\n");
+ for (intptr_t i = 0; i < last_stc_.NumberOfChecks(); i++) {
+ last_stc_.WriteCurrentEntryToBuffer(zone(), &buffer, i);
+ buffer.AddString("\n");
+ }
}
dart::Expect(__FILE__, __LINE__).Fail("%s", buffer.buffer());
}
@@ -629,14 +664,34 @@
Object& last_result_;
};
-// Tests three situations in turn with the same test case:
+// Tests three situations in turn with the test case and with an
+// appropriate null object test:
// 1) Install the lazy specialization stub for JIT and test.
// 2) Test again without installing a stub, so using the stub resulting from 1.
// 3) Install an eagerly specialized stub, similar to AOT mode but keeping any
// STC created by the earlier steps, and test.
static void RunTTSTest(const AbstractType& dst_type,
const TTSTestCase& test_case) {
+ bool null_should_fail = !Instance::NullIsAssignableTo(
+ dst_type, test_case.instantiator_tav, test_case.function_tav);
+
+ const TTSTestCase null_test(
+ Instance::Handle(), test_case.instantiator_tav, test_case.function_tav,
+ test_case.should_specialize, null_should_fail,
+ // Null is never a false negative.
+ /*should_be_false_negative=*/false,
+ // Since null is never a false negative, it can't trigger
+ // respecialization.
+ /*should_respecialize=*/false);
+
TTSTestState state(Thread::Current(), dst_type);
+ // First check the null case. This should _never_ create an STC.
+ state.InvokeLazilySpecializedStub(null_test);
+ state.InvokeExistingStub(null_test);
+ state.InvokeEagerlySpecializedStub(null_test);
+ EXPECT(state.last_stc().IsNull());
+
+ // Now run the actual test case.
state.InvokeLazilySpecializedStub(test_case);
state.InvokeExistingStub(test_case);
state.InvokeEagerlySpecializedStub(test_case);
@@ -824,24 +879,15 @@
auto& type_dynamic_t =
AbstractType::Handle(Type::New(class_i, tav_dynamic_t));
FinalizeAndCanonicalize(&type_dynamic_t);
- RunTTSTest(type_dynamic_t, FalseNegative({obj_i, tav_object, tav_null,
- /*should_specialize=*/false}));
- RunTTSTest(type_dynamic_t, Failure({obj_i2, tav_object, tav_null,
- /*should_specialize=*/false}));
- RunTTSTest(type_dynamic_t, Failure({obj_base_int, tav_object, tav_null,
- /*should_specialize=*/false}));
- RunTTSTest(type_dynamic_t, Failure({obj_a, tav_object, tav_null,
- /*should_specialize=*/false}));
- RunTTSTest(type_dynamic_t, Failure({obj_a1, tav_object, tav_null,
- /*should_specialize=*/false}));
- RunTTSTest(type_dynamic_t, FalseNegative({obj_a2, tav_object, tav_null,
- /*should_specialize=*/false}));
- RunTTSTest(type_dynamic_t, Failure({obj_b, tav_object, tav_null,
- /*should_specialize=*/false}));
- RunTTSTest(type_dynamic_t, Failure({obj_b1, tav_object, tav_null,
- /*should_specialize=*/false}));
- RunTTSTest(type_dynamic_t, FalseNegative({obj_b2, tav_object, tav_null,
- /*should_specialize=*/false}));
+ RunTTSTest(type_dynamic_t, FalseNegative({obj_i, tav_object, tav_null}));
+ RunTTSTest(type_dynamic_t, Failure({obj_i2, tav_object, tav_null}));
+ RunTTSTest(type_dynamic_t, Failure({obj_base_int, tav_object, tav_null}));
+ RunTTSTest(type_dynamic_t, Failure({obj_a, tav_object, tav_null}));
+ RunTTSTest(type_dynamic_t, Failure({obj_a1, tav_object, tav_null}));
+ RunTTSTest(type_dynamic_t, FalseNegative({obj_a2, tav_object, tav_null}));
+ RunTTSTest(type_dynamic_t, Failure({obj_b, tav_object, tav_null}));
+ RunTTSTest(type_dynamic_t, Failure({obj_b1, tav_object, tav_null}));
+ RunTTSTest(type_dynamic_t, FalseNegative({obj_b2, tav_object, tav_null}));
// obj as Object (with null safety)
auto isolate_group = IsolateGroup::Current();
@@ -978,8 +1024,10 @@
RunTTSTest(type_base_b, {obj_base_int, tav_null, tav_null});
RunTTSTest(type_base_b, Failure({obj_i2, tav_null, tav_null}));
- // We do not generate TTS for uninstantiated types if we would need to use
- // subtype range checks for the class of the interface type.
+ // We generate TTS for implemented classes and uninstantiated types, but
+ // any class that implements the type class but does not match in both
+ // instance TAV offset and type argument indices is guaranteed to be a
+ // false negative.
//
// obj as I<dynamic, String> // I is generic & implemented.
// obj as Base<A2<T>> // A2<T> is not instantiated.
@@ -993,11 +1041,9 @@
type_i_dynamic_string = type_i_dynamic_string.ToNullability(
Nullability::kNonNullable, Heap::kNew);
FinalizeAndCanonicalize(&type_i_dynamic_string);
- RunTTSTest(
- type_i_dynamic_string,
- FalseNegative({obj_i, tav_null, tav_null, /*should_specialize=*/false}));
- RunTTSTest(type_i_dynamic_string, Failure({obj_base_int, tav_null, tav_null,
- /*should_specialize=*/false}));
+ RunTTSTest(type_i_dynamic_string, {obj_i, tav_null, tav_null});
+ RunTTSTest(type_i_dynamic_string,
+ Failure({obj_base_int, tav_null, tav_null}));
// <...> as Base<A2<T>>
const auto& tav_t = TypeArguments::Handle(TypeArguments::New(1));
@@ -1035,6 +1081,112 @@
/*should_specialize=*/false}));
}
+ISOLATE_UNIT_TEST_CASE(TTS_Generic_Implements_Instantiated_Interface) {
+ const char* kScript =
+ R"(
+ abstract class I<T> {}
+ class B<R> implements I<String> {}
+
+ createBInt() => B<int>();
+)";
+
+ const auto& root_library = Library::Handle(LoadTestScript(kScript));
+ const auto& class_i = Class::Handle(GetClass(root_library, "I"));
+ const auto& obj_b_int = Object::Handle(Invoke(root_library, "createBInt"));
+
+ const auto& tav_null = Object::null_type_arguments();
+ auto& tav_string = TypeArguments::Handle(TypeArguments::New(1));
+ tav_string.SetTypeAt(0, Type::Handle(Type::StringType()));
+ CanonicalizeTAV(&tav_string);
+
+ auto& type_i_string = Type::Handle(Type::New(class_i, tav_string));
+ FinalizeAndCanonicalize(&type_i_string);
+ const auto& type_i_t = Type::Handle(class_i.DeclarationType());
+
+ RunTTSTest(type_i_string, {obj_b_int, tav_null, tav_null});
+ // Optimized TTSees don't currently handle the case where the implemented
+ // type is known, but the type being checked requires instantiation at
+ // runtime.
+ RunTTSTest(type_i_t, FalseNegative({obj_b_int, tav_string, tav_null}));
+}
+
+ISOLATE_UNIT_TEST_CASE(TTS_Future) {
+ const char* kScript =
+ R"(
+ import "dart:async";
+
+ createFutureInt() => (() async => 3)();
+)";
+
+ const auto& root_library = Library::Handle(LoadTestScript(kScript));
+ const auto& class_future = Class::Handle(GetClass(root_library, "Future"));
+ const auto& obj_futureint =
+ Object::Handle(Invoke(root_library, "createFutureInt"));
+
+ const auto& type_nullable_object = Type::Handle(
+ IsolateGroup::Current()->object_store()->nullable_object_type());
+ const auto& type_int = Type::Handle(Type::IntType());
+ const auto& type_string = Type::Handle(Type::StringType());
+ const auto& type_num = Type::Handle(Type::Number());
+
+ const auto& tav_null = Object::null_type_arguments();
+ auto& tav_dynamic = TypeArguments::Handle(TypeArguments::New(1));
+ tav_dynamic.SetTypeAt(0, Object::dynamic_type());
+ CanonicalizeTAV(&tav_dynamic);
+ auto& tav_nullable_object = TypeArguments::Handle(TypeArguments::New(1));
+ tav_nullable_object.SetTypeAt(0, type_nullable_object);
+ CanonicalizeTAV(&tav_nullable_object);
+ auto& tav_int = TypeArguments::Handle(TypeArguments::New(1));
+ tav_int.SetTypeAt(0, type_int);
+ CanonicalizeTAV(&tav_int);
+ auto& tav_num = TypeArguments::Handle(TypeArguments::New(1));
+ tav_num.SetTypeAt(0, type_num);
+ CanonicalizeTAV(&tav_num);
+ auto& tav_string = TypeArguments::Handle(TypeArguments::New(1));
+ tav_string.SetTypeAt(0, type_string);
+ CanonicalizeTAV(&tav_string);
+
+ auto& type_future = Type::Handle(Type::New(class_future, tav_null));
+ FinalizeAndCanonicalize(&type_future);
+ auto& type_future_dynamic =
+ Type::Handle(Type::New(class_future, tav_dynamic));
+ FinalizeAndCanonicalize(&type_future_dynamic);
+ auto& type_future_nullable_object =
+ Type::Handle(Type::New(class_future, tav_nullable_object));
+ FinalizeAndCanonicalize(&type_future_nullable_object);
+ auto& type_future_int = Type::Handle(Type::New(class_future, tav_int));
+ FinalizeAndCanonicalize(&type_future_int);
+ auto& type_future_string = Type::Handle(Type::New(class_future, tav_string));
+ FinalizeAndCanonicalize(&type_future_string);
+ auto& type_future_num = Type::Handle(Type::New(class_future, tav_num));
+ FinalizeAndCanonicalize(&type_future_num);
+ auto& type_future_t = Type::Handle(class_future.DeclarationType());
+
+ // Some more tests of generic implemented classes, using Future. Here,
+ // obj is an object of type Future<int>.
+ //
+ // obj as Future : Null type args (caught by TTS)
+ // obj as Future<dynamic> : Canonicalized to same as previous case.
+ // obj as Future<Object?> : Type arg is top type (caught by TTS)
+ // obj as Future<int> : Type arg is the same type (caught by TTS)
+ // obj as Future<String> : Type arg is not a subtype (error via runtime)
+ // obj as Future<num> : Type arg is a supertype that can be matched
+ // with cid range (caught by TTS)
+ // obj as Future<X>, : Type arg is a type parameter instantiated with
+ // X = int : ... the same type (caught by TTS)
+ // X = String : ... an unrelated type (error via runtime)
+ // X = num : ... a supertype (caught by STC/runtime)
+ RunTTSTest(type_future, {obj_futureint, tav_null, tav_null});
+ RunTTSTest(type_future_dynamic, {obj_futureint, tav_null, tav_null});
+ RunTTSTest(type_future_nullable_object, {obj_futureint, tav_null, tav_null});
+ RunTTSTest(type_future_int, {obj_futureint, tav_null, tav_null});
+ RunTTSTest(type_future_string, Failure({obj_futureint, tav_null, tav_null}));
+ RunTTSTest(type_future_num, {obj_futureint, tav_null, tav_null});
+ RunTTSTest(type_future_t, {obj_futureint, tav_int, tav_null});
+ RunTTSTest(type_future_t, Failure({obj_futureint, tav_string, tav_null}));
+ RunTTSTest(type_future_t, FalseNegative({obj_futureint, tav_num, tav_null}));
+}
+
ISOLATE_UNIT_TEST_CASE(TTS_Regress40964) {
const char* kScript =
R"(
@@ -1122,15 +1274,91 @@
// 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& type_smi = Type::Handle(Type::SmiType());
+ const auto& tav_null = Object::null_type_arguments();
- const auto& dst_type = AbstractType::Handle(smi_class.RareType());
- const auto& tav_null = TypeArguments::Handle(TypeArguments::null());
+ // Test on some easy-to-make instances.
+ RunTTSTest(type_smi, {Smi::Handle(Smi::New(0)), tav_null, tav_null});
+ RunTTSTest(type_smi, Failure({Integer::Handle(Integer::New(kMaxInt64)),
+ tav_null, tav_null}));
+ RunTTSTest(type_smi,
+ Failure({Double::Handle(Double::New(1.0)), tav_null, tav_null}));
+ RunTTSTest(type_smi, Failure({Symbols::Empty(), tav_null, tav_null}));
+ RunTTSTest(type_smi,
+ Failure({Array::Handle(Array::New(1)), tav_null, tav_null}));
+}
- THR_Print("\nTesting that instance of _Smi is a subtype of _Smi\n");
- RunTTSTest(dst_type, {Smi::Handle(Smi::New(0)), tav_null, tav_null});
+// Check that we generate correct TTS for int type.
+ISOLATE_UNIT_TEST_CASE(TTS_Int) {
+ const auto& type_int = Type::Handle(Type::IntType());
+ const auto& tav_null = Object::null_type_arguments();
+
+ // Test on some easy-to-make instances.
+ RunTTSTest(type_int, {Smi::Handle(Smi::New(0)), tav_null, tav_null});
+ RunTTSTest(type_int,
+ {Integer::Handle(Integer::New(kMaxInt64)), tav_null, tav_null});
+ RunTTSTest(type_int,
+ Failure({Double::Handle(Double::New(1.0)), tav_null, tav_null}));
+ RunTTSTest(type_int, Failure({Symbols::Empty(), tav_null, tav_null}));
+ RunTTSTest(type_int,
+ Failure({Array::Handle(Array::New(1)), tav_null, tav_null}));
+}
+
+// Check that we generate correct TTS for num type.
+ISOLATE_UNIT_TEST_CASE(TTS_Num) {
+ const auto& type_num = Type::Handle(Type::Number());
+ const auto& tav_null = Object::null_type_arguments();
+
+ // Test on some easy-to-make instances.
+ RunTTSTest(type_num, {Smi::Handle(Smi::New(0)), tav_null, tav_null});
+ RunTTSTest(type_num,
+ {Integer::Handle(Integer::New(kMaxInt64)), tav_null, tav_null});
+ RunTTSTest(type_num, {Double::Handle(Double::New(1.0)), tav_null, tav_null});
+ RunTTSTest(type_num, Failure({Symbols::Empty(), tav_null, tav_null}));
+ RunTTSTest(type_num,
+ Failure({Array::Handle(Array::New(1)), tav_null, tav_null}));
+}
+
+// Check that we generate correct TTS for Double type.
+ISOLATE_UNIT_TEST_CASE(TTS_Double) {
+ const auto& type_num = Type::Handle(Type::Double());
+ const auto& tav_null = Object::null_type_arguments();
+
+ // Test on some easy-to-make instances.
+ RunTTSTest(type_num, Failure({Smi::Handle(Smi::New(0)), tav_null, tav_null}));
+ RunTTSTest(type_num, Failure({Integer::Handle(Integer::New(kMaxInt64)),
+ tav_null, tav_null}));
+ RunTTSTest(type_num, {Double::Handle(Double::New(1.0)), tav_null, tav_null});
+ RunTTSTest(type_num, Failure({Symbols::Empty(), tav_null, tav_null}));
+ RunTTSTest(type_num,
+ Failure({Array::Handle(Array::New(1)), tav_null, tav_null}));
+}
+
+// Check that we generate correct TTS for Object type.
+ISOLATE_UNIT_TEST_CASE(TTS_Object) {
+ const auto& type_obj =
+ Type::Handle(IsolateGroup::Current()->object_store()->object_type());
+ const auto& tav_null = Object::null_type_arguments();
+
+ auto make_test_case = [&](const Instance& instance) -> TTSTestCase {
+ if (IsolateGroup::Current()->use_strict_null_safety_checks()) {
+ // The stub for non-nullable object should specialize, but only fails
+ // on null, which is already checked within RunTTSTest.
+ return {instance, tav_null, tav_null};
+ } else {
+ // The default type testing stub for nullable object is the top type
+ // stub, so it should neither specialize _or_ return false negatives.
+ return {instance, tav_null, tav_null, /*should_specialize=*/false};
+ }
+ };
+
+ // Test on some easy-to-make instances.
+ RunTTSTest(type_obj, make_test_case(Smi::Handle(Smi::New(0))));
+ RunTTSTest(type_obj,
+ make_test_case(Integer::Handle(Integer::New(kMaxInt64))));
+ RunTTSTest(type_obj, make_test_case(Double::Handle(Double::New(1.0))));
+ RunTTSTest(type_obj, make_test_case(Symbols::Empty()));
+ RunTTSTest(type_obj, make_test_case(Array::Handle(Array::New(1))));
}
// Check that we generate correct TTS for type Function (the non-FunctionType
@@ -1152,7 +1380,6 @@
const auto& obj_f = Object::Handle(Invoke(root_library, "createF"));
const auto& obj_g = Object::Handle(Invoke(root_library, "createG"));
const auto& obj_h = Object::Handle(Invoke(root_library, "createH"));
- const auto& obj_null = Instance::Handle();
const auto& tav_null = TypeArguments::Handle(TypeArguments::null());
const auto& type_function = Type::Handle(Type::DartFunctionType());
@@ -1160,11 +1387,6 @@
RunTTSTest(type_function, {obj_f, tav_null, tav_null});
RunTTSTest(type_function, {obj_g, tav_null, tav_null});
RunTTSTest(type_function, {obj_h, tav_null, tav_null});
- if (!thread->isolate_group()->use_strict_null_safety_checks()) {
- RunTTSTest(type_function, {obj_null, tav_null, tav_null});
- } else {
- RunTTSTest(type_function, Failure({obj_null, tav_null, tav_null}));
- }
const auto& class_a = Class::Handle(GetClass(root_library, "A"));
const auto& obj_a_int = Object::Handle(Invoke(root_library, "createAInt"));
diff --git a/sdk/lib/cli/cli.dart b/sdk/lib/cli/cli.dart
index 1dc8e0d..223dd68 100644
--- a/sdk/lib/cli/cli.dart
+++ b/sdk/lib/cli/cli.dart
@@ -4,6 +4,8 @@
/// {@category VM}
/// {@nodoc}
+@Deprecated(
+ "The functionality of this library is incomplete and may be removed in a later version")
library dart.cli;
import 'dart:async';
diff --git a/sdk/lib/cli/wait_for.dart b/sdk/lib/cli/wait_for.dart
index 58990c8..29e5386 100644
--- a/sdk/lib/cli/wait_for.dart
+++ b/sdk/lib/cli/wait_for.dart
@@ -111,7 +111,33 @@
* Please be aware that nesting calls to [waitFor] can lead to deadlock if
* subsequent calls block waiting for a condition that is only satisfied when
* an earlier call returns.
+ *
+ * **NOTICE**
+ * The `waitFor` feature is deprecated.
+ * The feature was intended to solve a particular problem for existing code,
+ * a problem introduced by a breaking change to the platform libraries.
+ * The `waitFor` function is not suitable for general use.
+ * The feature has shortcomings that can affect other code
+ * running in the same isolate, including:
+ * * A function call that looks synchronous may cause other asynchronous
+ * events to run before it returns.
+ * This is something synchronous code can usually assume not to happen,
+ * and some code may have been written to take advantage of that
+ * assumed behavior. Such code can fail in unexpected ways.
+ * * Multiple nested calls to `waitFor` may block each other
+ * since the most recent call always needs to complete
+ * before any other call can complete.
+ * Judicious use of `waitFor` is necessary to avoid unexpected deadlocks
+ * which wouldn't happen if using `await` instead.
+ * If more than one library in the same program is using `waitFor`,
+ * then it's hard to avoid or control whether such blocking will happen.
+ *
+ * The feature is not actively maintained.
+ * It will remain as-is to support the original problem it was added to solve,
+ * at least until that problem can be solved in some other way.
*/
+@Deprecated(
+ "This functionality is incomplete and may be removed in a later version")
T waitFor<T>(Future<T> future, {Duration? timeout}) {
late T result;
bool futureCompleted = false;
diff --git a/tools/VERSION b/tools/VERSION
index f7e936c..848e6fb 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 15
PATCH 0
-PRERELEASE 18
+PRERELEASE 19
PRERELEASE_PATCH 0
\ No newline at end of file