// 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 > 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::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_FINAL_COMPRESSED \ (IsNullableBit::encode(true) | IsImmutableBit::encode(true) | \ IsCompressedBit::encode(true)) #define NULLABLE_FIELD_VAR (IsNullableBit::encode(true)) #define NULLABLE_FIELD_VAR_COMPRESSED \ (IsNullableBit::encode(true) | IsCompressedBit::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_FINAL_COMPRESSED \ (Slot::IsImmutableBit::encode(true) | Slot::IsCompressedBit::encode(true)) #define NONNULLABLE_FIELD_VAR (0) #define NONNULLABLE_FIELD_VAR_COMPRESSED (Slot::IsCompressedBit::encode(true)) #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(kind) < kNativeSlotsCount); return native_fields_.load()[static_cast(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) | IsCompressedBit::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 { // If we unboxed the slot, we may know a more precise type. switch (representation()) { case kUnboxedInt64: if (nullable_cid() == kDynamicCid) { return CompileType::Int(); } // Might be an CID like nullable_cid == kSmiCid. break; case kUnboxedDouble: return CompileType::FromCid(kDoubleCid); case kUnboxedInt32x4: return CompileType::FromCid(kInt32x4Cid); case kUnboxedFloat32x4: return CompileType::FromCid(kFloat32x4Cid); case kUnboxedFloat64x2: return CompileType::FromCid(kFloat64x2Cid); default: break; } return CompileType(is_nullable(), nullable_cid(), nullable_cid() == kDynamicCid ? static_type_ : nullptr); } 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()->ToCString(); } else if (IsDartField()) { return String::Handle(field().name()).ToCString(); } else { return DataAs(); } } bool Slot::Equals(const Slot& other) const { if (kind_ != other.kind_ || offset_in_bytes_ != other.offset_in_bytes_) { return false; } switch (kind_) { case Kind::kTypeArguments: case Kind::kTypeArgumentsIndex: case Kind::kArrayElement: return true; case Kind::kCapturedVariable: return (flags_ == other.flags_) && (DataAs()->ptr() == other.DataAs()->ptr()) && static_type_->Equals(*(other.static_type_)); case Kind::kDartField: return other.DataAs()->Original() == DataAs()->Original(); default: UNREACHABLE(); return false; } } uword Slot::Hash() const { uword result = (static_cast(kind_) * 63 + offset_in_bytes_) * 31; if (IsDartField()) { result += String::Handle(DataAs()->name()).Hash(); } else if (IsLocalVariable()) { result += DataAs()->Hash(); } return result; } } // namespace dart