dart-sdk/runtime/vm/type_testing_stubs.cc
Martin Kustermann f574d49a84 [vm] Use std::atomic for guaranteeing unique TTS names
TTS can be generated on multiple threads at the same time and we should
therefore given them unique names by using an atomic counter.

Fixes https://github.com/dart-lang/sdk/issues/46609

TEST=Fixes one TSAN issue on iso-stress builder.

Change-Id: I4a318106a901cebe30914ac42f858cf3daced74c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/206742
Reviewed-by: Tess Strickland <sstrickl@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
2021-07-14 10:56:20 +00:00

1048 lines
38 KiB
C++

// Copyright (c) 2018, 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 <functional>
#include "vm/compiler/assembler/disassembler.h"
#include "vm/longjump.h"
#include "vm/object_store.h"
#include "vm/stub_code.h"
#include "vm/timeline.h"
#include "vm/type_testing_stubs.h"
#if !defined(DART_PRECOMPILED_RUNTIME)
#include "vm/compiler/backend/flow_graph_compiler.h"
#include "vm/compiler/backend/il_printer.h"
#endif // !defined(DART_PRECOMPILED_RUNTIME)
#define __ assembler->
namespace dart {
DECLARE_FLAG(bool, disassemble_stubs);
TypeTestingStubNamer::TypeTestingStubNamer()
: lib_(Library::Handle()),
klass_(Class::Handle()),
type_(AbstractType::Handle()),
string_(String::Handle()) {}
const char* TypeTestingStubNamer::StubNameForType(
const AbstractType& type) const {
Zone* Z = Thread::Current()->zone();
return OS::SCreate(Z, "TypeTestingStub_%s", StringifyType(type));
}
const char* TypeTestingStubNamer::StringifyType(
const AbstractType& type) const {
NoSafepointScope no_safepoint;
Zone* Z = Thread::Current()->zone();
if (type.IsType()) {
const intptr_t cid = Type::Cast(type).type_class_id();
ClassTable* class_table = IsolateGroup::Current()->class_table();
klass_ = class_table->At(cid);
ASSERT(!klass_.IsNull());
const char* curl = "";
lib_ = klass_.library();
if (!lib_.IsNull()) {
string_ = lib_.url();
curl = OS::SCreate(Z, "%s_", string_.ToCString());
} else {
static std::atomic<intptr_t> counter = 0;
curl = OS::SCreate(Z, "nolib%" Pd "_", counter++);
}
const char* concatenated = AssemblerSafeName(
OS::SCreate(Z, "%s_%s", curl, klass_.ScrubbedNameCString()));
const intptr_t type_parameters = klass_.NumTypeParameters();
auto& type_arguments = TypeArguments::Handle();
if (type.arguments() != TypeArguments::null() && type_parameters > 0) {
type_arguments = type.arguments();
ASSERT(type_arguments.Length() >= type_parameters);
const intptr_t length = type_arguments.Length();
for (intptr_t i = 0; i < type_parameters; ++i) {
type_ = type_arguments.TypeAt(length - type_parameters + i);
concatenated =
OS::SCreate(Z, "%s__%s", concatenated, StringifyType(type_));
}
}
return concatenated;
} else if (type.IsTypeParameter()) {
return AssemblerSafeName(
OS::SCreate(Z, "%s", TypeParameter::Cast(type).CanonicalNameCString()));
} else {
return AssemblerSafeName(OS::SCreate(Z, "%s", type.ToCString()));
}
}
const char* TypeTestingStubNamer::AssemblerSafeName(char* cname) {
char* cursor = cname;
while (*cursor != '\0') {
char c = *cursor;
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || (c == '_'))) {
*cursor = '_';
}
cursor++;
}
return cname;
}
CodePtr TypeTestingStubGenerator::DefaultCodeForType(
const AbstractType& type,
bool lazy_specialize /* = true */) {
auto isolate_group = IsolateGroup::Current();
if (type.IsTypeRef()) {
return isolate_group->use_strict_null_safety_checks()
? StubCode::DefaultTypeTest().ptr()
: StubCode::DefaultNullableTypeTest().ptr();
}
// During bootstrapping we have no access to stubs yet, so we'll just return
// `null` and patch these later in `Object::FinishInit()`.
if (!StubCode::HasBeenInitialized()) {
ASSERT(type.IsType());
const classid_t cid = type.type_class_id();
ASSERT(cid == kDynamicCid || cid == kVoidCid);
return Code::null();
}
if (type.IsTopTypeForSubtyping()) {
return StubCode::TopTypeTypeTest().ptr();
}
if (type.IsTypeParameter()) {
const bool nullable = Instance::NullIsAssignableTo(type);
if (nullable) {
return StubCode::NullableTypeParameterTypeTest().ptr();
} else {
return StubCode::TypeParameterTypeTest().ptr();
}
}
if (type.IsFunctionType()) {
const bool nullable = Instance::NullIsAssignableTo(type);
return nullable ? StubCode::DefaultNullableTypeTest().ptr()
: StubCode::DefaultTypeTest().ptr();
}
if (type.IsType()) {
const bool should_specialize = !FLAG_precompiled_mode && lazy_specialize;
const bool nullable = Instance::NullIsAssignableTo(type);
if (should_specialize) {
return nullable ? StubCode::LazySpecializeNullableTypeTest().ptr()
: StubCode::LazySpecializeTypeTest().ptr();
} else {
return nullable ? StubCode::DefaultNullableTypeTest().ptr()
: StubCode::DefaultTypeTest().ptr();
}
}
return StubCode::UnreachableTypeTest().ptr();
}
#if !defined(DART_PRECOMPILED_RUNTIME)
void TypeTestingStubGenerator::SpecializeStubFor(Thread* thread,
const AbstractType& type) {
HierarchyInfo hi(thread);
TypeTestingStubGenerator generator;
const Code& code =
Code::Handle(thread->zone(), generator.OptimizedCodeForType(type));
type.SetTypeTestingStub(code);
}
#endif
TypeTestingStubGenerator::TypeTestingStubGenerator()
: object_store_(IsolateGroup::Current()->object_store()) {}
CodePtr TypeTestingStubGenerator::OptimizedCodeForType(
const AbstractType& type) {
#if !defined(TARGET_ARCH_IA32)
ASSERT(StubCode::HasBeenInitialized());
if (type.IsTypeRef() || type.IsTypeParameter()) {
return TypeTestingStubGenerator::DefaultCodeForType(
type, /*lazy_specialize=*/false);
}
if (type.IsTopTypeForSubtyping()) {
return StubCode::TopTypeTypeTest().ptr();
}
if (type.IsCanonical()) {
if (type.IsType()) {
#if !defined(DART_PRECOMPILED_RUNTIME)
const Code& code = Code::Handle(
TypeTestingStubGenerator::BuildCodeForType(Type::Cast(type)));
if (!code.IsNull()) {
return code.ptr();
}
// Fall back to default.
#else
// In the precompiled runtime we cannot lazily create new optimized type
// testing stubs, so if we cannot find one, we'll just return the default
// one.
#endif // !defined(DART_PRECOMPILED_RUNTIME)
}
}
#endif // !defined(TARGET_ARCH_IA32)
return TypeTestingStubGenerator::DefaultCodeForType(
type, /*lazy_specialize=*/false);
}
#if !defined(TARGET_ARCH_IA32)
#if !defined(DART_PRECOMPILED_RUNTIME)
#if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64)
#define ONLY_ON_ARM(...) __VA_ARGS__
#else
#define ONLY_ON_ARM(...)
#endif
static CodePtr RetryCompilationWithFarBranches(
Thread* thread,
std::function<CodePtr(compiler::Assembler&)> fun) {
volatile bool use_far_branches = false;
while (true) {
LongJumpScope jump;
if (setjmp(*jump.Set()) == 0) {
// To use the already-defined __ Macro !
compiler::Assembler assembler(nullptr ONLY_ON_ARM(, use_far_branches));
return fun(assembler);
} else {
// We bailed out or we encountered an error.
const Error& error = Error::Handle(thread->StealStickyError());
if (error.ptr() == Object::branch_offset_error().ptr()) {
ASSERT(!use_far_branches);
use_far_branches = true;
} else {
UNREACHABLE();
}
}
}
}
#undef ONLY_ON_ARM
CodePtr TypeTestingStubGenerator::BuildCodeForType(const Type& type) {
auto thread = Thread::Current();
auto zone = thread->zone();
HierarchyInfo* hi = thread->hierarchy_info();
ASSERT(hi != NULL);
if (!hi->CanUseSubtypeRangeCheckFor(type) &&
!hi->CanUseGenericSubtypeRangeCheckFor(type)) {
return Code::null();
}
const Class& type_class = Class::Handle(type.type_class());
ASSERT(!type_class.IsNull());
auto& slow_tts_stub = Code::ZoneHandle(zone);
if (FLAG_precompiled_mode && FLAG_use_bare_instructions) {
slow_tts_stub = thread->isolate_group()->object_store()->slow_tts_stub();
}
const Code& code = Code::Handle(
thread->zone(),
RetryCompilationWithFarBranches(
thread, [&](compiler::Assembler& assembler) {
compiler::UnresolvedPcRelativeCalls unresolved_calls;
BuildOptimizedTypeTestStub(&assembler, &unresolved_calls,
slow_tts_stub, hi, type, type_class);
const auto& static_calls_table = Array::Handle(
zone, compiler::StubCodeCompiler::BuildStaticCallsTable(
zone, &unresolved_calls));
const char* name = namer_.StubNameForType(type);
const auto pool_attachment =
FLAG_use_bare_instructions
? Code::PoolAttachment::kNotAttachPool
: Code::PoolAttachment::kAttachPool;
Code& code = Code::Handle(thread->zone());
auto install_code_fun = [&]() {
code = Code::FinalizeCode(nullptr, &assembler, pool_attachment,
/*optimized=*/false, /*stats=*/nullptr);
if (!static_calls_table.IsNull()) {
code.set_static_calls_target_table(static_calls_table);
}
};
// We have to ensure no mutators are running, because:
//
// a) We allocate an instructions object, which might cause us to
// temporarily flip page protections from (RX -> RW -> RX).
//
SafepointWriteRwLocker ml(thread,
thread->isolate_group()->program_lock());
thread->isolate_group()->RunWithStoppedMutators(
install_code_fun,
/*use_force_growth=*/true);
Code::NotifyCodeObservers(name, code, /*optimized=*/false);
code.set_owner(type);
#ifndef PRODUCT
if (FLAG_support_disassembler && FLAG_disassemble_stubs) {
LogBlock lb;
THR_Print("Code for stub '%s' (type = %s): {\n", name,
type.ToCString());
DisassembleToStdout formatter;
code.Disassemble(&formatter);
THR_Print("}\n");
const ObjectPool& object_pool =
ObjectPool::Handle(code.object_pool());
if (!object_pool.IsNull()) {
object_pool.DebugPrint();
}
}
#endif // !PRODUCT
return code.ptr();
}));
return code.ptr();
}
void TypeTestingStubGenerator::BuildOptimizedTypeTestStubFastCases(
compiler::Assembler* assembler,
HierarchyInfo* hi,
const Type& type,
const Class& type_class) {
// These are handled via the TopTypeTypeTestStub!
ASSERT(!type.IsTopTypeForSubtyping());
// Fast case for 'int' and '_Smi' (which can appear in core libraries).
if (type.IsIntType() || type.IsSmiType()) {
compiler::Label non_smi_value;
__ BranchIfNotSmi(TypeTestABI::kInstanceReg, &non_smi_value);
__ Ret();
__ Bind(&non_smi_value);
} else if (type.IsDartFunctionType()) {
compiler::Label continue_checking;
__ CompareImmediate(TTSInternalRegs::kScratchReg, kClosureCid);
__ BranchIf(NOT_EQUAL, &continue_checking);
__ Ret();
__ Bind(&continue_checking);
} 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.
}
// Check the cid ranges which are a subtype of [type].
if (hi->CanUseSubtypeRangeCheckFor(type)) {
const CidRangeVector& ranges = hi->SubtypeRangesForClass(
type_class,
/*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);
} 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);
}
if (Instance::NullIsAssignableTo(type)) {
// Fast case for 'null'.
compiler::Label non_null;
__ CompareObject(TypeTestABI::kInstanceReg, Object::null_object());
__ BranchIf(NOT_EQUAL, &non_null);
__ Ret();
__ Bind(&non_null);
}
}
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);
}
FlowGraphCompiler::GenerateCidRangesCheck(
assembler, TTSInternalRegs::kScratchReg, ranges, &is_subtype,
&cid_range_failed, true);
__ Bind(&is_subtype);
__ Ret();
__ Bind(&cid_range_failed);
}
void TypeTestingStubGenerator::
BuildOptimizedSubclassRangeCheckWithTypeArguments(
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.
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
// b) Then we'll load the values for the type parameters.
__ LoadCompressedFieldFromOffset(
TTSInternalRegs::kInstanceTypeArgumentsReg, TypeTestABI::kInstanceReg,
compiler::target::Class::TypeArgumentsFieldOffset(type_class));
// 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);
__ 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,
const CidRangeVector& ranges,
compiler::Label* check_failed) {
__ LoadClassIdMayBeSmi(TTSInternalRegs::kScratchReg,
TypeTestABI::kInstanceReg);
compiler::Label is_subtype;
FlowGraphCompiler::GenerateCidRangesCheck(
assembler, TTSInternalRegs::kScratchReg, ranges, &is_subtype,
check_failed, true);
__ Bind(&is_subtype);
}
// Generate code to verify that instance's type argument is a subtype of
// 'type_arg'.
void TypeTestingStubGenerator::BuildOptimizedTypeArgumentValueCheck(
compiler::Assembler* assembler,
HierarchyInfo* hi,
const AbstractType& type_arg,
intptr_t type_param_value_offset_i,
compiler::Label* check_failed) {
if (type_arg.IsTopTypeForSubtyping()) {
return;
}
// If the upper bound is a type parameter and its value is "dynamic"
// we always succeed.
compiler::Label is_dynamic;
if (type_arg.IsTypeParameter()) {
const TypeParameter& type_param = TypeParameter::Cast(type_arg);
const Register kTypeArgumentsReg =
type_param.IsClassTypeParameter()
? TypeTestABI::kInstantiatorTypeArgumentsReg
: TypeTestABI::kFunctionTypeArgumentsReg;
__ CompareObject(kTypeArgumentsReg, Object::null_object());
__ BranchIf(EQUAL, &is_dynamic);
__ LoadCompressedFieldFromOffset(
TTSInternalRegs::kScratchReg, kTypeArgumentsReg,
compiler::target::TypeArguments::type_at_offset(type_param.index()));
__ CompareWithCompressedFieldFromOffset(
TTSInternalRegs::kScratchReg,
TTSInternalRegs::kInstanceTypeArgumentsReg,
compiler::target::TypeArguments::type_at_offset(
type_param_value_offset_i));
__ BranchIf(NOT_EQUAL, check_failed);
} else {
const Class& type_class = Class::Handle(type_arg.type_class());
const bool null_is_assignable = Instance::NullIsAssignableTo(type_arg);
const CidRangeVector& ranges =
hi->SubtypeRangesForClass(type_class,
/*include_abstract=*/true,
/*exclude_null=*/!null_is_assignable);
__ LoadCompressedFieldFromOffset(
TTSInternalRegs::kScratchReg,
TTSInternalRegs::kInstanceTypeArgumentsReg,
compiler::target::TypeArguments::type_at_offset(
type_param_value_offset_i));
__ LoadCompressedFieldFromOffset(
TTSInternalRegs::kScratchReg, TTSInternalRegs::kScratchReg,
compiler::target::Type::type_class_id_offset());
compiler::Label is_subtype;
__ SmiUntag(TTSInternalRegs::kScratchReg);
if (null_is_assignable) {
__ CompareImmediate(TTSInternalRegs::kScratchReg, kNullCid);
__ BranchIf(EQUAL, &is_subtype);
}
// Never is a bottom type.
__ CompareImmediate(TTSInternalRegs::kScratchReg, kNeverCid);
__ BranchIf(EQUAL, &is_subtype);
FlowGraphCompiler::GenerateCidRangesCheck(
assembler, TTSInternalRegs::kScratchReg, ranges, &is_subtype,
check_failed, true);
__ Bind(&is_subtype);
// Weak NNBD mode uses LEGACY_SUBTYPE which ignores nullability.
// We don't need to check nullability of LHS for nullable and legacy RHS
// ("Right Legacy", "Right Nullable" rules).
if (IsolateGroup::Current()->use_strict_null_safety_checks() &&
!type_arg.IsNullable() && !type_arg.IsLegacy()) {
// Nullable type is not a subtype of non-nullable type.
// TODO(dartbug.com/40736): Allocate a register for instance type argument
// and avoid reloading it.
__ LoadCompressedFieldFromOffset(
TTSInternalRegs::kScratchReg,
TTSInternalRegs::kInstanceTypeArgumentsReg,
compiler::target::TypeArguments::type_at_offset(
type_param_value_offset_i));
__ CompareTypeNullabilityWith(TTSInternalRegs::kScratchReg,
compiler::target::Nullability::kNullable);
__ BranchIf(EQUAL, check_failed);
}
}
__ Bind(&is_dynamic);
}
void RegisterTypeArgumentsUse(const Function& function,
TypeUsageInfo* type_usage_info,
const Class& klass,
Definition* type_arguments) {
// The [type_arguments] can, in the general case, be any kind of [Definition]
// but generally (in order of expected frequency)
//
// Case a)
// type_arguments <- Constant(#null)
// type_arguments <- Constant(#TypeArguments: [ ... ])
//
// Case b)
// type_arguments <- InstantiateTypeArguments(ita, fta, uta)
// (where uta may not be a constant non-null TypeArguments object)
//
// Case c)
// type_arguments <- LoadField(vx)
// type_arguments <- LoadField(vx T{_ABC})
// type_arguments <- LoadField(vx T{Type: class: '_ABC'})
//
// Case d, e)
// type_arguments <- LoadIndexedUnsafe(rbp[vx + 16]))
// type_arguments <- Parameter(0)
if (ConstantInstr* constant = type_arguments->AsConstant()) {
const Object& object = constant->value();
ASSERT(object.IsNull() || object.IsTypeArguments());
const TypeArguments& type_arguments =
TypeArguments::Handle(TypeArguments::RawCast(object.ptr()));
type_usage_info->UseTypeArgumentsInInstanceCreation(klass, type_arguments);
} else if (InstantiateTypeArgumentsInstr* instantiate =
type_arguments->AsInstantiateTypeArguments()) {
if (instantiate->type_arguments()->BindsToConstant() &&
!instantiate->type_arguments()->BoundConstant().IsNull()) {
const auto& ta =
TypeArguments::Cast(instantiate->type_arguments()->BoundConstant());
type_usage_info->UseTypeArgumentsInInstanceCreation(klass, ta);
}
} else if (LoadFieldInstr* load_field = type_arguments->AsLoadField()) {
Definition* instance = load_field->instance()->definition();
intptr_t cid = instance->Type()->ToNullableCid();
if (cid == kDynamicCid) {
// This is an approximation: If we only know the type, but not the cid, we
// might have a this-dispatch where we know it's either this class or any
// subclass.
// We try to strengthen this assumption furher down by checking the offset
// of the type argument vector, but generally speaking this could be a
// false-postive, which is still ok!
const AbstractType& type = *instance->Type()->ToAbstractType();
if (type.IsType()) {
const Class& type_class = Class::Handle(type.type_class());
if (type_class.NumTypeArguments() >= klass.NumTypeArguments()) {
cid = type_class.id();
}
}
}
if (cid != kDynamicCid) {
const Class& instance_klass =
Class::Handle(IsolateGroup::Current()->class_table()->At(cid));
if (load_field->slot().IsTypeArguments() && instance_klass.IsGeneric() &&
compiler::target::Class::TypeArgumentsFieldOffset(instance_klass) ==
load_field->slot().offset_in_bytes()) {
// This is a subset of Case c) above, namely forwarding the type
// argument vector.
//
// We use the declaration type arguments for the instance creation,
// which is a non-instantiated, expanded, type arguments vector.
const Type& declaration_type =
Type::Handle(instance_klass.DeclarationType());
TypeArguments& declaration_type_args =
TypeArguments::Handle(declaration_type.arguments());
type_usage_info->UseTypeArgumentsInInstanceCreation(
klass, declaration_type_args);
}
}
} else if (type_arguments->IsParameter() ||
type_arguments->IsLoadIndexedUnsafe()) {
// This happens in constructors with non-optional/optional parameters
// where we forward the type argument vector to object allocation.
//
// Theoretically this could be a false-positive, which is still ok, but
// practically it's guaranteed that this is a forward of a type argument
// vector passed in by the caller.
if (function.IsFactory()) {
const Class& enclosing_class = Class::Handle(function.Owner());
const Type& declaration_type =
Type::Handle(enclosing_class.DeclarationType());
TypeArguments& declaration_type_args =
TypeArguments::Handle(declaration_type.arguments());
type_usage_info->UseTypeArgumentsInInstanceCreation(
klass, declaration_type_args);
}
} else {
// It can also be a phi node where the inputs are any of the above,
// or it could be the result of _prependTypeArguments call.
ASSERT(type_arguments->IsPhi() || type_arguments->IsStaticCall());
}
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
#else // !defined(TARGET_ARCH_IA32)
#if !defined(DART_PRECOMPILED_RUNTIME)
void RegisterTypeArgumentsUse(const Function& function,
TypeUsageInfo* type_usage_info,
const Class& klass,
Definition* type_arguments) {
// We only have a [TypeUsageInfo] object available durin AOT compilation.
UNREACHABLE();
}
#endif // !defined(DART_PRECOMPILED_RUNTIME)
#endif // !defined(TARGET_ARCH_IA32)
#undef __
const TypeArguments& TypeArgumentInstantiator::InstantiateTypeArguments(
const Class& klass,
const TypeArguments& type_arguments) {
const intptr_t len = klass.NumTypeArguments();
ScopedHandle<TypeArguments> instantiated_type_arguments(
&type_arguments_handles_);
*instantiated_type_arguments = TypeArguments::New(len);
for (intptr_t i = 0; i < len; ++i) {
type_ = type_arguments.TypeAt(i);
type_ = InstantiateType(type_);
instantiated_type_arguments->SetTypeAt(i, type_);
ASSERT(type_.IsCanonical() ||
(type_.IsTypeRef() &&
AbstractType::Handle(TypeRef::Cast(type_).type()).IsCanonical()));
}
*instantiated_type_arguments =
instantiated_type_arguments->Canonicalize(Thread::Current(), nullptr);
return *instantiated_type_arguments;
}
AbstractTypePtr TypeArgumentInstantiator::InstantiateType(
const AbstractType& type) {
if (type.IsTypeParameter()) {
const TypeParameter& parameter = TypeParameter::Cast(type);
ASSERT(parameter.IsClassTypeParameter());
ASSERT(parameter.IsFinalized());
if (instantiator_type_arguments_.IsNull()) {
return Type::DynamicType();
}
AbstractType& result = AbstractType::Handle(
instantiator_type_arguments_.TypeAt(parameter.index()));
result = result.SetInstantiatedNullability(TypeParameter::Cast(type),
Heap::kOld);
return result.NormalizeFutureOrType(Heap::kOld);
} else if (type.IsFunctionType()) {
// No support for function types yet.
UNREACHABLE();
return nullptr;
} else if (type.IsTypeRef()) {
// No support for recursive types.
UNREACHABLE();
return nullptr;
} else if (type.IsType()) {
if (type.IsInstantiated() || type.arguments() == TypeArguments::null()) {
return type.ptr();
}
const Type& from = Type::Cast(type);
klass_ = from.type_class();
ScopedHandle<Type> to(&type_handles_);
ScopedHandle<TypeArguments> to_type_arguments(&type_arguments_handles_);
*to_type_arguments = TypeArguments::null();
*to = Type::New(klass_, *to_type_arguments);
*to_type_arguments = from.arguments();
to->set_arguments(InstantiateTypeArguments(klass_, *to_type_arguments));
to->SetIsFinalized();
*to ^= to->Canonicalize(Thread::Current(), nullptr);
return to->ptr();
}
UNREACHABLE();
return NULL;
}
TypeUsageInfo::TypeUsageInfo(Thread* thread)
: ThreadStackResource(thread),
zone_(thread->zone()),
finder_(zone_),
assert_assignable_types_(),
instance_creation_arguments_(
new TypeArgumentsSet
[thread->isolate_group()->class_table()->NumCids()]),
klass_(Class::Handle(zone_)) {
thread->set_type_usage_info(this);
}
TypeUsageInfo::~TypeUsageInfo() {
thread()->set_type_usage_info(NULL);
delete[] instance_creation_arguments_;
}
void TypeUsageInfo::UseTypeInAssertAssignable(const AbstractType& type) {
if (!assert_assignable_types_.HasKey(&type)) {
AddTypeToSet(&assert_assignable_types_, &type);
}
}
void TypeUsageInfo::UseTypeArgumentsInInstanceCreation(
const Class& klass,
const TypeArguments& ta) {
if (ta.IsNull() || ta.IsCanonical()) {
// The Dart VM performs an optimization where it re-uses type argument
// vectors if the use-site needs a prefix of an already-existent type
// arguments vector.
//
// For example:
//
// class Foo<K, V> {
// foo() => new Bar<K>();
// }
//
// So the length of the type arguments vector can be longer than the number
// of type arguments the class expects.
ASSERT(ta.IsNull() || klass.NumTypeArguments() <= ta.Length());
// If this is a non-instantiated [TypeArguments] object, then it referes to
// type parameters. We need to ensure the type parameters in [ta] only
// refer to type parameters in the class.
if (!ta.IsNull() && !ta.IsInstantiated() &&
finder_.FindClass(ta).IsNull()) {
return;
}
klass_ = klass.ptr();
while (klass_.NumTypeArguments() > 0) {
const intptr_t cid = klass_.id();
TypeArgumentsSet& set = instance_creation_arguments_[cid];
if (!set.HasKey(&ta)) {
set.Insert(&TypeArguments::ZoneHandle(zone_, ta.ptr()));
}
klass_ = klass_.SuperClass();
}
}
}
void TypeUsageInfo::BuildTypeUsageInformation() {
ClassTable* class_table = thread()->isolate_group()->class_table();
const intptr_t cid_count = class_table->NumCids();
// Step 1) Propagate instantiated type argument vectors.
PropagateTypeArguments(class_table, cid_count);
// Step 2) Collect the type parameters we're interested in.
TypeParameterSet parameters_tested_against;
CollectTypeParametersUsedInAssertAssignable(&parameters_tested_against);
// Step 2) Add all types which flow into a type parameter we test against to
// the set of types tested against.
UpdateAssertAssignableTypes(class_table, cid_count,
&parameters_tested_against);
}
void TypeUsageInfo::PropagateTypeArguments(ClassTable* class_table,
intptr_t cid_count) {
// See comment in .h file for what this method does.
Class& klass = Class::Handle(zone_);
TypeArguments& temp_type_arguments = TypeArguments::Handle(zone_);
// We cannot modify a set while we are iterating over it, so we delay the
// addition to the set to the point when iteration has finished and use this
// list as temporary storage.
GrowableObjectArray& delayed_type_argument_set =
GrowableObjectArray::Handle(zone_, GrowableObjectArray::New());
TypeArgumentInstantiator instantiator(zone_);
const intptr_t kPropgationRounds = 2;
for (intptr_t round = 0; round < kPropgationRounds; ++round) {
for (intptr_t cid = 0; cid < cid_count; ++cid) {
if (!class_table->IsValidIndex(cid) ||
!class_table->HasValidClassAt(cid)) {
continue;
}
klass = class_table->At(cid);
bool null_in_delayed_type_argument_set = false;
delayed_type_argument_set.SetLength(0);
auto it = instance_creation_arguments_[cid].GetIterator();
for (const TypeArguments** type_arguments = it.Next();
type_arguments != nullptr; type_arguments = it.Next()) {
// We have a "type allocation" with "klass<type_arguments[0:N]>".
if (!(*type_arguments)->IsNull() &&
!(*type_arguments)->IsInstantiated()) {
const Class& enclosing_class = finder_.FindClass(**type_arguments);
if (!klass.IsNull()) {
// We know that "klass<type_arguments[0:N]>" happens inside
// [enclosing_class].
if (enclosing_class.ptr() != klass.ptr()) {
// Now we try to instantiate [type_arguments] with all the known
// instantiator type argument vectors of the [enclosing_class].
const intptr_t enclosing_class_cid = enclosing_class.id();
TypeArgumentsSet& instantiator_set =
instance_creation_arguments_[enclosing_class_cid];
auto it2 = instantiator_set.GetIterator();
for (const TypeArguments** instantiator_type_arguments =
it2.Next();
instantiator_type_arguments != nullptr;
instantiator_type_arguments = it2.Next()) {
// We have also a "type allocation" with
// "enclosing_class<instantiator_type_arguments[0:M]>".
if ((*instantiator_type_arguments)->IsNull() ||
(*instantiator_type_arguments)->IsInstantiated()) {
temp_type_arguments = instantiator.Instantiate(
klass, **type_arguments, **instantiator_type_arguments);
if (temp_type_arguments.IsNull() &&
!null_in_delayed_type_argument_set) {
null_in_delayed_type_argument_set = true;
delayed_type_argument_set.Add(temp_type_arguments);
} else {
delayed_type_argument_set.Add(temp_type_arguments);
}
}
}
}
}
}
}
// Now we add the [delayed_type_argument_set] elements to the set of
// instantiator type arguments of [klass] (and its superclasses).
if (delayed_type_argument_set.Length() > 0) {
while (klass.NumTypeArguments() > 0) {
TypeArgumentsSet& type_argument_set =
instance_creation_arguments_[klass.id()];
const intptr_t len = delayed_type_argument_set.Length();
for (intptr_t i = 0; i < len; ++i) {
temp_type_arguments =
TypeArguments::RawCast(delayed_type_argument_set.At(i));
if (!type_argument_set.HasKey(&temp_type_arguments)) {
type_argument_set.Insert(
&TypeArguments::ZoneHandle(zone_, temp_type_arguments.ptr()));
}
}
klass = klass.SuperClass();
}
}
}
}
}
void TypeUsageInfo::CollectTypeParametersUsedInAssertAssignable(
TypeParameterSet* set) {
TypeParameter& param = TypeParameter::Handle(zone_);
auto it = assert_assignable_types_.GetIterator();
for (const AbstractType** type = it.Next(); type != nullptr;
type = it.Next()) {
AddToSetIfParameter(set, *type, &param);
}
}
void TypeUsageInfo::UpdateAssertAssignableTypes(
ClassTable* class_table,
intptr_t cid_count,
TypeParameterSet* parameters_tested_against) {
Class& klass = Class::Handle(zone_);
TypeParameter& param = TypeParameter::Handle(zone_);
AbstractType& type = AbstractType::Handle(zone_);
// Because Object/dynamic are common values for type parameters, we add them
// eagerly and avoid doing it down inside the loop.
type = Type::DynamicType();
UseTypeInAssertAssignable(type);
type = Type::ObjectType(); // TODO(regis): Add nullable Object?
UseTypeInAssertAssignable(type);
for (intptr_t cid = 0; cid < cid_count; ++cid) {
if (!class_table->IsValidIndex(cid) || !class_table->HasValidClassAt(cid)) {
continue;
}
klass = class_table->At(cid);
if (klass.NumTypeArguments() <= 0) {
continue;
}
const intptr_t num_parameters = klass.NumTypeParameters();
for (intptr_t i = 0; i < num_parameters; ++i) {
param = klass.TypeParameterAt(i);
if (parameters_tested_against->HasKey(&param)) {
TypeArgumentsSet& ta_set = instance_creation_arguments_[cid];
auto it = ta_set.GetIterator();
for (const TypeArguments** ta = it.Next(); ta != nullptr;
ta = it.Next()) {
// We only add instantiated types to the set (and dynamic/Object were
// already handled above).
if (!(*ta)->IsNull()) {
type = (*ta)->TypeAt(i);
if (type.IsInstantiated()) {
UseTypeInAssertAssignable(type);
}
}
}
}
}
}
}
void TypeUsageInfo::AddToSetIfParameter(TypeParameterSet* set,
const AbstractType* type,
TypeParameter* param) {
if (type->IsTypeParameter()) {
*param ^= type->ptr();
if (!param->IsNull() && !set->HasKey(param)) {
set->Insert(&TypeParameter::Handle(zone_, param->ptr()));
}
}
}
void TypeUsageInfo::AddTypeToSet(TypeSet* set, const AbstractType* type) {
if (!set->HasKey(type)) {
set->Insert(&AbstractType::ZoneHandle(zone_, type->ptr()));
}
}
bool TypeUsageInfo::IsUsedInTypeTest(const AbstractType& type) {
const AbstractType* dereferenced_type = &type;
if (type.IsTypeRef()) {
dereferenced_type = &AbstractType::Handle(TypeRef::Cast(type).type());
}
if (dereferenced_type->IsFinalized()) {
return assert_assignable_types_.HasKey(dereferenced_type);
}
return false;
}
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
void DeoptimizeTypeTestingStubs() {
class CollectTypes : public ObjectVisitor {
public:
CollectTypes(GrowableArray<AbstractType*>* types, Zone* zone)
: types_(types), object_(Object::Handle(zone)), zone_(zone) {}
void VisitObject(ObjectPtr object) {
if (object->IsPseudoObject()) {
// Cannot even be wrapped in handles.
return;
}
object_ = object;
if (object_.IsAbstractType()) {
types_->Add(
&AbstractType::Handle(zone_, AbstractType::RawCast(object)));
}
}
private:
GrowableArray<AbstractType*>* types_;
Object& object_;
Zone* zone_;
};
Thread* thread = Thread::Current();
TIMELINE_DURATION(thread, Isolate, "DeoptimizeTypeTestingStubs");
HANDLESCOPE(thread);
Zone* zone = thread->zone();
GrowableArray<AbstractType*> types;
{
HeapIterationScope iter(thread);
CollectTypes visitor(&types, zone);
iter.IterateObjects(&visitor);
}
TypeTestingStubGenerator generator;
Code& code = Code::Handle(zone);
for (intptr_t i = 0; i < types.length(); i++) {
code = generator.DefaultCodeForType(*types[i]);
types[i]->SetTypeTestingStub(code);
}
}
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
} // namespace dart