text: // 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_;
    PointerSet<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(ClassName)                                        \
    (IsNullableBit::encode(true) | IsImmutableBit::encode(true) |                \
     IsCompressedBit::encode(ClassName::ContainsCompressedPointers()))
  #define NULLABLE_FIELD_VAR(ClassName)                                          \
    (IsNullableBit::encode(true) |                                               \
     IsCompressedBit::encode(ClassName::ContainsCompressedPointers()))
  #define DEFINE_NULLABLE_BOXED_NATIVE_FIELD(ClassName, UnderlyingType,          \
                                             FieldName, cid, mutability)         \
    Slot(Kind::k##ClassName##_##FieldName,                                       \
         NULLABLE_FIELD_##mutability(ClassName), 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(ClassName)                                     \
    (Slot::IsImmutableBit::encode(true) |                                        \
     IsCompressedBit::encode(ClassName::ContainsCompressedPointers()))
  #define NONNULLABLE_FIELD_VAR(ClassName)                                       \
    (IsCompressedBit::encode(ClassName::ContainsCompressedPointers()))
  #define DEFINE_NONNULLABLE_BOXED_NATIVE_FIELD(ClassName, UnderlyingType,       \
                                                FieldName, cid, mutability)      \
    Slot(Kind::k##ClassName##_##FieldName,                                       \
         NONNULLABLE_FIELD_##mutability(ClassName), 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
  #undef NONNULLABLE_FIELD_VAR
  #undef NONNULLABLE_FIELD_FINAL
  
  #define UNBOXED_FIELD_FINAL (Slot::IsImmutableBit::encode(true))
  #define UNBOXED_FIELD_VAR (0)
  #define DEFINE_UNBOXED_NATIVE_FIELD(ClassName, UnderlyingType, FieldName,      \
                                      representation, mutability)                \
    Slot(Kind::k##ClassName##_##FieldName, UNBOXED_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 UNBOXED_FIELD_VAR
  #undef UNBOXED_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)];
  }
  
  bool Slot::IsImmutableLengthSlot() const {
    switch (kind()) {
      case Slot::Kind::kArray_length:
      case Slot::Kind::kTypedDataBase_length:
      case Slot::Kind::kString_length:
      case Slot::Kind::kTypeArguments_length:
        return true;
      case Slot::Kind::kGrowableObjectArray_length:
        return false;
  
        // Not length loads.
  #define UNBOXED_NATIVE_SLOT_CASE(Class, Untagged, Field, Rep, IsFinal)         \
    case Slot::Kind::k##Class##_##Field:
        UNBOXED_NATIVE_SLOTS_LIST(UNBOXED_NATIVE_SLOT_CASE)
  #undef UNBOXED_NATIVE_SLOT_CASE
      case Slot::Kind::kLinkedHashBase_index:
      case Slot::Kind::kImmutableLinkedHashBase_index:
      case Slot::Kind::kLinkedHashBase_data:
      case Slot::Kind::kImmutableLinkedHashBase_data:
      case Slot::Kind::kLinkedHashBase_hash_mask:
      case Slot::Kind::kLinkedHashBase_used_data:
      case Slot::Kind::kLinkedHashBase_deleted_keys:
      case Slot::Kind::kArgumentsDescriptor_type_args_len:
      case Slot::Kind::kArgumentsDescriptor_positional_count:
      case Slot::Kind::kArgumentsDescriptor_count:
      case Slot::Kind::kArgumentsDescriptor_size:
      case Slot::Kind::kArrayElement:
      case Slot::Kind::kInstance_native_fields_array:
      case Slot::Kind::kTypeArguments:
      case Slot::Kind::kTypedDataView_offset_in_bytes:
      case Slot::Kind::kTypedDataView_typed_data:
      case Slot::Kind::kGrowableObjectArray_data:
      case Slot::Kind::kArray_type_arguments:
      case Slot::Kind::kContext_parent:
      case Slot::Kind::kClosure_context:
      case Slot::Kind::kClosure_delayed_type_arguments:
      case Slot::Kind::kClosure_function:
      case Slot::Kind::kClosure_function_type_arguments:
      case Slot::Kind::kClosure_instantiator_type_arguments:
      case Slot::Kind::kClosure_hash:
      case Slot::Kind::kCapturedVariable:
      case Slot::Kind::kDartField:
      case Slot::Kind::kFinalizer_callback:
      case Slot::Kind::kFinalizer_type_arguments:
      case Slot::Kind::kFinalizerBase_all_entries:
      case Slot::Kind::kFinalizerBase_detachments:
      case Slot::Kind::kFinalizerBase_entries_collected:
      case Slot::Kind::kFinalizerEntry_detach:
      case Slot::Kind::kFinalizerEntry_finalizer:
      case Slot::Kind::kFinalizerEntry_next:
      case Slot::Kind::kFinalizerEntry_token:
      case Slot::Kind::kFinalizerEntry_value:
      case Slot::Kind::kNativeFinalizer_callback:
      case Slot::Kind::kFunction_data:
      case Slot::Kind::kFunction_signature:
      case Slot::Kind::kFunctionType_named_parameter_names:
      case Slot::Kind::kFunctionType_parameter_types:
      case Slot::Kind::kFunctionType_type_parameters:
      case Slot::Kind::kType_arguments:
      case Slot::Kind::kTypeArgumentsIndex:
      case Slot::Kind::kTypeParameters_names:
      case Slot::Kind::kTypeParameters_flags:
      case Slot::Kind::kTypeParameters_bounds:
      case Slot::Kind::kTypeParameters_defaults:
      case Slot::Kind::kTypeParameter_bound:
      case Slot::Kind::kUnhandledException_exception:
      case Slot::Kind::kUnhandledException_stacktrace:
      case Slot::Kind::kWeakProperty_key:
      case Slot::Kind::kWeakProperty_value:
      case Slot::Kind::kWeakReference_target:
      case Slot::Kind::kWeakReference_type_arguments:
        return false;
    }
    UNREACHABLE();
    return false;
  }
  
  // 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::GetTypeArgumentsSlotFor(Thread* thread, const Class& cls) {
    if (cls.id() == kArrayCid || cls.id() == kImmutableArrayCid) {
      return Slot::Array_type_arguments();
    }
    const intptr_t offset =
        compiler::target::Class::TypeArgumentsFieldOffset(cls);
    ASSERT(offset != Class::kNoTypeArguments);
    return SlotCache::Instance(thread).Canonicalize(
        Slot(Kind::kTypeArguments,
             IsImmutableBit::encode(true) |
                 IsCompressedBit::encode(
                     compiler::target::Class::HasCompressedPointers(cls)),
             kTypeArgumentsCid, offset, ":type_arguments",
             /*static_type=*/nullptr, kTagged));
  }
  
  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) |
                 IsCompressedBit::encode(Context::ContainsCompressedPointers()) |
                 IsSentinelVisibleBit::encode(variable.is_late()),
             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(TypeArguments::ContainsCompressedPointers()),
        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) |
                 IsCompressedBit::encode(Array::ContainsCompressedPointers()),
             kDynamicCid, offset_in_bytes, ":array_element",
             /*static_type=*/nullptr, kTagged);
    return SlotCache::Instance(thread).Canonicalize(slot);
  }
  
  FieldGuardState::FieldGuardState(const Field& field)
      : state_(GuardedCidBits::encode(field.guarded_cid()) |
               IsNonNullableIntegerBit::encode(field.is_non_nullable_integer()) |
               IsUnboxingCandidateBit::encode(field.is_unboxing_candidate()) |
               IsNullableBit::encode(field.is_nullable())) {}
  
  bool FieldGuardState::IsUnboxed() const {
    ASSERT(!is_non_nullable_integer() || FLAG_precompiled_mode);
    const bool valid_class = ((FlowGraphCompiler::SupportsUnboxedDoubles() &&
                               (guarded_cid() == kDoubleCid)) ||
                              (FlowGraphCompiler::SupportsUnboxedSimd128() &&
                               (guarded_cid() == kFloat32x4Cid)) ||
                              (FlowGraphCompiler::SupportsUnboxedSimd128() &&
                               (guarded_cid() == kFloat64x2Cid)) ||
                              is_non_nullable_integer());
    return is_unboxing_candidate() && !is_nullable() && valid_class;
  }
  
  bool FieldGuardState::IsPotentialUnboxed() const {
    if (FLAG_precompiled_mode) {
      // kernel_loader.cc:ReadInferredType sets the guarded cid for fields based
      // on inferred types from TFA (if available). The guarded cid is therefore
      // proven to be correct.
      return IsUnboxed();
    }
  
    return is_unboxing_candidate() &&
           (IsUnboxed() || (guarded_cid() == kIllegalCid));
  }
  
  bool Slot::IsUnboxed() const {
    return field_guard_state().IsUnboxed();
  }
  
  bool Slot::IsPotentialUnboxed() const {
    return field_guard_state().IsPotentialUnboxed();
  }
  
  Representation Slot::UnboxedRepresentation() const {
    switch (field_guard_state().guarded_cid()) {
      case kDoubleCid:
        return kUnboxedDouble;
      case kFloat32x4Cid:
        return kUnboxedFloat32x4;
      case kFloat64x2Cid:
        return kUnboxedFloat64x2;
      default:
        RELEASE_ASSERT(field_guard_state().is_non_nullable_integer());
        return kUnboxedInt64;
    }
  }
  
  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;
    }
  
    FieldGuardState field_guard_state(field);
  
    bool used_guarded_state = false;
    if (field_guard_state.guarded_cid() != kIllegalCid &&
        field_guard_state.guarded_cid() != kDynamicCid) {
      // Use guarded state if it is more precise then what we already have.
      if (nullable_cid == kDynamicCid) {
        nullable_cid = field_guard_state.guarded_cid();
        used_guarded_state = true;
      }
  
      if (is_nullable && !field_guard_state.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_guard_state.is_non_nullable_integer()) {
      ASSERT(FLAG_precompiled_mode);
      is_nullable = false;
      if (field_guard_state.IsUnboxed()) {
        rep = kUnboxedInt64;
      }
    }
  
    Class& owner = Class::Handle(zone, field.Owner());
    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) |
            IsCompressedBit::encode(
                compiler::target::Class::HasCompressedPointers(owner)) |
            IsSentinelVisibleBit::encode(field.is_late() && field.is_final() &&
                                         !field.has_initializer()),
        nullable_cid, compiler::target::Field::OffsetOf(field), &field, &type,
        rep, field_guard_state));
  
    // 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()) {
  #if defined(TARGET_ARCH_IS_32_BIT)
      // Int32/Uint32 values are not guaranteed to fit in a Smi.
      case kUnboxedInt32:
      case kUnboxedUint32:
  #endif
      case kUnboxedInt64:
        if (nullable_cid() == kDynamicCid) {
          return CompileType::Int();
        }
        break;
  #if defined(TARGET_ARCH_IS_64_BIT)
      // Int32/Uint32 values are guaranteed to fit in a Smi.
      case kUnboxedInt32:
      case kUnboxedUint32:
  #endif
      case kUnboxedUint8:
        return CompileType::Smi();
      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(), is_sentinel_visible(), 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<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_ || 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<const String>()->ptr() ==
                other.DataAs<const String>()->ptr()) &&
               static_type_->Equals(*(other.static_type_));
  
      case Kind::kDartField:
        return other.DataAs<const Field>()->Original() ==
               DataAs<const Field>()->Original();
  
      default:
        UNREACHABLE();
        return false;
    }
  }
  
  uword Slot::Hash() const {
    uword 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
  