blob: 5b814c50efc9bcc092f341dc86e3e9686af5f13d [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/compiler/backend/slot.h"
#include "vm/compiler/backend/flow_graph_compiler.h"
#include "vm/compiler/backend/il.h"
#include "vm/compiler/compiler_state.h"
#include "vm/hash_map.h"
#include "vm/parser.h"
#include "vm/scopes.h"
namespace dart {
// Canonicalization cache for Slot objects.
//
// This cache is attached to the CompilerState to ensure that we preserve
// identity of Slot objects during each individual compilation.
class SlotCache : public ZoneAllocated {
public:
// Returns an instance of SlotCache for the current compilation.
static SlotCache& Instance(Thread* thread) {
auto result = thread->compiler_state().slot_cache();
if (result == nullptr) {
result = new (thread->zone()) SlotCache(thread);
thread->compiler_state().set_slot_cache(result);
}
return *result;
}
const Slot& Canonicalize(const Slot& value) {
auto result = fields_.LookupValue(&value);
if (result == nullptr) {
result = new (zone_) Slot(value);
fields_.Insert(result);
}
return *result;
}
private:
explicit SlotCache(Thread* thread)
: zone_(thread->zone()), fields_(thread->zone()) {}
Zone* const zone_;
DirectChainedHashMap<PointerKeyValueTrait<const Slot> > fields_;
};
#define NATIVE_SLOT_NAME(C, F) Kind::k##C##_##F
#define NATIVE_TO_STR(C, F) #C "_" #F
const char* Slot::KindToCString(Kind k) {
switch (k) {
#define NATIVE_CASE(C, __, F, ___, ____) \
case NATIVE_SLOT_NAME(C, F): \
return NATIVE_TO_STR(C, F);
NATIVE_SLOTS_LIST(NATIVE_CASE)
#undef NATIVE_CASE
case Kind::kTypeArguments:
return "TypeArguments";
case Kind::kArrayElement:
return "ArrayElement";
case Kind::kCapturedVariable:
return "CapturedVariable";
case Kind::kDartField:
return "DartField";
default:
UNREACHABLE();
return nullptr;
}
}
bool Slot::ParseKind(const char* str, Kind* out) {
ASSERT(str != nullptr && out != nullptr);
#define NATIVE_CASE(C, __, F, ___, ____) \
if (strcmp(str, NATIVE_TO_STR(C, F)) == 0) { \
*out = NATIVE_SLOT_NAME(C, F); \
return true; \
}
NATIVE_SLOTS_LIST(NATIVE_CASE)
#undef NATIVE_CASE
if (strcmp(str, "TypeArguments") == 0) {
*out = Kind::kTypeArguments;
return true;
}
if (strcmp(str, "ArrayElement") == 0) {
*out = Kind::kArrayElement;
return true;
}
if (strcmp(str, "CapturedVariable") == 0) {
*out = Kind::kCapturedVariable;
return true;
}
if (strcmp(str, "DartField") == 0) {
*out = Kind::kDartField;
return true;
}
return false;
}
#undef NATIVE_TO_STR
#undef NATIVE_SLOT_NAME
static classid_t GetUnboxedNativeSlotCid(Representation rep) {
// Currently we only support integer unboxed fields.
if (RepresentationUtils::IsUnboxedInteger(rep)) {
return Boxing::BoxCid(rep);
}
UNREACHABLE();
return kIllegalCid;
}
AcqRelAtomic<Slot*> Slot::native_fields_(nullptr);
enum NativeSlotsEnumeration {
#define DECLARE_KIND(CN, __, FN, ___, ____) k##CN##_##FN,
NATIVE_SLOTS_LIST(DECLARE_KIND)
#undef DECLARE_KIND
kNativeSlotsCount
};
const Slot& Slot::GetNativeSlot(Kind kind) {
if (native_fields_.load() == nullptr) {
Slot* new_value = new Slot[kNativeSlotsCount]{
#define NULLABLE_FIELD_FINAL \
(IsNullableBit::encode(true) | IsImmutableBit::encode(true))
#define NULLABLE_FIELD_VAR (IsNullableBit::encode(true))
#define DEFINE_NULLABLE_BOXED_NATIVE_FIELD(ClassName, UnderlyingType, \
FieldName, cid, mutability) \
Slot(Kind::k##ClassName##_##FieldName, NULLABLE_FIELD_##mutability, \
k##cid##Cid, compiler::target::ClassName::FieldName##_offset(), \
#ClassName "." #FieldName, nullptr, kTagged),
NULLABLE_BOXED_NATIVE_SLOTS_LIST(DEFINE_NULLABLE_BOXED_NATIVE_FIELD)
#undef DEFINE_NULLABLE_BOXED_NATIVE_FIELD
#undef NULLABLE_FIELD_FINAL
#undef NULLABLE_FIELD_VAR
#define NONNULLABLE_FIELD_FINAL (Slot::IsImmutableBit::encode(true))
#define NONNULLABLE_FIELD_VAR (0)
#define DEFINE_NONNULLABLE_BOXED_NATIVE_FIELD(ClassName, UnderlyingType, \
FieldName, cid, mutability) \
Slot(Kind::k##ClassName##_##FieldName, NONNULLABLE_FIELD_##mutability, \
k##cid##Cid, compiler::target::ClassName::FieldName##_offset(), \
#ClassName "." #FieldName, nullptr, kTagged),
NONNULLABLE_BOXED_NATIVE_SLOTS_LIST(
DEFINE_NONNULLABLE_BOXED_NATIVE_FIELD)
#undef DEFINE_NONNULLABLE_BOXED_NATIVE_FIELD
#define DEFINE_UNBOXED_NATIVE_FIELD(ClassName, UnderlyingType, FieldName, \
representation, mutability) \
Slot(Kind::k##ClassName##_##FieldName, NONNULLABLE_FIELD_##mutability, \
GetUnboxedNativeSlotCid(kUnboxed##representation), \
compiler::target::ClassName::FieldName##_offset(), \
#ClassName "." #FieldName, nullptr, kUnboxed##representation),
UNBOXED_NATIVE_SLOTS_LIST(DEFINE_UNBOXED_NATIVE_FIELD)
#undef DEFINE_UNBOXED_NATIVE_FIELD
#undef NONNULLABLE_FIELD_VAR
#undef NONNULLABLE_FIELD_FINAL
};
Slot* old_value = nullptr;
if (!native_fields_.compare_exchange_strong(old_value, new_value)) {
delete[] new_value;
}
}
ASSERT(static_cast<uint8_t>(kind) < kNativeSlotsCount);
return native_fields_.load()[static_cast<uint8_t>(kind)];
}
// Note: should only be called with cids of array-like classes.
const Slot& Slot::GetLengthFieldForArrayCid(intptr_t array_cid) {
if (IsExternalTypedDataClassId(array_cid) || IsTypedDataClassId(array_cid) ||
IsTypedDataViewClassId(array_cid)) {
return GetNativeSlot(Kind::kTypedDataBase_length);
}
switch (array_cid) {
case kGrowableObjectArrayCid:
return GetNativeSlot(Kind::kGrowableObjectArray_length);
case kOneByteStringCid:
case kTwoByteStringCid:
case kExternalOneByteStringCid:
case kExternalTwoByteStringCid:
return GetNativeSlot(Kind::kString_length);
case kArrayCid:
case kImmutableArrayCid:
return GetNativeSlot(Kind::kArray_length);
case kTypeArgumentsCid:
return GetNativeSlot(Kind::kTypeArguments_length);
default:
UNREACHABLE();
return GetNativeSlot(Kind::kArray_length);
}
}
const Slot& Slot::GetTypeArgumentsSlotAt(Thread* thread, intptr_t offset) {
ASSERT(offset != Class::kNoTypeArguments);
return SlotCache::Instance(thread).Canonicalize(Slot(
Kind::kTypeArguments, IsImmutableBit::encode(true), kTypeArgumentsCid,
offset, ":type_arguments", /*static_type=*/nullptr, kTagged));
}
const Slot& Slot::GetTypeArgumentsSlotFor(Thread* thread, const Class& cls) {
return GetTypeArgumentsSlotAt(
thread, compiler::target::Class::TypeArgumentsFieldOffset(cls));
}
const Slot& Slot::GetContextVariableSlotFor(Thread* thread,
const LocalVariable& variable) {
ASSERT(variable.is_captured());
return SlotCache::Instance(thread).Canonicalize(
Slot(Kind::kCapturedVariable,
IsImmutableBit::encode(variable.is_final() && !variable.is_late()) |
IsNullableBit::encode(true),
kDynamicCid,
compiler::target::Context::variable_offset(variable.index().value()),
&variable.name(), &variable.type(), kTagged));
}
const Slot& Slot::GetTypeArgumentsIndexSlot(Thread* thread, intptr_t index) {
const intptr_t offset =
compiler::target::TypeArguments::type_at_offset(index);
const Slot& slot =
Slot(Kind::kTypeArgumentsIndex, IsImmutableBit::encode(true), kDynamicCid,
offset, ":argument", /*static_type=*/nullptr, kTagged);
return SlotCache::Instance(thread).Canonicalize(slot);
}
const Slot& Slot::GetArrayElementSlot(Thread* thread,
intptr_t offset_in_bytes) {
const Slot& slot =
Slot(Kind::kArrayElement, IsNullableBit::encode(true), kDynamicCid,
offset_in_bytes, ":array_element", /*static_type=*/nullptr, kTagged);
return SlotCache::Instance(thread).Canonicalize(slot);
}
const Slot& Slot::Get(const Field& field,
const ParsedFunction* parsed_function) {
Thread* thread = Thread::Current();
Zone* zone = thread->zone();
Representation rep = kTagged;
intptr_t nullable_cid = kDynamicCid;
bool is_nullable = true;
if (field.has_pragma()) {
const intptr_t cid = MethodRecognizer::ResultCidFromPragma(field);
if (cid != kDynamicCid) {
nullable_cid = cid;
is_nullable = false;
} else if (MethodRecognizer::HasNonNullableResultTypeFromPragma(field)) {
is_nullable = false;
}
}
AbstractType& type = AbstractType::ZoneHandle(zone, field.type());
if (type.IsStrictlyNonNullable()) {
is_nullable = false;
}
bool used_guarded_state = false;
if (field.guarded_cid() != kIllegalCid &&
field.guarded_cid() != kDynamicCid) {
// Use guarded state if it is more precise then what we already have.
if (nullable_cid == kDynamicCid) {
nullable_cid = field.guarded_cid();
used_guarded_state = true;
}
if (is_nullable && !field.is_nullable()) {
is_nullable = false;
used_guarded_state = true;
}
}
if (field.needs_load_guard()) {
// Should be kept in sync with LoadStaticFieldInstr::ComputeType.
type = Type::DynamicType();
nullable_cid = kDynamicCid;
is_nullable = true;
used_guarded_state = false;
}
if (field.is_non_nullable_integer()) {
ASSERT(FLAG_precompiled_mode);
is_nullable = false;
if (FlowGraphCompiler::IsUnboxedField(field)) {
rep = kUnboxedInt64;
}
}
const Slot& slot = SlotCache::Instance(thread).Canonicalize(
Slot(Kind::kDartField,
IsImmutableBit::encode((field.is_final() && !field.is_late()) ||
field.is_const()) |
IsNullableBit::encode(is_nullable) |
IsGuardedBit::encode(used_guarded_state),
nullable_cid, compiler::target::Field::OffsetOf(field), &field,
&type, rep));
// If properties of this slot were based on the guarded state make sure
// to add the field to the list of guarded fields. Note that during background
// compilation we might have two field clones that have incompatible guarded
// state - however both of these clones would correspond to the same slot.
// That is why we check the is_guarded_field() property of the slot rather
// than look at the current guarded state of the field, because current
// guarded state of the field might be set to kDynamicCid, while it was
// set to something more concrete when the slot was created.
// Note that we could have created this slot during an unsuccessful inlining
// attempt where we built and discarded the graph, in this case guarded
// fields associated with that graph are also discarded. However the slot
// itself stays behind in the compilation global cache. Thus we must always
// try to add it to the list of guarded fields of the current function.
if (slot.is_guarded_field()) {
if (thread->isolate_group()->use_field_guards()) {
ASSERT(parsed_function != nullptr);
parsed_function->AddToGuardedFields(&slot.field());
} else {
// In precompiled mode we use guarded_cid field for type information
// inferred by TFA.
ASSERT(CompilerState::Current().is_aot());
}
}
return slot;
}
CompileType Slot::ComputeCompileType() const {
return CompileType::CreateNullable(is_nullable(), nullable_cid());
}
const AbstractType& Slot::static_type() const {
return static_type_ != nullptr ? *static_type_ : Object::null_abstract_type();
}
const char* Slot::Name() const {
if (IsLocalVariable()) {
return DataAs<const String>()->ToCString();
} else if (IsDartField()) {
return String::Handle(field().name()).ToCString();
} else {
return DataAs<const char>();
}
}
bool Slot::Equals(const Slot* other) const {
if (kind_ != other->kind_) {
return false;
}
switch (kind_) {
case Kind::kTypeArguments:
case Kind::kTypeArgumentsIndex:
case Kind::kArrayElement:
return (offset_in_bytes_ == other->offset_in_bytes_);
case Kind::kCapturedVariable:
return (offset_in_bytes_ == other->offset_in_bytes_) &&
(flags_ == other->flags_) &&
(DataAs<const String>()->ptr() ==
other->DataAs<const String>()->ptr());
case Kind::kDartField:
return (offset_in_bytes_ == other->offset_in_bytes_) &&
other->DataAs<const Field>()->Original() ==
DataAs<const Field>()->Original();
default:
UNREACHABLE();
return false;
}
}
intptr_t Slot::Hashcode() const {
intptr_t result = (static_cast<int8_t>(kind_) * 63 + offset_in_bytes_) * 31;
if (IsDartField()) {
result += String::Handle(DataAs<const Field>()->name()).Hash();
} else if (IsLocalVariable()) {
result += DataAs<const String>()->Hash();
}
return result;
}
} // namespace dart