// Copyright (c) 2014, 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.

#ifndef RUNTIME_VM_HASH_TABLE_H_
#define RUNTIME_VM_HASH_TABLE_H_

#include "platform/assert.h"
#include "vm/object.h"

namespace dart {

// Storage traits control how memory is allocated for HashTable.
// Default ArrayStorageTraits use an Array to store HashTable contents.
struct ArrayStorageTraits {
  using ArrayHandle = Array;
  using ArrayPtr = dart::ArrayPtr;
  static constexpr intptr_t ArrayCid = kArrayCid;

  static ArrayHandle& PtrToHandle(ArrayPtr ptr) { return Array::Handle(ptr); }

  static void SetHandle(ArrayHandle& dst, const ArrayHandle& src) {  // NOLINT
    dst = src.ptr();
  }

  static void ClearHandle(ArrayHandle& handle) {  // NOLINT
    handle = Array::null();
  }

  static ArrayPtr New(Zone* zone, intptr_t length, Heap::Space space) {
    return Array::New(length, space);
  }

  static ObjectPtr At(ArrayHandle* array, intptr_t index) {
    return array->At(index);
  }

  static void SetAt(ArrayHandle* array, intptr_t index, const Object& value) {
    array->SetAt(index, value);
  }
};

struct WeakArrayStorageTraits {
  using ArrayHandle = WeakArray;
  using ArrayPtr = dart::WeakArrayPtr;
  static constexpr intptr_t ArrayCid = kWeakArrayCid;

  static ArrayHandle& PtrToHandle(ArrayPtr ptr) {
    return WeakArray::Handle(ptr);
  }

  static void SetHandle(ArrayHandle& dst, const ArrayHandle& src) {  // NOLINT
    dst = src.ptr();
  }

  static void ClearHandle(ArrayHandle& handle) {  // NOLINT
    handle = WeakArray::null();
  }

  static ArrayPtr New(Zone* zone, intptr_t length, Heap::Space space) {
    return WeakArray::New(length, space);
  }

  static ObjectPtr At(ArrayHandle* array, intptr_t index) {
    return array->At(index);
  }

  static void SetAt(ArrayHandle* array, intptr_t index, const Object& value) {
    array->SetAt(index, value);
  }
};

struct AcqRelStorageTraits : ArrayStorageTraits {
  static ObjectPtr At(ArrayHandle* array, intptr_t index) {
    return array->AtAcquire(index);
  }

  static void SetAt(ArrayHandle* array, intptr_t index, const Object& value) {
    array->SetAtRelease(index, value);
  }
};

struct WeakAcqRelStorageTraits : WeakArrayStorageTraits {
  static ObjectPtr At(ArrayHandle* array, intptr_t index) {
    return array->AtAcquire(index);
  }

  static void SetAt(ArrayHandle* array, intptr_t index, const Object& value) {
    array->SetAtRelease(index, value);
  }
};

class HashTableBase : public ValueObject {
 public:
  static const Object& UnusedMarker() { return Object::sentinel(); }
  static const Object& DeletedMarker() { return Object::null_object(); }
};

// OVERVIEW:
//
// Hash maps and hash sets all use RawArray as backing storage. At the lowest
// level is a generic open-addressing table that supports deletion.
//  - HashTable
// The next layer provides ordering and iteration functionality:
//  - UnorderedHashTable
//  - LinkedListHashTable (TODO(koda): Implement.)
// The utility class HashTables handles growth and conversion.
// The next layer fixes the payload size and provides a natural interface:
//  - HashMap
//  - HashSet
// Combining either of these with an iteration strategy, we get the templates
// intended for use outside this file:
//  - UnorderedHashMap
//  - LinkedListHashMap
//  - UnorderedHashSet
//  - LinkedListHashSet
// Each of these can be finally specialized with KeyTraits to support any set of
// lookup key types (e.g., look up a char* in a set of String objects), and
// any equality and hash code computation.
//
// The classes all wrap an Array handle, and methods like HashSet::Insert can
// trigger growth into a new RawArray, updating the handle. Debug mode asserts
// that 'Release' was called once to access the final array before destruction.
// NOTE: The handle returned by 'Release' is cleared by ~HashTable.
//
// Example use:
//  typedef UnorderedHashMap<FooTraits> FooMap;
//  ...
//  FooMap cache(get_foo_cache());
//  cache.UpdateOrInsert(name0, obj0);
//  cache.UpdateOrInsert(name1, obj1);
//  ...
//  set_foo_cache(cache.Release());
//
// If you *know* that no mutating operations were called, you can optimize:
//  ...
//  obj ^= cache.GetOrNull(name);
//  ASSERT(cache.Release().ptr() == get_foo_cache());
//
// TODO(koda): When exposing these to Dart code, document and assert that
// KeyTraits methods must not run Dart code (since the C++ code doesn't check
// for concurrent modification).

// Open-addressing hash table template using a RawArray as backing storage.
//
// The elements of the array are partitioned into entries:
//  [ header | metadata | entry0 | entry1 | ... | entryN ]
// Each entry contains a key, followed by zero or more payload components,
// and has 3 possible states: unused, occupied, or deleted.
// The header tracks the number of entries in each state.
// Any object except the backing storage array and Object::sentinel()
// may be stored as a key. Any object may be stored in a payload.
//
// Parameters
//  KeyTraits: defines static methods
//    bool IsMatch(const Key& key, const Object& obj) and
//    uword Hash(const Key& key) for any number of desired lookup key types.
//  kPayloadSize: number of components of the payload in each entry.
//  kMetaDataSize: number of elements reserved (e.g., for iteration order data).
template <typename KeyTraits,
          intptr_t kPayloadSize,
          intptr_t kMetaDataSize,
          typename StorageTraits = ArrayStorageTraits>
class HashTable : public HashTableBase {
 public:
  typedef KeyTraits Traits;
  typedef StorageTraits Storage;

  // Uses the passed in handles for all handle operations.
  // 'Release' must be called at the end to obtain the final table
  // after potential growth/shrinkage.
  HashTable(Object* key, Smi* index, typename StorageTraits::ArrayHandle* data)
      : key_handle_(key),
        smi_handle_(index),
        data_(data),
        released_data_(nullptr) {}
  // Uses 'zone' for handle allocation. 'Release' must be called at the end
  // to obtain the final table after potential growth/shrinkage.
  HashTable(Zone* zone, typename StorageTraits::ArrayPtr data)
      : key_handle_(&Object::Handle(zone)),
        smi_handle_(&Smi::Handle(zone)),
        data_(&StorageTraits::PtrToHandle(data)),
        released_data_(nullptr) {}

  // Returns the final table. The handle is cleared when this HashTable is
  // destroyed.
  typename StorageTraits::ArrayHandle& Release() {
    ASSERT(data_ != nullptr);
    ASSERT(released_data_ == nullptr);
    // Ensure that no methods are called after 'Release'.
    released_data_ = data_;
    data_ = nullptr;
    return *released_data_;
  }

  ~HashTable() {
    // In DEBUG mode, calling 'Release' is mandatory.
    ASSERT(data_ == nullptr);
    if (released_data_ != nullptr) {
      StorageTraits::ClearHandle(*released_data_);
    }
  }

  // Returns a backing storage size such that 'num_occupied' distinct keys can
  // be inserted into the table.
  static intptr_t ArrayLengthForNumOccupied(intptr_t num_occupied) {
    // Because we use quadratic (actually triangle number) probing it is
    // important that the size is a power of two (otherwise we could fail to
    // find an empty slot).  This is described in Knuth's The Art of Computer
    // Programming Volume 2, Chapter 6.4, exercise 20 (solution in the
    // appendix, 2nd edition).
    intptr_t num_entries = Utils::RoundUpToPowerOfTwo(num_occupied + 1);
    return kFirstKeyIndex + (kEntrySize * num_entries);
  }

  // Initializes an empty table.
  void Initialize() const {
    ASSERT(data_->Length() >= ArrayLengthForNumOccupied(0));
    *smi_handle_ = Smi::New(0);
    StorageTraits::SetAt(data_, kOccupiedEntriesIndex, *smi_handle_);
    StorageTraits::SetAt(data_, kDeletedEntriesIndex, *smi_handle_);

#if !defined(PRODUCT)
    StorageTraits::SetAt(data_, kNumGrowsIndex, *smi_handle_);
    StorageTraits::SetAt(data_, kNumLT5LookupsIndex, *smi_handle_);
    StorageTraits::SetAt(data_, kNumLT25LookupsIndex, *smi_handle_);
    StorageTraits::SetAt(data_, kNumGT25LookupsIndex, *smi_handle_);
    StorageTraits::SetAt(data_, kNumProbesIndex, *smi_handle_);
#endif  // !defined(PRODUCT)

    for (intptr_t i = kHeaderSize; i < data_->Length(); ++i) {
      StorageTraits::SetAt(data_, i, UnusedMarker());
    }
  }

  // Returns whether 'key' matches any key in the table.
  template <typename Key>
  bool ContainsKey(const Key& key) const {
    return FindKey(key) != -1;
  }

  // Returns the entry that matches 'key', or -1 if none exists.
  template <typename Key>
  intptr_t FindKey(const Key& key) const {
    const intptr_t num_entries = NumEntries();
    // TODO(koda): Add salt.
    NOT_IN_PRODUCT(intptr_t collisions = 0;)
    uword hash = KeyTraits::Hash(key);
    ASSERT(Utils::IsPowerOfTwo(num_entries));
    intptr_t probe = hash & (num_entries - 1);
    int probe_distance = 1;
    while (true) {
      if (IsUnused(probe)) {
        NOT_IN_PRODUCT(UpdateCollisions(collisions);)
        return -1;
      } else if (!IsDeleted(probe)) {
        *key_handle_ = GetKey(probe);
        if (KeyTraits::IsMatch(key, *key_handle_)) {
          NOT_IN_PRODUCT(UpdateCollisions(collisions);)
          return probe;
        }
        NOT_IN_PRODUCT(collisions += 1;)
      }
      // Advance probe.  See ArrayLengthForNumOccupied comment for
      // explanation of how we know this hits all slots.
      probe = (probe + probe_distance) & (num_entries - 1);
      probe_distance++;
    }
    UNREACHABLE();
    return -1;
  }

  // Sets *entry to either:
  // - an occupied entry matching 'key', and returns true, or
  // - an unused/deleted entry where a matching key may be inserted,
  //   and returns false.
  template <typename Key>
  bool FindKeyOrDeletedOrUnused(const Key& key, intptr_t* entry) const {
    const intptr_t num_entries = NumEntries();
    ASSERT(entry != nullptr);
    NOT_IN_PRODUCT(intptr_t collisions = 0;)
    uword hash = KeyTraits::Hash(key);
    ASSERT(Utils::IsPowerOfTwo(num_entries));
    intptr_t probe = hash & (num_entries - 1);
    int probe_distance = 1;
    intptr_t deleted = -1;
    while (true) {
      if (IsUnused(probe)) {
        *entry = (deleted != -1) ? deleted : probe;
        NOT_IN_PRODUCT(UpdateCollisions(collisions);)
        return false;
      } else if (IsDeleted(probe)) {
        if (deleted == -1) {
          deleted = probe;
        }
      } else {
        *key_handle_ = GetKey(probe);
        if (KeyTraits::IsMatch(key, *key_handle_)) {
          *entry = probe;
          NOT_IN_PRODUCT(UpdateCollisions(collisions);)
          return true;
        }
        NOT_IN_PRODUCT(collisions += 1;)
      }
      // Advance probe.  See ArrayLengthForNumOccupied comment for
      // explanation of how we know this hits all slots.
      probe = (probe + probe_distance) & (num_entries - 1);
      probe_distance++;
    }
    UNREACHABLE();
    return false;
  }

  // Sets the key of a previously unoccupied entry. This must not be the last
  // unoccupied entry.
  void InsertKey(intptr_t entry, const Object& key) const {
    ASSERT(key.ptr() != UnusedMarker().ptr());
    ASSERT(key.ptr() != DeletedMarker().ptr());
    ASSERT(!IsOccupied(entry));
    AdjustSmiValueAt(kOccupiedEntriesIndex, 1);
    if (IsDeleted(entry)) {
      AdjustSmiValueAt(kDeletedEntriesIndex, -1);
    } else {
      ASSERT(IsUnused(entry));
    }
    InternalSetKey(entry, key);
    ASSERT(IsOccupied(entry));
  }

  bool IsUnused(intptr_t entry) const {
    return InternalGetKey(entry) == UnusedMarker().ptr();
  }
  bool IsOccupied(intptr_t entry) const {
    return !IsUnused(entry) && !IsDeleted(entry);
  }
  bool IsDeleted(intptr_t entry) const {
    return InternalGetKey(entry) == DeletedMarker().ptr();
  }

  ObjectPtr GetKey(intptr_t entry) const {
    ASSERT(IsOccupied(entry));
    return InternalGetKey(entry);
  }
  ObjectPtr GetPayload(intptr_t entry, intptr_t component) const {
    ASSERT(IsOccupied(entry));
    return WeakSerializationReference::Unwrap(
        StorageTraits::At(data_, PayloadIndex(entry, component)));
  }
  void UpdatePayload(intptr_t entry,
                     intptr_t component,
                     const Object& value) const {
    ASSERT(IsOccupied(entry));
    ASSERT(0 <= component && component < kPayloadSize);
    StorageTraits::SetAt(data_, PayloadIndex(entry, component), value);
  }
  // Deletes both the key and payload of the specified entry.
  void DeleteEntry(intptr_t entry) const {
    ASSERT(IsOccupied(entry));
    for (intptr_t i = 0; i < kPayloadSize; ++i) {
      UpdatePayload(entry, i, DeletedMarker());
    }
    InternalSetKey(entry, DeletedMarker());
    AdjustSmiValueAt(kOccupiedEntriesIndex, -1);
    AdjustSmiValueAt(kDeletedEntriesIndex, 1);
  }
  intptr_t NumEntries() const {
    return (data_->Length() - kFirstKeyIndex) / kEntrySize;
  }
  intptr_t NumUnused() const {
    return NumEntries() - NumOccupied() - NumDeleted();
  }
  intptr_t NumOccupied() const { return GetSmiValueAt(kOccupiedEntriesIndex); }
  intptr_t NumDeleted() const { return GetSmiValueAt(kDeletedEntriesIndex); }
  Object& KeyHandle() const { return *key_handle_; }
  Smi& SmiHandle() const { return *smi_handle_; }

#if !defined(PRODUCT)
  intptr_t NumGrows() const { return GetSmiValueAt(kNumGrowsIndex); }
  intptr_t NumLT5Collisions() const {
    return GetSmiValueAt(kNumLT5LookupsIndex);
  }
  intptr_t NumLT25Collisions() const {
    return GetSmiValueAt(kNumLT25LookupsIndex);
  }
  intptr_t NumGT25Collisions() const {
    return GetSmiValueAt(kNumGT25LookupsIndex);
  }
  intptr_t NumProbes() const { return GetSmiValueAt(kNumProbesIndex); }
  void UpdateGrowth() const {
    if (KeyTraits::ReportStats()) {
      AdjustSmiValueAt(kNumGrowsIndex, 1);
    }
  }
  void UpdateCollisions(intptr_t collisions) const {
    if (KeyTraits::ReportStats()) {
      AdjustSmiValueAt(kNumProbesIndex, collisions + 1);
      if (collisions < 5) {
        AdjustSmiValueAt(kNumLT5LookupsIndex, 1);
      } else if (collisions < 25) {
        AdjustSmiValueAt(kNumLT25LookupsIndex, 1);
      } else {
        AdjustSmiValueAt(kNumGT25LookupsIndex, 1);
      }
    }
  }
  void PrintStats() const {
    if (!KeyTraits::ReportStats()) {
      return;
    }
    const intptr_t num5 = NumLT5Collisions();
    const intptr_t num25 = NumLT25Collisions();
    const intptr_t num_more = NumGT25Collisions();
    // clang-format off
    OS::PrintErr("Stats for %s table :\n"
              " Size of table = %" Pd ",Number of Occupied entries = %" Pd "\n"
              " Number of Grows = %" Pd "\n"
              " Number of lookups with < 5 collisions = %" Pd "\n"
              " Number of lookups with < 25 collisions = %" Pd "\n"
              " Number of lookups with > 25 collisions = %" Pd "\n"
              " Average number of probes = %g\n",
              KeyTraits::Name(),
              NumEntries(), NumOccupied(), NumGrows(),
              num5, num25, num_more,
              static_cast<double>(NumProbes()) / (num5 + num25 + num_more));
    // clang-format on
  }
#endif  // !PRODUCT

  void UpdateWeakDeleted() const {
    if (StorageTraits::ArrayCid != kWeakArrayCid) return;

    // As entries are deleted by GC, NumOccupied and NumDeleted become stale.
    // Re-count before growing/rehashing to prevent table growth when the
    // number of live entries is not increasing.
    intptr_t num_occupied = 0;
    intptr_t num_deleted = 0;
    for (intptr_t i = 0, n = NumEntries(); i < n; i++) {
      if (IsDeleted(i)) {
        num_deleted++;
      }
      if (IsOccupied(i)) {
        num_occupied++;
      }
    }
    SetSmiValueAt(kOccupiedEntriesIndex, num_occupied);
    SetSmiValueAt(kDeletedEntriesIndex, num_deleted);
  }

 protected:
  static constexpr intptr_t kOccupiedEntriesIndex = 0;
  static constexpr intptr_t kDeletedEntriesIndex = 1;
#if defined(PRODUCT)
  static constexpr intptr_t kHeaderSize = kDeletedEntriesIndex + 1;
#else
  static constexpr intptr_t kNumGrowsIndex = 2;
  static constexpr intptr_t kNumLT5LookupsIndex = 3;
  static constexpr intptr_t kNumLT25LookupsIndex = 4;
  static constexpr intptr_t kNumGT25LookupsIndex = 5;
  static constexpr intptr_t kNumProbesIndex = 6;
  static constexpr intptr_t kHeaderSize = kNumProbesIndex + 1;
#endif
  static constexpr intptr_t kMetaDataIndex = kHeaderSize;
  static constexpr intptr_t kFirstKeyIndex = kHeaderSize + kMetaDataSize;
  static constexpr intptr_t kEntrySize = 1 + kPayloadSize;

  intptr_t KeyIndex(intptr_t entry) const {
    ASSERT(0 <= entry && entry < NumEntries());
    return kFirstKeyIndex + (kEntrySize * entry);
  }

  intptr_t PayloadIndex(intptr_t entry, intptr_t component) const {
    ASSERT(0 <= component && component < kPayloadSize);
    return KeyIndex(entry) + 1 + component;
  }

  ObjectPtr InternalGetKey(intptr_t entry) const {
    return WeakSerializationReference::Unwrap(
        StorageTraits::At(data_, KeyIndex(entry)));
  }

  void InternalSetKey(intptr_t entry, const Object& key) const {
    StorageTraits::SetAt(data_, KeyIndex(entry), key);
  }

  intptr_t GetSmiValueAt(intptr_t index) const {
    ASSERT(!data_->IsNull());
    if (StorageTraits::At(data_, index)->IsHeapObject()) {
      Object::Handle(StorageTraits::At(data_, index)).Print();
    }
    ASSERT(!StorageTraits::At(data_, index)->IsHeapObject());
    return Smi::Value(Smi::RawCast(StorageTraits::At(data_, index)));
  }

  void SetSmiValueAt(intptr_t index, intptr_t value) const {
    *smi_handle_ = Smi::New(value);
    StorageTraits::SetAt(data_, index, *smi_handle_);
  }

  void AdjustSmiValueAt(intptr_t index, intptr_t delta) const {
    SetSmiValueAt(index, (GetSmiValueAt(index) + delta));
  }

  Object* key_handle_;
  Smi* smi_handle_;
  // Exactly one of these is non-null, depending on whether Release was called.
  typename StorageTraits::ArrayHandle* data_;
  typename StorageTraits::ArrayHandle* released_data_;

  friend class HashTables;
  template <typename Table, bool kAllCanonicalObjectsAreIncludedIntoSet>
  friend class CanonicalSetDeserializationCluster;
  template <typename Table,
            typename HandleType,
            typename PointerType,
            bool kAllCanonicalObjectsAreIncludedIntoSet>
  friend class CanonicalSetSerializationCluster;
};

// Table with unspecified iteration order. No payload overhead or metadata.
template <typename KeyTraits,
          intptr_t kUserPayloadSize,
          typename StorageTraits = ArrayStorageTraits>
class UnorderedHashTable
    : public HashTable<KeyTraits, kUserPayloadSize, 0, StorageTraits> {
 public:
  typedef HashTable<KeyTraits, kUserPayloadSize, 0, StorageTraits> BaseTable;
  typedef typename StorageTraits::ArrayPtr ArrayPtr;
  typedef typename StorageTraits::ArrayHandle ArrayHandle;
  static constexpr intptr_t kPayloadSize = kUserPayloadSize;
  explicit UnorderedHashTable(ArrayPtr data)
      : BaseTable(Thread::Current()->zone(), data) {}
  UnorderedHashTable(Zone* zone, ArrayPtr data) : BaseTable(zone, data) {}
  UnorderedHashTable(Object* key, Smi* value, ArrayHandle* data)
      : BaseTable(key, value, data) {}
  // Note: Does not check for concurrent modification.
  class Iterator {
   public:
    explicit Iterator(const UnorderedHashTable* table)
        : table_(table), entry_(-1) {}
    bool MoveNext() {
      while (entry_ < (table_->NumEntries() - 1)) {
        ++entry_;
        if (table_->IsOccupied(entry_)) {
          return true;
        }
      }
      return false;
    }
    intptr_t Current() { return entry_; }

   private:
    const UnorderedHashTable* table_;
    intptr_t entry_;
  };

  // No extra book-keeping needed for Initialize, InsertKey, DeleteEntry.
};

class HashTables : public AllStatic {
 public:
  // Allocates and initializes a table.
  template <typename Table>
  static typename Table::Storage::ArrayPtr New(intptr_t initial_capacity,
                                               Heap::Space space = Heap::kNew) {
    auto zone = Thread::Current()->zone();
    Table table(
        zone,
        Table::Storage::New(
            zone, Table::ArrayLengthForNumOccupied(initial_capacity), space));
    table.Initialize();
    return table.Release().ptr();
  }

  template <typename Table>
  static typename Table::Storage::ArrayPtr New(
      const typename Table::Storage::ArrayHandle& array) {
    Table table(Thread::Current()->zone(), array.ptr());
    table.Initialize();
    return table.Release().ptr();
  }

  // Clears 'to' and inserts all elements from 'from', in iteration order.
  // The tables must have the same user payload size.
  template <typename From, typename To>
  static void Copy(const From& from, const To& to) {
    COMPILE_ASSERT(From::kPayloadSize == To::kPayloadSize);
    to.Initialize();
    ASSERT(from.NumOccupied() < to.NumEntries());
    typename From::Iterator it(&from);
    Object& obj = Object::Handle();
    while (it.MoveNext()) {
      intptr_t from_entry = it.Current();
      obj = from.GetKey(from_entry);
      intptr_t to_entry = -1;
      const Object& key = obj;
      bool present = to.FindKeyOrDeletedOrUnused(key, &to_entry);
      ASSERT(!present);
      to.InsertKey(to_entry, obj);
      for (intptr_t i = 0; i < From::kPayloadSize; ++i) {
        obj = from.GetPayload(from_entry, i);
        to.UpdatePayload(to_entry, i, obj);
      }
    }
  }

  static constexpr double kMaxLoadFactor = 0.71;

  template <typename Table>
  static void EnsureLoadFactor(double high, const Table& table) {
    // We count deleted elements because they take up space just
    // like occupied slots in order to cause a rehashing.
    const double current = (1 + table.NumOccupied() + table.NumDeleted()) /
                           static_cast<double>(table.NumEntries());
    const bool too_many_deleted = table.NumOccupied() <= table.NumDeleted();
    if (current < high && !too_many_deleted) {
      return;
    }

    table.UpdateWeakDeleted();

    // Normally we double the size here, but if less than half are occupied
    // then it won't grow (this would imply that there were quite a lot of
    // deleted slots).  We don't want to constantly rehash if we are adding
    // and deleting entries at just under the load factor limit, so we may
    // double the size even though the number of occupied slots would not
    // necessarily justify it.  For example if the max load factor is 71% and
    // the table is 70% full we will double the size to avoid a rehash every
    // time 1% has been added and deleted.
    const intptr_t new_capacity = table.NumOccupied() * 2 + 1;
    ASSERT(table.NumOccupied() == 0 ||
           ((1.0 + table.NumOccupied()) /
            Utils::RoundUpToPowerOfTwo(new_capacity)) <= high);
    Table new_table(New<Table>(new_capacity,  // Is rounded up to power of 2.
                               table.data_->IsOld() ? Heap::kOld : Heap::kNew));
    Copy(table, new_table);
    Table::Storage::SetHandle(*table.data_, new_table.Release());
    NOT_IN_PRODUCT(table.UpdateGrowth(); table.PrintStats();)
  }

  // Serializes a table by concatenating its entries as an array.
  template <typename Table>
  static ArrayPtr ToArray(const Table& table, bool include_payload) {
    const intptr_t entry_size = include_payload ? (1 + Table::kPayloadSize) : 1;
    Array& result = Array::Handle(Array::New(table.NumOccupied() * entry_size));
    typename Table::Iterator it(&table);
    Object& obj = Object::Handle();
    intptr_t result_index = 0;
    while (it.MoveNext()) {
      intptr_t entry = it.Current();
      obj = table.GetKey(entry);
      result.SetAt(result_index++, obj);
      if (include_payload) {
        for (intptr_t i = 0; i < Table::kPayloadSize; ++i) {
          obj = table.GetPayload(entry, i);
          result.SetAt(result_index++, obj);
        }
      }
    }
    return result.ptr();
  }

#if defined(DART_PRECOMPILER)
  // Replace elements of this set with WeakSerializationReferences.
  static void Weaken(const Array& table) {
    if (!table.IsNull()) {
      Object& element = Object::Handle();
      for (intptr_t i = 0; i < table.Length(); i++) {
        element = table.At(i);
        if (!element.IsSmi()) {
          element = WeakSerializationReference::New(
              element, HashTableBase::DeletedMarker());
          table.SetAt(i, element);
        }
      }
    }
  }
#endif
};

template <typename BaseIterTable>
class HashMap : public BaseIterTable {
 public:
  explicit HashMap(ArrayPtr data)
      : BaseIterTable(Thread::Current()->zone(), data) {}
  HashMap(Zone* zone, ArrayPtr data) : BaseIterTable(zone, data) {}
  HashMap(Object* key, Smi* value, Array* data)
      : BaseIterTable(key, value, data) {}
  template <typename Key>
  ObjectPtr GetOrNull(const Key& key, bool* present = nullptr) const {
    intptr_t entry = BaseIterTable::FindKey(key);
    if (present != nullptr) {
      *present = (entry != -1);
    }
    return (entry == -1) ? Object::null() : BaseIterTable::GetPayload(entry, 0);
  }
  template <typename Key>
  ObjectPtr GetOrDie(const Key& key) const {
    intptr_t entry = BaseIterTable::FindKey(key);
    if (entry == -1) UNREACHABLE();
    return BaseIterTable::GetPayload(entry, 0);
  }
  bool UpdateOrInsert(const Object& key, const Object& value) const {
    EnsureCapacity();
    intptr_t entry = -1;
    bool present = BaseIterTable::FindKeyOrDeletedOrUnused(key, &entry);
    if (!present) {
      BaseIterTable::InsertKey(entry, key);
    }
    BaseIterTable::UpdatePayload(entry, 0, value);
    return present;
  }
  // Update the value of an existing key. Note that 'key' need not be an Object.
  template <typename Key>
  void UpdateValue(const Key& key, const Object& value) const {
    intptr_t entry = BaseIterTable::FindKey(key);
    ASSERT(entry != -1);
    BaseIterTable::UpdatePayload(entry, 0, value);
  }
  // If 'key' is not present, maps it to 'value_if_absent'. Returns the final
  // value in the map.
  ObjectPtr InsertOrGetValue(const Object& key,
                             const Object& value_if_absent) const {
    EnsureCapacity();
    intptr_t entry = -1;
    if (!BaseIterTable::FindKeyOrDeletedOrUnused(key, &entry)) {
      BaseIterTable::InsertKey(entry, key);
      BaseIterTable::UpdatePayload(entry, 0, value_if_absent);
      return value_if_absent.ptr();
    } else {
      return BaseIterTable::GetPayload(entry, 0);
    }
  }
  // Like InsertOrGetValue, but calls NewKey to allocate a key object if needed.
  template <typename Key>
  ObjectPtr InsertNewOrGetValue(const Key& key,
                                const Object& value_if_absent) const {
    EnsureCapacity();
    intptr_t entry = -1;
    if (!BaseIterTable::FindKeyOrDeletedOrUnused(key, &entry)) {
      BaseIterTable::KeyHandle() =
          BaseIterTable::BaseTable::Traits::NewKey(key);
      BaseIterTable::InsertKey(entry, BaseIterTable::KeyHandle());
      BaseIterTable::UpdatePayload(entry, 0, value_if_absent);
      return value_if_absent.ptr();
    } else {
      return BaseIterTable::GetPayload(entry, 0);
    }
  }

  template <typename Key>
  bool Remove(const Key& key) const {
    intptr_t entry = BaseIterTable::FindKey(key);
    if (entry == -1) {
      return false;
    } else {
      BaseIterTable::DeleteEntry(entry);
      return true;
    }
  }

  void Clear() const { BaseIterTable::Initialize(); }

 protected:
  void EnsureCapacity() const {
    HashTables::EnsureLoadFactor(HashTables::kMaxLoadFactor, *this);
  }
};

template <typename KeyTraits>
class UnorderedHashMap : public HashMap<UnorderedHashTable<KeyTraits, 1> > {
 public:
  typedef HashMap<UnorderedHashTable<KeyTraits, 1> > BaseMap;
  explicit UnorderedHashMap(ArrayPtr data)
      : BaseMap(Thread::Current()->zone(), data) {}
  UnorderedHashMap(Zone* zone, ArrayPtr data) : BaseMap(zone, data) {}
  UnorderedHashMap(Object* key, Smi* value, Array* data)
      : BaseMap(key, value, data) {}
};

template <typename BaseIterTable, typename StorageTraits>
class HashSet : public BaseIterTable {
 public:
  typedef typename StorageTraits::ArrayPtr ArrayPtr;
  typedef typename StorageTraits::ArrayHandle ArrayHandle;
  explicit HashSet(ArrayPtr data)
      : BaseIterTable(Thread::Current()->zone(), data) {}
  HashSet(Zone* zone, ArrayPtr data) : BaseIterTable(zone, data) {}
  HashSet(Object* key, Smi* value, ArrayHandle* data)
      : BaseIterTable(key, value, data) {}
  bool Insert(const Object& key) {
    EnsureCapacity();
    intptr_t entry = -1;
    bool present = BaseIterTable::FindKeyOrDeletedOrUnused(key, &entry);
    if (!present) {
      BaseIterTable::InsertKey(entry, key);
    }
    return present;
  }

  // If 'key' is not present, insert and return it. Else, return the existing
  // key in the set (useful for canonicalization).
  ObjectPtr InsertOrGet(const Object& key) const {
    EnsureCapacity();
    intptr_t entry = -1;
    if (!BaseIterTable::FindKeyOrDeletedOrUnused(key, &entry)) {
      BaseIterTable::InsertKey(entry, key);
      return key.ptr();
    } else {
      return BaseIterTable::GetKey(entry);
    }
  }

  // Like InsertOrGet, but calls NewKey to allocate a key object if needed.
  template <typename Key>
  ObjectPtr InsertNewOrGet(const Key& key) const {
    EnsureCapacity();
    intptr_t entry = -1;
    if (!BaseIterTable::FindKeyOrDeletedOrUnused(key, &entry)) {
      BaseIterTable::KeyHandle() =
          BaseIterTable::BaseTable::Traits::NewKey(key);
      BaseIterTable::InsertKey(entry, BaseIterTable::KeyHandle());
      return BaseIterTable::KeyHandle().ptr();
    } else {
      return BaseIterTable::GetKey(entry);
    }
  }

  template <typename Key>
  ObjectPtr GetOrNull(const Key& key, bool* present = nullptr) const {
    intptr_t entry = BaseIterTable::FindKey(key);
    if (present != nullptr) {
      *present = (entry != -1);
    }
    return (entry == -1) ? Object::null() : BaseIterTable::GetKey(entry);
  }

  template <typename Key>
  bool Remove(const Key& key) const {
    intptr_t entry = BaseIterTable::FindKey(key);
    if (entry == -1) {
      return false;
    } else {
      BaseIterTable::DeleteEntry(entry);
      return true;
    }
  }

  void Clear() const { BaseIterTable::Initialize(); }

 protected:
  void EnsureCapacity() const {
    HashTables::EnsureLoadFactor(HashTables::kMaxLoadFactor, *this);
  }
};

template <typename KeyTraits, typename TableStorageTraits = ArrayStorageTraits>
class UnorderedHashSet
    : public HashSet<UnorderedHashTable<KeyTraits, 0, TableStorageTraits>,
                     TableStorageTraits> {
  using UnderlyingTable = UnorderedHashTable<KeyTraits, 0, TableStorageTraits>;

 public:
  typedef HashSet<UnderlyingTable, TableStorageTraits> BaseSet;
  typedef typename TableStorageTraits::ArrayPtr ArrayPtr;
  typedef typename TableStorageTraits::ArrayHandle ArrayHandle;
  explicit UnorderedHashSet(ArrayPtr data)
      : BaseSet(Thread::Current()->zone(), data) {
    ASSERT(data != Object::null());
  }
  UnorderedHashSet(Zone* zone, ArrayPtr data) : BaseSet(zone, data) {}
  UnorderedHashSet(Object* key, Smi* value, ArrayHandle* data)
      : BaseSet(key, value, data) {}

  void Dump() const {
    Object& entry = Object::Handle();
    for (intptr_t i = 0; i < this->data_->Length(); i++) {
      entry = WeakSerializationReference::Unwrap(
          TableStorageTraits::At(this->data_, i));
      if (entry.ptr() == BaseSet::UnusedMarker().ptr() ||
          entry.ptr() == BaseSet::DeletedMarker().ptr() || entry.IsSmi()) {
        // empty, deleted, num_used/num_deleted
        OS::PrintErr("%" Pd ": %s\n", i, entry.ToCString());
      } else {
        intptr_t hash = KeyTraits::Hash(entry);
        OS::PrintErr("%" Pd ": %" Pd ", %s\n", i, hash, entry.ToCString());
      }
    }
  }
};

typedef UnorderedHashMap<SmiTraits> IntHashMap;

}  // namespace dart

#endif  // RUNTIME_VM_HASH_TABLE_H_
