/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef gc_ArenaList_inl_h
#define gc_ArenaList_inl_h

#include "gc/ArenaList.h"

#include "gc/Heap.h"
#include "gc/Zone.h"

bool js::gc::ArenaList::hasNonFullArenas() const {
  // Non-full arenas are kept at the start so we can check the first one.
  return !isEmpty() && !getFirst()->isFull();
}

js::gc::Arena* js::gc::ArenaList::takeInitialNonFullArena() {
  Arena* arena = getFirst();
  if (!arena || arena->isFull()) {
    return nullptr;
  }

  moveFrontToBack();

  return arena;
}

js::gc::SortedArenaList::SortedArenaList(js::gc::AllocKind allocKind)
    : thingsPerArena_(Arena::thingsPerArena(allocKind)) {
#ifdef DEBUG
  MOZ_ASSERT(thingsPerArena_ <= MaxThingsPerArena);
  MOZ_ASSERT(emptyIndex() < BucketCount);
  allocKind_ = allocKind;
#endif
}

size_t js::gc::SortedArenaList::index(size_t nfree, bool* frontOut) const {
  // Get the bucket index to use for arenas with |nfree| free things and set
  // |frontOut| to indicate whether to prepend or append to the bucket.

  MOZ_ASSERT(nfree <= thingsPerArena_);

  // Full arenas go in the first bucket on their own.
  if (nfree == 0) {
    *frontOut = false;
    return 0;
  }

  // Empty arenas go in the last bucket on their own.
  if (nfree == thingsPerArena_) {
    *frontOut = false;
    return emptyIndex();
  }

  // All other arenas are alternately added to the front and back of successive
  // buckets as |nfree| increases.
  *frontOut = (nfree % 2) != 0;
  size_t index = (nfree + 1) / 2;
  MOZ_ASSERT(index != 0);
  MOZ_ASSERT(index != emptyIndex());
  return index;
}

size_t js::gc::SortedArenaList::emptyIndex() const {
  // Get the bucket index to use for empty arenas. This must have its own
  // bucket so they can be removed with extractEmptyTo.
  return bucketsUsed() - 1;
}

size_t js::gc::SortedArenaList::bucketsUsed() const {
  // Get the total number of buckets used for the current alloc kind.
  return HowMany(thingsPerArena_ - 1, 2) + 2;
}

void js::gc::SortedArenaList::insertAt(Arena* arena, size_t nfree) {
  MOZ_ASSERT(!isConvertedToArenaList);
  MOZ_ASSERT(nfree <= thingsPerArena_);

  bool front;
  size_t i = index(nfree, &front);
  MOZ_ASSERT(i < BucketCount);
  if (front) {
    buckets[i].pushFront(arena);
  } else {
    buckets[i].pushBack(arena);
  }
}

bool js::gc::SortedArenaList::hasEmptyArenas() const {
  return !buckets[emptyIndex()].isEmpty();
}

void js::gc::SortedArenaList::extractEmptyTo(Arena** destListHeadPtr) {
  MOZ_ASSERT(!isConvertedToArenaList);
  MOZ_ASSERT(destListHeadPtr);
  check();

  Bucket& bucket = buckets[emptyIndex()];
  if (!bucket.isEmpty()) {
    Arena* tail = *destListHeadPtr;
    Arena* bucketLast = bucket.getLast();
    *destListHeadPtr = bucket.release();
    bucketLast->next = tail;
  }

  MOZ_ASSERT(bucket.isEmpty());
}

js::gc::ArenaList js::gc::SortedArenaList::convertToArenaList(
    Arena* maybeBucketLastOut[BucketCount]) {
#ifdef DEBUG
  MOZ_ASSERT(!isConvertedToArenaList);
  isConvertedToArenaList = true;
  check();
#endif

  if (maybeBucketLastOut) {
    for (size_t i = 0; i < BucketCount; i++) {
      maybeBucketLastOut[i] = buckets[i].getLast();
    }
  }

  // The returned ArenaList needs to contain all non-full arenas in order
  // of increasing free space, followed by all full arenas.
  ArenaList result;
  size_t used = bucketsUsed();
  for (size_t i = 1; i <= used; ++i) {
    size_t bucket = i % used;  // [1, used) then 0.
    result.append(std::move(buckets[bucket]));
  }
  return result;
}

void js::gc::SortedArenaList::restoreFromArenaList(
    ArenaList& list, Arena* bucketLast[BucketCount]) {
#ifdef DEBUG
  MOZ_ASSERT(isConvertedToArenaList);
  isConvertedToArenaList = false;
#endif

  // Group the ArenaList elements into SinglyLinkedList buckets, where the
  // boundaries between buckets are retrieved from |bucketLast|.

  Arena* remaining = list.release();

  size_t used = bucketsUsed();
  for (size_t i = 1; i <= used; ++i) {
    size_t bucket = i % used;  // [1, used) then 0.
    MOZ_ASSERT(buckets[bucket].isEmpty());
    if (bucketLast[bucket]) {
      MOZ_ASSERT(remaining);
      Arena* first = remaining;
      Arena* last = bucketLast[bucket];
      remaining = last->next;
      last->next = nullptr;
      new (&buckets[bucket]) Bucket(first, last);
    }
  }

  MOZ_ASSERT(!remaining);
  check();
}

void js::gc::SortedArenaList::check() const {
#ifdef DEBUG
  const auto& fullBucket = buckets[0];
  for (auto arena = fullBucket.iter(); !arena.done(); arena.next()) {
    MOZ_ASSERT(arena->getAllocKind() == allocKind());
    MOZ_ASSERT(!arena->hasFreeThings());
  }

  for (size_t i = 1; i < emptyIndex(); i++) {
    const auto& bucket = buckets[i];
    size_t lastFree = 0;
    for (auto arena = bucket.iter(); !arena.done(); arena.next()) {
      MOZ_ASSERT(arena->getAllocKind() == allocKind());
      size_t nfree = arena->countFreeCells();
      MOZ_ASSERT(nfree == i * 2 - 1 || nfree == i * 2);
      MOZ_ASSERT(nfree >= lastFree);
      lastFree = nfree;
    }
  }

  const auto& emptyBucket = buckets[emptyIndex()];
  for (auto arena = emptyBucket.iter(); !arena.done(); arena.next()) {
    MOZ_ASSERT(arena->getAllocKind() == allocKind());
    MOZ_ASSERT(arena->isEmpty());
  }

  for (size_t i = emptyIndex() + 1; i < BucketCount; i++) {
    MOZ_ASSERT(buckets[i].isEmpty());
  }
#endif
}

#ifdef DEBUG

bool js::gc::FreeLists::allEmpty() const {
  for (auto i : AllAllocKinds()) {
    if (!isEmpty(i)) {
      return false;
    }
  }
  return true;
}

bool js::gc::FreeLists::isEmpty(AllocKind kind) const {
  return freeLists_[kind]->isEmpty();
}

#endif

void js::gc::FreeLists::clear() {
  for (auto i : AllAllocKinds()) {
#ifdef DEBUG
    auto old = freeLists_[i];
    if (!old->isEmpty()) {
      old->getArena()->checkNoMarkedFreeCells();
    }
#endif
    freeLists_[i] = &emptySentinel;
  }
}

js::gc::TenuredCell* js::gc::FreeLists::allocate(AllocKind kind) {
  return freeLists_[kind]->allocate(Arena::thingSize(kind));
}

void js::gc::FreeLists::unmarkPreMarkedFreeCells(AllocKind kind) {
  FreeSpan* freeSpan = freeLists_[kind];
  if (!freeSpan->isEmpty()) {
    freeSpan->getArena()->unmarkPreMarkedFreeCells();
  }
}

JSRuntime* js::gc::ArenaLists::runtime() {
  return zone_->runtimeFromMainThread();
}

JSRuntime* js::gc::ArenaLists::runtimeFromAnyThread() {
  return zone_->runtimeFromAnyThread();
}

js::gc::Arena* js::gc::ArenaLists::getFirstArena(AllocKind thingKind) const {
  return arenaList(thingKind).getFirst();
}

js::gc::Arena* js::gc::ArenaLists::getFirstCollectingArena(
    AllocKind thingKind) const {
  return collectingArenaList(thingKind).getFirst();
}

bool js::gc::ArenaLists::arenaListsAreEmpty() const {
  for (auto i : AllAllocKinds()) {
    /*
     * The arena cannot be empty if the background finalization is not yet
     * done.
     */
    if (concurrentUse(i) == ConcurrentUse::BackgroundFinalize) {
      return false;
    }
    if (!arenaList(i).isEmpty()) {
      return false;
    }
  }
  return true;
}

bool js::gc::ArenaLists::doneBackgroundFinalize(AllocKind kind) const {
  return concurrentUse(kind) == ConcurrentUse::None;
}

void js::gc::ArenaLists::clearFreeLists() { freeLists().clear(); }

MOZ_ALWAYS_INLINE js::gc::TenuredCell* js::gc::ArenaLists::allocateFromFreeList(
    AllocKind thingKind) {
  return freeLists().allocate(thingKind);
}

void js::gc::ArenaLists::unmarkPreMarkedFreeCells() {
  for (auto i : AllAllocKinds()) {
    freeLists().unmarkPreMarkedFreeCells(i);
  }
}

void js::gc::ArenaLists::checkEmptyFreeLists() {
  MOZ_ASSERT(freeLists().allEmpty());
}

void js::gc::ArenaLists::checkEmptyArenaLists() {
#ifdef DEBUG
  for (auto i : AllAllocKinds()) {
    checkEmptyArenaList(i);
  }
#endif
}

#endif  // gc_ArenaList_inl_h
