// Copyright 2023 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_OFF_HEAP_HASH_TABLE_H_
#define V8_OBJECTS_OFF_HEAP_HASH_TABLE_H_

#include "src/common/globals.h"
#include "src/execution/isolate-utils.h"
#include "src/objects/compressed-slots.h"
#include "src/objects/fixed-array.h"
#include "src/objects/slots.h"
#include "src/objects/smi.h"
#include "src/objects/visitors.h"
#include "src/roots/roots.h"

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

namespace v8 {
namespace internal {

// A base class for building off-heap hash tables (e.g. the string table) that
// stores tagged values.
//
// It is a variable sized structure, with a "header" followed directly in memory
// by the elements themselves. These are accessed as offsets from the elements_
// field, which itself provides storage for the first element.
//
// The elements themselves are stored as an open-addressed hash table, with
// quadratic probing and Smi 0 and Smi 1 as the empty and deleted sentinels,
// respectively.
//
// It is a CRTP class whose derived class must provide the following
// definitions:
//
//  // The number of elements per table entry.
//  static constexpr int kEntrySize;
//
//  // The factor by which to decide if the table ought to shrink.
//  static constexpr int kMaxEmptyFactor;
//
//  // The minimum number of elements for new tables.
//  static constexpr int kMinCapacity;
//
//  // Computes the hash of a key {obj}.
//  static uint32_t Hash(Tagged<Object> obj);
//
//  // Returns whether the lookup key {key} matches the key element {obj}.
//  template <typename IsolateT, typename Key>
//  static bool KeyIsMatch(IsolateT* isolate, Key key, Tagged<Object> obj);
//
//  // Load the key object at entry {index}, decompressing it if needed.
//  Tagged<Object> GetKey(PtrComprCageBase, InternalIndex index);
//
//  // Store the key object at the entry {index}.
//  void SetKey(InternalIndex index, Tagged<Object> key);
//
//  // Store an entire entry at {index}. The arity of this function must be
//  // kEntrySize + 1.
//  void Set(InternalIndex index, Tagged<Object>...);
//
//  // Copy an entry in this table at {from_index} into the entry in {to} at
//  // {to_index}, exclusive of the key.
//  void CopyEntryExcludingKeyInto(PtrComprCageBase, InternalIndex from_index,
//                                 Derived* to, InternalIndex to_index);
//
template <typename Derived>
class OffHeapHashTableBase {
 public:
  static constexpr Tagged<Smi> empty_element() { return Smi::FromInt(0); }
  static constexpr Tagged<Smi> deleted_element() { return Smi::FromInt(1); }

  static bool IsKey(Tagged<Object> k) {
    return k != empty_element() && k != deleted_element();
  }

  int capacity() const { return capacity_; }
  int number_of_elements() const { return number_of_elements_; }
  int number_of_deleted_elements() const { return number_of_deleted_elements_; }

  OffHeapObjectSlot slot(InternalIndex index, int offset = 0) const {
    DCHECK_LT(offset, Derived::kEntrySize);
    return OffHeapObjectSlot(
        &elements_[index.as_uint32() * Derived::kEntrySize + offset]);
  }

  template <typename... Args>
  void AddAt(PtrComprCageBase cage_base, InternalIndex entry, Args&&... args) {
    Derived* derived_this = static_cast<Derived*>(this);

    DCHECK_EQ(derived_this->GetKey(cage_base, entry), empty_element());
    DCHECK_LT(number_of_elements_ + 1, capacity());
    DCHECK(HasSufficientCapacityToAdd(1));

    derived_this->Set(entry, std::forward<Args>(args)...);
    number_of_elements_++;
  }

  template <typename... Args>
  void OverwriteDeletedAt(PtrComprCageBase cage_base, InternalIndex entry,
                          Args&&... args) {
    Derived* derived_this = static_cast<Derived*>(this);

    DCHECK_EQ(derived_this->GetKey(cage_base, entry), deleted_element());
    DCHECK_LT(number_of_elements_ + 1, capacity());
    DCHECK(HasSufficientCapacityToAdd(capacity(), number_of_elements(),
                                      number_of_deleted_elements() - 1, 1));

    derived_this->Set(entry, std::forward<Args>(args)...);
    number_of_elements_++;
    number_of_deleted_elements_--;
  }

  void ElementsRemoved(int count) {
    DCHECK_LE(count, number_of_elements_);
    number_of_elements_ -= count;
    number_of_deleted_elements_ += count;
  }

  size_t GetSizeExcludingHeader() const {
    return GetSizeExcludingHeader(capacity_);
  }

  template <typename IsolateT, typename FindKey>
  inline InternalIndex FindEntry(IsolateT* isolate, FindKey key,
                                 uint32_t hash) const;

  inline InternalIndex FindInsertionEntry(PtrComprCageBase cage_base,
                                          uint32_t hash) const;

  template <typename IsolateT, typename FindKey>
  inline InternalIndex FindEntryOrInsertionEntry(IsolateT* isolate, FindKey key,
                                                 uint32_t hash) const;

  inline bool ShouldResizeToAdd(int number_of_additional_elements,
                                int* new_capacity);

  inline void RehashInto(PtrComprCageBase cage_base, Derived* new_table);

  inline void IterateElements(Root root, RootVisitor* visitor);

 protected:
  explicit OffHeapHashTableBase(int capacity);

  // Returns probe entry.
  static inline InternalIndex FirstProbe(uint32_t hash, uint32_t size) {
    return InternalIndex(hash & (size - 1));
  }

  static inline InternalIndex NextProbe(InternalIndex last, uint32_t number,
                                        uint32_t size) {
    return InternalIndex((last.as_uint32() + number) & (size - 1));
  }

  bool HasSufficientCapacityToAdd(int number_of_additional_elements) const {
    return HasSufficientCapacityToAdd(capacity(), number_of_elements(),
                                      number_of_deleted_elements(),
                                      number_of_additional_elements);
  }
  static inline bool HasSufficientCapacityToAdd(
      int capacity, int number_of_elements, int number_of_deleted_elements,
      int number_of_additional_elements);
  static inline int ComputeCapacity(int at_least_space_for);
  static inline int ComputeCapacityWithShrink(int current_capacity,
                                              int at_least_space_for);

  static inline size_t GetSizeExcludingHeader(int capacity) {
    // Subtract sizeof(Tagged_t) from the result, as the member elements_
    // already supplies the storage for the first element.
    return (capacity * sizeof(Tagged_t) * Derived::kEntrySize) -
           sizeof(Tagged_t);
  }

  // Returns memory to hold a Derived, which may be inline inside Container. The
  // offset of the elements_ field relative to Container must be passed for
  // static layout checks.
  template <typename Container, size_t OffsetOfElementsInContainer>
  static inline void* Allocate(int capacity);

  static inline void Free(void* container);

  int number_of_elements_;
  int number_of_deleted_elements_;
  const int capacity_;
  Tagged_t elements_[1];
};

}  // namespace internal
}  // namespace v8

#endif  // V8_OBJECTS_OFF_HEAP_HASH_TABLE_H_
