// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_OBJECTS_SWISS_NAME_DICTIONARY_INL_H_
#define V8_OBJECTS_SWISS_NAME_DICTIONARY_INL_H_

#include <algorithm>

#include "src/base/macros.h"
#include "src/execution/isolate-utils-inl.h"
#include "src/heap/heap.h"
#include "src/objects/fixed-array-inl.h"
#include "src/objects/instance-type-inl.h"
#include "src/objects/js-collection-iterator.h"
#include "src/objects/objects-inl.h"
#include "src/objects/smi.h"
#include "src/objects/swiss-name-dictionary.h"

// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"

namespace v8 {
namespace internal {

#include "torque-generated/src/objects/swiss-name-dictionary-tq-inl.inc"

CAST_ACCESSOR(SwissNameDictionary)
OBJECT_CONSTRUCTORS_IMPL(SwissNameDictionary, HeapObject)

swiss_table::ctrl_t* SwissNameDictionary::CtrlTable() {
  return reinterpret_cast<ctrl_t*>(
      field_address(CtrlTableStartOffset(Capacity())));
}

uint8_t* SwissNameDictionary::PropertyDetailsTable() {
  return reinterpret_cast<uint8_t*>(
      field_address(PropertyDetailsTableStartOffset(Capacity())));
}

int SwissNameDictionary::Capacity() {
  return ReadField<int32_t>(CapacityOffset());
}

void SwissNameDictionary::SetCapacity(int capacity) {
  DCHECK(IsValidCapacity(capacity));

  WriteField<int32_t>(CapacityOffset(), capacity);
}

int SwissNameDictionary::NumberOfElements() {
  return GetMetaTableField(kMetaTableElementCountFieldIndex);
}

int SwissNameDictionary::NumberOfDeletedElements() {
  return GetMetaTableField(kMetaTableDeletedElementCountFieldIndex);
}

void SwissNameDictionary::SetNumberOfElements(int elements) {
  SetMetaTableField(kMetaTableElementCountFieldIndex, elements);
}

void SwissNameDictionary::SetNumberOfDeletedElements(int deleted_elements) {
  SetMetaTableField(kMetaTableDeletedElementCountFieldIndex, deleted_elements);
}

int SwissNameDictionary::UsedCapacity() {
  return NumberOfElements() + NumberOfDeletedElements();
}

// static
constexpr bool SwissNameDictionary::IsValidCapacity(int capacity) {
  return capacity == 0 || (capacity >= kInitialCapacity &&
                           // Must be power of 2.
                           ((capacity & (capacity - 1)) == 0));
}

// static
constexpr int SwissNameDictionary::DataTableSize(int capacity) {
  return capacity * kTaggedSize * kDataTableEntryCount;
}

// static
constexpr int SwissNameDictionary::CtrlTableSize(int capacity) {
  // Doing + |kGroupWidth| due to the copy of first group at the end of control
  // table.
  return (capacity + kGroupWidth) * kOneByteSize;
}

// static
constexpr int SwissNameDictionary::SizeFor(int capacity) {
  DCHECK(IsValidCapacity(capacity));
  return PropertyDetailsTableStartOffset(capacity) + capacity;
}

// We use 7/8th as maximum load factor for non-special cases.
// For 16-wide groups, that gives an average of two empty slots per group.
// Similar to Abseil's CapacityToGrowth.
// static
constexpr int SwissNameDictionary::MaxUsableCapacity(int capacity) {
  DCHECK(IsValidCapacity(capacity));

  if (Group::kWidth == 8 && capacity == 4) {
    // If the group size is 16 we can fully utilize capacity 4: There will be
    // enough kEmpty entries in the ctrl table.
    return 3;
  }
  return capacity - capacity / 8;
}

// Returns |at_least_space_for| * 8/7 for non-special cases. Similar to Abseil's
// GrowthToLowerboundCapacity.
// static
int SwissNameDictionary::CapacityFor(int at_least_space_for) {
  if (at_least_space_for <= 4) {
    if (at_least_space_for == 0) {
      return 0;
    } else if (at_least_space_for < 4) {
      return 4;
    } else if (kGroupWidth == 16) {
      DCHECK_EQ(4, at_least_space_for);
      return 4;
    } else if (kGroupWidth == 8) {
      DCHECK_EQ(4, at_least_space_for);
      return 8;
    }
  }

  int non_normalized = at_least_space_for + at_least_space_for / 7;
  return base::bits::RoundUpToPowerOfTwo32(non_normalized);
}

int SwissNameDictionary::EntryForEnumerationIndex(int enumeration_index) {
  DCHECK_LT(enumeration_index, UsedCapacity());
  return GetMetaTableField(kMetaTableEnumerationDataStartIndex +
                           enumeration_index);
}

void SwissNameDictionary::SetEntryForEnumerationIndex(int enumeration_index,
                                                      int entry) {
  DCHECK_LT(enumeration_index, UsedCapacity());
  DCHECK_LT(static_cast<unsigned>(entry), static_cast<unsigned>(Capacity()));
  DCHECK(IsFull(GetCtrl(entry)));

  SetMetaTableField(kMetaTableEnumerationDataStartIndex + enumeration_index,
                    entry);
}

template <typename IsolateT>
InternalIndex SwissNameDictionary::FindEntry(IsolateT* isolate, Object key) {
  Name name = Name::cast(key);
  DCHECK(name.IsUniqueName());
  uint32_t hash = name.hash();

  // We probe the hash table in groups of |kGroupWidth| buckets. One bucket
  // corresponds to a 1-byte entry in the control table.
  // Each group can be uniquely identified by the index of its first bucket,
  // which must be a value between 0 (inclusive) and Capacity() (exclusive).
  // Note that logically, groups wrap around after index Capacity() - 1. This
  // means that probing the group starting at, for example, index Capacity() - 1
  // means probing CtrlTable()[Capacity() - 1] followed by CtrlTable()[0] to
  // CtrlTable()[6], assuming a group width of 8. However, in memory, this is
  // achieved by maintaining an additional |kGroupWidth| bytes after the first
  // Capacity() entries of the control table. These contain a copy of the first
  // max(Capacity(), kGroupWidth) entries of the control table. If Capacity() <
  // |kGroupWidth|, then the remaining |kGroupWidth| - Capacity() control bytes
  // are left as |kEmpty|.
  // This means that actually, probing the group starting
  // at index Capacity() - 1 is achieved by probing CtrlTable()[Capacity() - 1],
  // followed by CtrlTable()[Capacity()] to CtrlTable()[Capacity() + 7].

  ctrl_t* ctrl = CtrlTable();
  auto seq = probe(hash, Capacity());
  // At this point, seq.offset() denotes the index of the first bucket in the
  // first group to probe. Note that this doesn't have to be divisible by
  // |kGroupWidth|, but can have any value between 0 (inclusive) and Capacity()
  // (exclusive).
  while (true) {
    Group g{ctrl + seq.offset()};
    for (int i : g.Match(swiss_table::H2(hash))) {
      int candidate_entry = seq.offset(i);
      Object candidate_key = KeyAt(candidate_entry);
      // This key matching is SwissNameDictionary specific!
      if (candidate_key == key) return InternalIndex(candidate_entry);
    }
    if (g.MatchEmpty()) return InternalIndex::NotFound();

    // The following selects the next group to probe. Note that seq.offset()
    // always advances by a multiple of |kGroupWidth|, modulo Capacity(). This
    // is done in a way such that we visit Capacity() / |kGroupWidth|
    // non-overlapping (!) groups before we would visit the same group (or
    // bucket) again.
    seq.next();

    // If the following DCHECK weren't true, we would have probed all Capacity()
    // different buckets without finding one containing |kEmpty| (which would
    // haved triggered the g.MatchEmpty() check above). This must not be the
    // case because the maximum load factor of 7/8 guarantees that there must
    // always remain empty buckets.
    //
    // The only exception from this rule are small tables, where 2 * Capacity()
    // < |kGroupWidth|, in which case all Capacity() entries can be filled
    // without leaving empty buckets. The layout of the control
    // table guarantees that after the first Capacity() entries of the control
    // table, the control table contains a copy of those first Capacity()
    // entries, followed by kGroupWidth - 2 * Capacity() entries containing
    // |kEmpty|. This guarantees that the g.MatchEmpty() check above will
    // always trigger if the element wasn't found, correctly preventing us from
    // probing more than one group in this special case.
    DCHECK_LT(seq.index(), Capacity());
  }
}

template <typename IsolateT>
InternalIndex SwissNameDictionary::FindEntry(IsolateT* isolate,
                                             Handle<Object> key) {
  return FindEntry(isolate, *key);
}

Object SwissNameDictionary::LoadFromDataTable(int entry, int data_offset) {
  return LoadFromDataTable(GetPtrComprCageBase(*this), entry, data_offset);
}

Object SwissNameDictionary::LoadFromDataTable(PtrComprCageBase cage_base,
                                              int entry, int data_offset) {
  DCHECK_LT(static_cast<unsigned>(entry), static_cast<unsigned>(Capacity()));
  int offset = DataTableStartOffset() +
               (entry * kDataTableEntryCount + data_offset) * kTaggedSize;
  return TaggedField<Object>::Relaxed_Load(cage_base, *this, offset);
}

void SwissNameDictionary::StoreToDataTable(int entry, int data_offset,
                                           Object data) {
  DCHECK_LT(static_cast<unsigned>(entry), static_cast<unsigned>(Capacity()));

  int offset = DataTableStartOffset() +
               (entry * kDataTableEntryCount + data_offset) * kTaggedSize;

  RELAXED_WRITE_FIELD(*this, offset, data);
  WRITE_BARRIER(*this, offset, data);
}

void SwissNameDictionary::StoreToDataTableNoBarrier(int entry, int data_offset,
                                                    Object data) {
  DCHECK_LT(static_cast<unsigned>(entry), static_cast<unsigned>(Capacity()));

  int offset = DataTableStartOffset() +
               (entry * kDataTableEntryCount + data_offset) * kTaggedSize;

  RELAXED_WRITE_FIELD(*this, offset, data);
}

void SwissNameDictionary::ClearDataTableEntry(Isolate* isolate, int entry) {
  ReadOnlyRoots roots(isolate);

  StoreToDataTable(entry, kDataTableKeyEntryIndex, roots.the_hole_value());
  StoreToDataTable(entry, kDataTableValueEntryIndex, roots.the_hole_value());
}

void SwissNameDictionary::ValueAtPut(int entry, Object value) {
  DCHECK(!value.IsTheHole());
  StoreToDataTable(entry, kDataTableValueEntryIndex, value);
}

void SwissNameDictionary::ValueAtPut(InternalIndex entry, Object value) {
  ValueAtPut(entry.as_int(), value);
}

void SwissNameDictionary::SetKey(int entry, Object key) {
  DCHECK(!key.IsTheHole());
  StoreToDataTable(entry, kDataTableKeyEntryIndex, key);
}

void SwissNameDictionary::DetailsAtPut(int entry, PropertyDetails details) {
  DCHECK_LT(static_cast<unsigned>(entry), static_cast<unsigned>(Capacity()));
  uint8_t encoded_details = details.ToByte();
  PropertyDetailsTable()[entry] = encoded_details;
}

void SwissNameDictionary::DetailsAtPut(InternalIndex entry,
                                       PropertyDetails details) {
  DetailsAtPut(entry.as_int(), details);
}

Object SwissNameDictionary::KeyAt(int entry) {
  return LoadFromDataTable(entry, kDataTableKeyEntryIndex);
}

Object SwissNameDictionary::KeyAt(InternalIndex entry) {
  return KeyAt(entry.as_int());
}

Name SwissNameDictionary::NameAt(InternalIndex entry) {
  return Name::cast(KeyAt(entry));
}

// This version can be called on empty buckets.
Object SwissNameDictionary::ValueAtRaw(int entry) {
  return LoadFromDataTable(entry, kDataTableValueEntryIndex);
}

Object SwissNameDictionary::ValueAt(InternalIndex entry) {
  DCHECK(IsFull(GetCtrl(entry.as_int())));
  return ValueAtRaw(entry.as_int());
}

PropertyDetails SwissNameDictionary::DetailsAt(int entry) {
  // GetCtrl(entry) does a bounds check for |entry| value.
  DCHECK(IsFull(GetCtrl(entry)));

  uint8_t encoded_details = PropertyDetailsTable()[entry];
  return PropertyDetails::FromByte(encoded_details);
}

PropertyDetails SwissNameDictionary::DetailsAt(InternalIndex entry) {
  return DetailsAt(entry.as_int());
}

// static
template <typename IsolateT>
Handle<SwissNameDictionary> SwissNameDictionary::EnsureGrowable(
    IsolateT* isolate, Handle<SwissNameDictionary> table) {
  int capacity = table->Capacity();

  if (table->UsedCapacity() < MaxUsableCapacity(capacity)) {
    // We have room for at least one more entry, nothing to do.
    return table;
  }

  int new_capacity = capacity == 0 ? kInitialCapacity : capacity * 2;
  return Rehash(isolate, table, new_capacity);
}

swiss_table::ctrl_t SwissNameDictionary::GetCtrl(int entry) {
  DCHECK_LT(static_cast<unsigned>(entry), static_cast<unsigned>(Capacity()));

  return CtrlTable()[entry];
}

void SwissNameDictionary::SetCtrl(int entry, ctrl_t h) {
  int capacity = Capacity();
  DCHECK_LT(static_cast<unsigned>(entry), static_cast<unsigned>(capacity));

  ctrl_t* ctrl = CtrlTable();
  ctrl[entry] = h;

  // The ctrl table contains a copy of the first group (i.e., the group starting
  // at entry 0) after the first |capacity| entries of the ctrl table. This
  // means that the ctrl table always has size |capacity| + |kGroupWidth|.
  // However, note that we may have |capacity| < |kGroupWidth|. For example, if
  // Capacity() == 8 and |kGroupWidth| == 16, then ctrl[0] is copied to ctrl[8],
  // ctrl[1] to ctrl[9], etc. In this case, ctrl[16] to ctrl[23] remain unused,
  // which means that their values are always Ctrl::kEmpty.
  // We achieve the necessary copying without branching here using some bit
  // magic: We set {copy_entry = entry} in those cases where we don't actually
  // have to perform a copy (meaning that we just repeat the {ctrl[entry] = h}
  // from above). If we do need to do some actual copying, we set {copy_entry =
  // Capacity() + entry}.

  int mask = capacity - 1;
  int copy_entry =
      ((entry - Group::kWidth) & mask) + 1 + ((Group::kWidth - 1) & mask);
  DCHECK_IMPLIES(entry < static_cast<int>(Group::kWidth),
                 copy_entry == capacity + entry);
  DCHECK_IMPLIES(entry >= static_cast<int>(Group::kWidth), copy_entry == entry);
  ctrl[copy_entry] = h;
}

// static
inline int SwissNameDictionary::FindFirstEmpty(uint32_t hash) {
  // See SwissNameDictionary::FindEntry for description of probing algorithm.

  auto seq = probe(hash, Capacity());
  while (true) {
    Group g{CtrlTable() + seq.offset()};
    auto mask = g.MatchEmpty();
    if (mask) {
      // Note that picking the lowest bit set here means using the leftmost
      // empty bucket in the group. Here, "left" means smaller entry/bucket
      // index.
      return seq.offset(mask.LowestBitSet());
    }
    seq.next();
    DCHECK_LT(seq.index(), Capacity());
  }
}

void SwissNameDictionary::SetMetaTableField(int field_index, int value) {
  // See the STATIC_ASSERTs on |kMax1ByteMetaTableCapacity| and
  // |kMax2ByteMetaTableCapacity| in the .cc file for an explanation of these
  // constants.
  int capacity = Capacity();
  ByteArray meta_table = this->meta_table();
  if (capacity <= kMax1ByteMetaTableCapacity) {
    SetMetaTableField<uint8_t>(meta_table, field_index, value);
  } else if (capacity <= kMax2ByteMetaTableCapacity) {
    SetMetaTableField<uint16_t>(meta_table, field_index, value);
  } else {
    SetMetaTableField<uint32_t>(meta_table, field_index, value);
  }
}

int SwissNameDictionary::GetMetaTableField(int field_index) {
  // See the STATIC_ASSERTs on |kMax1ByteMetaTableCapacity| and
  // |kMax2ByteMetaTableCapacity| in the .cc file for an explanation of these
  // constants.
  int capacity = Capacity();
  ByteArray meta_table = this->meta_table();
  if (capacity <= kMax1ByteMetaTableCapacity) {
    return GetMetaTableField<uint8_t>(meta_table, field_index);
  } else if (capacity <= kMax2ByteMetaTableCapacity) {
    return GetMetaTableField<uint16_t>(meta_table, field_index);
  } else {
    return GetMetaTableField<uint32_t>(meta_table, field_index);
  }
}

// static
template <typename T>
void SwissNameDictionary::SetMetaTableField(ByteArray meta_table,
                                            int field_index, int value) {
  STATIC_ASSERT((std::is_same<T, uint8_t>::value) ||
                (std::is_same<T, uint16_t>::value) ||
                (std::is_same<T, uint32_t>::value));
  DCHECK_LE(value, std::numeric_limits<T>::max());
  DCHECK_LT(meta_table.GetDataStartAddress() + field_index * sizeof(T),
            meta_table.GetDataEndAddress());
  T* raw_data = reinterpret_cast<T*>(meta_table.GetDataStartAddress());
  raw_data[field_index] = value;
}

// static
template <typename T>
int SwissNameDictionary::GetMetaTableField(ByteArray meta_table,
                                           int field_index) {
  STATIC_ASSERT((std::is_same<T, uint8_t>::value) ||
                (std::is_same<T, uint16_t>::value) ||
                (std::is_same<T, uint32_t>::value));
  DCHECK_LT(meta_table.GetDataStartAddress() + field_index * sizeof(T),
            meta_table.GetDataEndAddress());
  T* raw_data = reinterpret_cast<T*>(meta_table.GetDataStartAddress());
  return raw_data[field_index];
}

constexpr int SwissNameDictionary::MetaTableSizePerEntryFor(int capacity) {
  DCHECK(IsValidCapacity(capacity));

  // See the STATIC_ASSERTs on |kMax1ByteMetaTableCapacity| and
  // |kMax2ByteMetaTableCapacity| in the .cc file for an explanation of these
  // constants.
  if (capacity <= kMax1ByteMetaTableCapacity) {
    return sizeof(uint8_t);
  } else if (capacity <= kMax2ByteMetaTableCapacity) {
    return sizeof(uint16_t);
  } else {
    return sizeof(uint32_t);
  }
}

constexpr int SwissNameDictionary::MetaTableSizeFor(int capacity) {
  DCHECK(IsValidCapacity(capacity));

  int per_entry_size = MetaTableSizePerEntryFor(capacity);

  // The enumeration table only needs to have as many slots as there can be
  // present + deleted entries in the hash table (= maximum load factor *
  // capactiy). Two more slots to store the number of present and deleted
  // entries.
  return per_entry_size * (MaxUsableCapacity(capacity) + 2);
}

bool SwissNameDictionary::IsKey(ReadOnlyRoots roots, Object key_candidate) {
  return key_candidate != roots.the_hole_value();
}

bool SwissNameDictionary::ToKey(ReadOnlyRoots roots, int entry,
                                Object* out_key) {
  Object k = KeyAt(entry);
  if (!IsKey(roots, k)) return false;
  *out_key = k;
  return true;
}

bool SwissNameDictionary::ToKey(ReadOnlyRoots roots, InternalIndex entry,
                                Object* out_key) {
  return ToKey(roots, entry.as_int(), out_key);
}

// static
template <typename IsolateT>
Handle<SwissNameDictionary> SwissNameDictionary::Add(
    IsolateT* isolate, Handle<SwissNameDictionary> original_table,
    Handle<Name> key, Handle<Object> value, PropertyDetails details,
    InternalIndex* entry_out) {
  DCHECK(original_table->FindEntry(isolate, *key).is_not_found());

  Handle<SwissNameDictionary> table = EnsureGrowable(isolate, original_table);

  int nof = table->NumberOfElements();
  int nod = table->NumberOfDeletedElements();
  int new_enum_index = nof + nod;

  int new_entry = table->AddInternal(*key, *value, details);

  table->SetNumberOfElements(nof + 1);
  table->SetEntryForEnumerationIndex(new_enum_index, new_entry);

  if (entry_out) {
    *entry_out = InternalIndex(new_entry);
  }

  return table;
}

int SwissNameDictionary::AddInternal(Name key, Object value,
                                     PropertyDetails details) {
  DisallowHeapAllocation no_gc;

  DCHECK(key.IsUniqueName());
  DCHECK_LE(UsedCapacity(), MaxUsableCapacity(Capacity()));

  uint32_t hash = key.hash();

  // For now we don't re-use deleted buckets (due to enumeration table
  // complications), which is why we only look for empty buckets here, not
  // deleted ones.
  int target = FindFirstEmpty(hash);

  SetCtrl(target, swiss_table::H2(hash));
  SetKey(target, key);
  ValueAtPut(target, value);
  DetailsAtPut(target, details);

  // Note that we do not update the number of elements or the enumeration table
  // in this function.

  return target;
}

template <typename IsolateT>
void SwissNameDictionary::Initialize(IsolateT* isolate, ByteArray meta_table,
                                     int capacity) {
  DCHECK(IsValidCapacity(capacity));
  DisallowHeapAllocation no_gc;
  ReadOnlyRoots roots(isolate);

  SetCapacity(capacity);
  SetHash(PropertyArray::kNoHashSentinel);

  memset(CtrlTable(), Ctrl::kEmpty, CtrlTableSize(capacity));

  MemsetTagged(RawField(DataTableStartOffset()), roots.the_hole_value(),
               capacity * kDataTableEntryCount);

  set_meta_table(meta_table);

  SetNumberOfElements(0);
  SetNumberOfDeletedElements(0);

  // We leave the enumeration table PropertyDetails table and uninitialized.
}

SwissNameDictionary::IndexIterator::IndexIterator(
    Handle<SwissNameDictionary> dict, int start)
    : enum_index_{start}, dict_{dict} {
  if (!COMPRESS_POINTERS_IN_ISOLATE_CAGE_BOOL && dict.is_null()) {
    used_capacity_ = 0;
  } else {
    used_capacity_ = dict->UsedCapacity();
  }
}

SwissNameDictionary::IndexIterator&
SwissNameDictionary::IndexIterator::operator++() {
  DCHECK_LT(enum_index_, used_capacity_);
  ++enum_index_;
  return *this;
}

bool SwissNameDictionary::IndexIterator::operator==(
    const SwissNameDictionary::IndexIterator& b) const {
  DCHECK_LE(enum_index_, used_capacity_);
  DCHECK_LE(b.enum_index_, used_capacity_);
  DCHECK(dict_.equals(b.dict_));

  return this->enum_index_ == b.enum_index_;
}

bool SwissNameDictionary::IndexIterator::operator!=(
    const IndexIterator& b) const {
  return !(*this == b);
}

InternalIndex SwissNameDictionary::IndexIterator::operator*() {
  DCHECK_LE(enum_index_, used_capacity_);

  if (enum_index_ == used_capacity_) return InternalIndex::NotFound();

  return InternalIndex(dict_->EntryForEnumerationIndex(enum_index_));
}

SwissNameDictionary::IndexIterable::IndexIterable(
    Handle<SwissNameDictionary> dict)
    : dict_{dict} {}

SwissNameDictionary::IndexIterator SwissNameDictionary::IndexIterable::begin() {
  return IndexIterator(dict_, 0);
}

SwissNameDictionary::IndexIterator SwissNameDictionary::IndexIterable::end() {
  if (!COMPRESS_POINTERS_IN_ISOLATE_CAGE_BOOL && dict_.is_null()) {
    return IndexIterator(dict_, 0);
  } else {
    DCHECK(!dict_.is_null());
    return IndexIterator(dict_, dict_->UsedCapacity());
  }
}

SwissNameDictionary::IndexIterable
SwissNameDictionary::IterateEntriesOrdered() {
  // If we are supposed to iterate the empty dictionary (which is non-writable)
  // and pointer compression with a per-Isolate cage is disabled, we have no
  // simple way to get the isolate, which we would need to create a handle.
  // TODO(emrich): Consider always using roots.empty_swiss_dictionary_handle()
  // in the condition once this function gets Isolate as a parameter in order to
  // avoid empty dict checks.
  if (!COMPRESS_POINTERS_IN_ISOLATE_CAGE_BOOL && Capacity() == 0)
    return IndexIterable(Handle<SwissNameDictionary>::null());

  Isolate* isolate;
  GetIsolateFromHeapObject(*this, &isolate);
  DCHECK_NE(isolate, nullptr);
  return IndexIterable(handle(*this, isolate));
}

SwissNameDictionary::IndexIterable SwissNameDictionary::IterateEntries() {
  return IterateEntriesOrdered();
}

void SwissNameDictionary::SetHash(int32_t hash) {
  WriteField(PrefixOffset(), hash);
}

int SwissNameDictionary::Hash() { return ReadField<int32_t>(PrefixOffset()); }

// static
constexpr int SwissNameDictionary::MaxCapacity() {
  int const_size =
      DataTableStartOffset() + ByteArray::kHeaderSize +
      // Size for present and deleted element count at max capacity:
      2 * sizeof(uint32_t);
  int per_entry_size =
      // size of data table entries:
      kDataTableEntryCount * kTaggedSize +
      // ctrl table entry size:
      kOneByteSize +
      // PropertyDetails table entry size:
      kOneByteSize +
      // Enumeration table entry size at maximum capacity:
      sizeof(uint32_t);

  int result = (FixedArray::kMaxSize - const_size) / per_entry_size;
  DCHECK_GE(Smi::kMaxValue, result);

  return result;
}

// static
constexpr int SwissNameDictionary::PrefixOffset() {
  return HeapObject::kHeaderSize;
}

// static
constexpr int SwissNameDictionary::CapacityOffset() {
  return PrefixOffset() + sizeof(uint32_t);
}

// static
constexpr int SwissNameDictionary::MetaTablePointerOffset() {
  return CapacityOffset() + sizeof(int32_t);
}

// static
constexpr int SwissNameDictionary::DataTableStartOffset() {
  return MetaTablePointerOffset() + kTaggedSize;
}

// static
constexpr int SwissNameDictionary::DataTableEndOffset(int capacity) {
  return CtrlTableStartOffset(capacity);
}

// static
constexpr int SwissNameDictionary::CtrlTableStartOffset(int capacity) {
  return DataTableStartOffset() + DataTableSize(capacity);
}

// static
constexpr int SwissNameDictionary::PropertyDetailsTableStartOffset(
    int capacity) {
  return CtrlTableStartOffset(capacity) + CtrlTableSize(capacity);
}

// static
bool SwissNameDictionary::IsEmpty(ctrl_t c) { return c == Ctrl::kEmpty; }

// static
bool SwissNameDictionary::IsFull(ctrl_t c) {
  STATIC_ASSERT(Ctrl::kEmpty < 0);
  STATIC_ASSERT(Ctrl::kDeleted < 0);
  STATIC_ASSERT(Ctrl::kSentinel < 0);
  return c >= 0;
}

// static
bool SwissNameDictionary::IsDeleted(ctrl_t c) { return c == Ctrl::kDeleted; }

// static
bool SwissNameDictionary::IsEmptyOrDeleted(ctrl_t c) {
  STATIC_ASSERT(Ctrl::kDeleted < Ctrl::kSentinel);
  STATIC_ASSERT(Ctrl::kEmpty < Ctrl::kSentinel);
  STATIC_ASSERT(Ctrl::kSentinel < 0);
  return c < Ctrl::kSentinel;
}

// static
swiss_table::ProbeSequence<SwissNameDictionary::kGroupWidth>
SwissNameDictionary::probe(uint32_t hash, int capacity) {
  // If |capacity| is 0, we must produce 1 here, such that the - 1 below
  // yields 0, which is the correct modulo mask for a table of capacity 0.
  int non_zero_capacity = capacity | (capacity == 0);
  return swiss_table::ProbeSequence<SwissNameDictionary::kGroupWidth>(
      swiss_table::H1(hash), static_cast<uint32_t>(non_zero_capacity - 1));
}

ACCESSORS_CHECKED2(SwissNameDictionary, meta_table, ByteArray,
                   MetaTablePointerOffset(), true,
                   value.length() >= kMetaTableEnumerationDataStartIndex)

}  // namespace internal
}  // namespace v8

#endif  // V8_OBJECTS_SWISS_NAME_DICTIONARY_INL_H_
