// Copyright (c) 2013, 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/heap/weak_table.h"

#include "platform/assert.h"
#include "vm/isolate.h"
#include "vm/raw_object.h"

namespace dart {

intptr_t WeakTable::SizeFor(intptr_t count, intptr_t size) {
  intptr_t result = size;
  if (count <= (size / 4)) {
    // Reduce the capacity.
    result = size / 2;
  } else {
    // Increase the capacity.
    result = size * 2;
    if (result < size) {
      FATAL(
          "Reached impossible state of having more weak table entries"
          " than memory available for heap objects.");
    }
  }
  if (result < kMinSize) {
    result = kMinSize;
  }
  return result;
}

void WeakTable::SetValueExclusive(ObjectPtr key, intptr_t val) {
  const intptr_t mask = size() - 1;
  intptr_t idx = Hash(key) & mask;
  intptr_t empty_idx = -1;
  ObjectPtr obj = ObjectAtExclusive(idx);

  while (obj != static_cast<ObjectPtr>(kNoEntry)) {
    if (obj == key) {
      SetValueAt(idx, val);
      return;
    } else if ((empty_idx < 0) &&
               (static_cast<intptr_t>(obj) == kDeletedEntry)) {
      empty_idx = idx;  // Insert at this location if not found.
    }
    idx = (idx + 1) & mask;
    obj = ObjectAtExclusive(idx);
  }

  if (val == 0) {
    // Do not enter an invalid value. Associating 0 with a key deletes it from
    // this weak table above in SetValueAt. If the key was not present in the
    // weak table we are done.
    return;
  }

  if (empty_idx >= 0) {
    // We will be reusing a slot below.
    set_used(used() - 1);
    idx = empty_idx;
  }

  ASSERT(!IsValidEntryAtExclusive(idx));
  // Set the key and value.
  SetObjectAt(idx, key);
  SetValueAt(idx, val);
  // Update the counts.
  set_used(used() + 1);
  set_count(count() + 1);

  // Rehash if needed to ensure that there are empty slots available.
  if (used_ >= limit()) {
    Rehash();
  }
}

bool WeakTable::MarkValueExclusive(ObjectPtr key, intptr_t val) {
  const intptr_t mask = size() - 1;
  intptr_t idx = Hash(key) & mask;
  intptr_t empty_idx = -1;
  ObjectPtr obj = ObjectAtExclusive(idx);

  while (obj != static_cast<ObjectPtr>(kNoEntry)) {
    if (obj == key) {
      return false;
    } else if ((empty_idx < 0) &&
               (static_cast<intptr_t>(obj) == kDeletedEntry)) {
      empty_idx = idx;  // Insert at this location if not found.
    }
    idx = (idx + 1) & mask;
    obj = ObjectAtExclusive(idx);
  }

  ASSERT(val != 0);

  if (empty_idx >= 0) {
    // We will be reusing a slot below.
    set_used(used() - 1);
    idx = empty_idx;
  }

  ASSERT(!IsValidEntryAtExclusive(idx));
  // Set the key and value.
  SetObjectAt(idx, key);
  SetValueAt(idx, val);
  // Update the counts.
  set_used(used() + 1);
  set_count(count() + 1);

  // Rehash if needed to ensure that there are empty slots available.
  if (used_ >= limit()) {
    Rehash();
  }
  return true;
}

void WeakTable::Reset() {
  intptr_t* old_data = data_;
  used_ = 0;
  count_ = 0;
  size_ = kMinSize;
  free(old_data);
  data_ = reinterpret_cast<intptr_t*>(malloc(size_ * kEntrySize * kWordSize));
  for (intptr_t i = 0; i < size_; i++) {
    data_[ObjectIndex(i)] = kNoEntry;
    data_[ValueIndex(i)] = kNoValue;
  }
}

void WeakTable::Forward(ObjectPointerVisitor* visitor) {
  if (used_ == 0) return;

  for (intptr_t i = 0; i < size_; i++) {
    if (IsValidEntryAtExclusive(i)) {
      visitor->VisitPointer(ObjectPointerAt(i));
    }
  }

  Rehash();
}

#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
void WeakTable::ReportSurvivingAllocations(
    Dart_HeapSamplingReportCallback callback,
    void* context) {
  MutexLocker ml(&mutex_);
  for (intptr_t i = 0; i < size_; i++) {
    if (IsValidEntryAtExclusive(i)) {
      void* data = reinterpret_cast<void*>(data_[ValueIndex(i)]);
      callback(context, data);
    }
  }
}

void WeakTable::CleanupValues(Dart_HeapSamplingDeleteCallback cleanup) {
  for (intptr_t i = 0; i < size_; i++) {
    if (IsValidEntryAtExclusive(i)) {
      cleanup(reinterpret_cast<void*>(data_[ValueIndex(i)]));
    }
  }
}
#endif

void WeakTable::Rehash() {
  intptr_t old_size = size();
  intptr_t* old_data = data_;

  intptr_t new_size = SizeFor(count(), size());
  ASSERT(Utils::IsPowerOfTwo(new_size));
  intptr_t* new_data =
      reinterpret_cast<intptr_t*>(malloc(new_size * kEntrySize * kWordSize));
  for (intptr_t i = 0; i < new_size; i++) {
    new_data[ObjectIndex(i)] = kNoEntry;
    new_data[ValueIndex(i)] = kNoValue;
  }

  intptr_t mask = new_size - 1;
  set_used(0);
  for (intptr_t i = 0; i < old_size; i++) {
    if (IsValidEntryAtExclusive(i)) {
      // Find the new hash location for this entry.
      ObjectPtr key = ObjectAtExclusive(i);
      intptr_t idx = Hash(key) & mask;
      ObjectPtr obj = static_cast<ObjectPtr>(new_data[ObjectIndex(idx)]);
      while (obj != static_cast<ObjectPtr>(kNoEntry)) {
        ASSERT(obj != key);  // Duplicate entry is not expected.
        idx = (idx + 1) & mask;
        obj = static_cast<ObjectPtr>(new_data[ObjectIndex(idx)]);
      }

      new_data[ObjectIndex(idx)] = static_cast<intptr_t>(key);
      new_data[ValueIndex(idx)] = ValueAtExclusive(i);
      set_used(used() + 1);
    }
  }
  // We should only have used valid entries.
  ASSERT(used() == count());

  // Switch to using the newly allocated backing store.
  size_ = new_size;
  data_ = new_data;
  free(old_data);
}

}  // namespace dart
