blob: fe40775e092902e868e2c6e45d3b8ffe8cfa224d [file] [log] [blame]
// 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 "vm/type_testing_stubs.h"
#include "vm/compiler/assembler/disassembler.h"
#include "vm/object_store.h"
#include "vm/stub_code.h"
#include "vm/timeline.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()),
type_arguments_(TypeArguments::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() && !type.IsFunctionType()) {
const intptr_t cid = Type::Cast(type).type_class_id();
ClassTable* class_table = Isolate::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 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();
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()) {
string_ = TypeParameter::Cast(type).name();
return AssemblerSafeName(OS::SCreate(Z, "%s", string_.ToCString()));
} 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 */) {
if (type.IsTypeRef()) {
return Isolate::Current()->null_safety()
? StubCode::DefaultTypeTest().raw()
: StubCode::DefaultNullableTypeTest().raw();
}
// 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().raw();
}
if (type.IsType() || type.IsTypeParameter()) {
const bool should_specialize = !FLAG_precompiled_mode && lazy_specialize;
const bool nullable = Instance::NullIsAssignableTo(type);
if (should_specialize) {
return nullable ? StubCode::LazySpecializeNullableTypeTest().raw()
: StubCode::LazySpecializeTypeTest().raw();
} else {
return nullable ? StubCode::DefaultNullableTypeTest().raw()
: StubCode::DefaultTypeTest().raw();
}
}
return StubCode::UnreachableTypeTest().raw();
}
#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_(Isolate::Current()->object_store()) {}
CodePtr TypeTestingStubGenerator::OptimizedCodeForType(
const AbstractType& type) {
#if !defined(TARGET_ARCH_IA32)
ASSERT(StubCode::HasBeenInitialized());
if (type.IsTypeRef()) {
return TypeTestingStubGenerator::DefaultCodeForType(
type, /*lazy_specialize=*/false);
}
if (type.IsTopTypeForSubtyping()) {
return StubCode::TopTypeTypeTest().raw();
}
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.raw();
}
// 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)
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()->object_store()->slow_tts_stub();
}
// To use the already-defined __ Macro !
compiler::Assembler assembler(nullptr);
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).
//
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.raw();
}
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'.
if (type.IsIntType()) {
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() && Isolate::Current()->null_safety());
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& int_type = Type::Handle(Type::IntType());
const bool smi_is_ok = int_type.IsSubtypeOf(type, Heap::kNew);
BuildOptimizedSubtypeRangeCheck(assembler, ranges, smi_is_ok);
} else {
ASSERT(hi->CanUseGenericSubtypeRangeCheckFor(type));
const intptr_t num_type_parameters = type_class.NumTypeParameters();
const intptr_t num_type_arguments = type_class.NumTypeArguments();
const TypeArguments& tp =
TypeArguments::Handle(type_class.type_parameters());
ASSERT(tp.Length() == num_type_parameters);
const TypeArguments& ta = TypeArguments::Handle(type.arguments());
ASSERT(ta.Length() == num_type_arguments);
BuildOptimizedSubclassRangeCheckWithTypeArguments(assembler, hi, type,
type_class, tp, 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& tp,
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.
__ LoadField(
TTSInternalRegs::kInstanceTypeArgumentsReg,
compiler::FieldAddress(
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);
__ LoadField(
TTSInternalRegs::kScratchReg,
compiler::FieldAddress(kTypeArgumentsReg,
compiler::target::TypeArguments::type_at_offset(
type_param.index())));
__ CompareWithFieldValue(
TTSInternalRegs::kScratchReg,
compiler::FieldAddress(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 CidRangeVector& ranges = hi->SubtypeRangesForClass(
type_class,
/*include_abstract=*/true,
/*exclude_null=*/!Instance::NullIsAssignableTo(type_arg));
__ LoadField(
TTSInternalRegs::kScratchReg,
compiler::FieldAddress(TTSInternalRegs::kInstanceTypeArgumentsReg,
compiler::target::TypeArguments::type_at_offset(
type_param_value_offset_i)));
__ LoadField(
TTSInternalRegs::kScratchReg,
compiler::FieldAddress(TTSInternalRegs::kScratchReg,
compiler::target::Type::type_class_id_offset()));
compiler::Label is_subtype;
__ SmiUntag(TTSInternalRegs::kScratchReg);
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 (Isolate::Current()->null_safety() && !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.
__ LoadField(TTSInternalRegs::kScratchReg,
compiler::FieldAddress(
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(
// <type-expr-with-parameters>, ita, fta)
//
// 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.raw()));
type_usage_info->UseTypeArgumentsInInstanceCreation(klass, type_arguments);
} else if (InstantiateTypeArgumentsInstr* instantiate =
type_arguments->AsInstantiateTypeArguments()) {
const TypeArguments& ta = instantiate->type_arguments();
ASSERT(!ta.IsNull());
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(Isolate::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(NULL);
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.raw();
}
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, type.token_pos());
*to_type_arguments = from.arguments();
to->set_arguments(InstantiateTypeArguments(klass_, *to_type_arguments));
to->SetIsFinalized();
*to ^= to->Canonicalize(NULL);
return to->raw();
}
UNREACHABLE();
return NULL;
}
TypeUsageInfo::TypeUsageInfo(Thread* thread)
: ThreadStackResource(thread),
zone_(thread->zone()),
finder_(zone_),
assert_assignable_types_(),
instance_creation_arguments_(
new TypeArgumentsSet[thread->isolate()->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.raw();
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.raw()));
}
klass_ = klass_.SuperClass();
}
}
}
void TypeUsageInfo::BuildTypeUsageInformation() {
ClassTable* class_table = thread()->isolate()->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.raw() != klass.raw()) {
// 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.raw()));
}
}
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_);
TypeArguments& params = TypeArguments::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();
params = klass.type_parameters();
for (intptr_t i = 0; i < num_parameters; ++i) {
param ^= params.TypeAt(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->raw();
if (!param->IsNull() && !set->HasKey(param)) {
set->Insert(&TypeParameter::Handle(zone_, param->raw()));
}
}
}
void TypeUsageInfo::AddTypeToSet(TypeSet* set, const AbstractType* type) {
if (!set->HasKey(type)) {
set->Insert(&AbstractType::ZoneHandle(zone_, type->raw()));
}
}
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