/*
 * 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_Nursery_inl_h
#define gc_Nursery_inl_h

#include "gc/Nursery.h"

#include "gc/GCRuntime.h"
#include "gc/RelocationOverlay.h"
#include "js/TracingAPI.h"
#include "vm/JSContext.h"
#include "vm/NativeObject.h"
#include "vm/StringType.h"

namespace js {
namespace gc {
class Cell;
}  // namespace gc
}  // namespace js

inline JSRuntime* js::Nursery::runtime() const { return gc->rt; }

template <typename T>
bool js::Nursery::isInside(const SharedMem<T>& p) const {
  return isInside(p.unwrap(/*safe - used for value in comparison above*/));
}

inline void js::Nursery::addMallocedBufferBytes(size_t nbytes) {
  MOZ_ASSERT(nbytes > 0);
  toSpace.mallocedBufferBytes += nbytes;
  if (MOZ_UNLIKELY(toSpace.mallocedBufferBytes > capacity() * 8)) {
    requestMinorGC(JS::GCReason::NURSERY_MALLOC_BUFFERS);
  }
}

inline void js::Nursery::removeMallocedBufferBytes(size_t nbytes) {
  MOZ_ASSERT(nbytes > 0);
  MOZ_ASSERT(toSpace.mallocedBufferBytes >= nbytes);
  toSpace.mallocedBufferBytes -= nbytes;
}

inline bool js::Nursery::addStringBuffer(JSLinearString* s) {
  MOZ_ASSERT(IsInsideNursery(s));
  MOZ_ASSERT(isEnabled());
  MOZ_ASSERT(s->hasStringBuffer());

  auto* buffer = s->stringBuffer();
  if (!stringBuffers_.emplaceBack(s, buffer)) {
    return false;
  }

  // Note: update mallocedBufferBytes only if the buffer has a refcount of 1, to
  // avoid double counting when the same buffer is used by multiple nursery
  // strings.
  if (!buffer->HasMultipleReferences()) {
    addMallocedBufferBytes(buffer->AllocationSize());
  }
  return true;
}

inline bool js::Nursery::addExtensibleStringBuffer(
    JSLinearString* s, mozilla::StringBuffer* buffer, bool updateMallocBytes) {
  MOZ_ASSERT(IsInsideNursery(s));
  MOZ_ASSERT(isEnabled());
  if (!extensibleStringBuffers_.putNew(s, buffer)) {
    return false;
  }
  MOZ_ASSERT(!buffer->HasMultipleReferences());
  if (updateMallocBytes) {
    addMallocedBufferBytes(buffer->AllocationSize());
  }
  return true;
}

inline void js::Nursery::removeMallocedBuffer(void* buffer, size_t nbytes) {
  MOZ_ASSERT(!JS::RuntimeHeapIsMinorCollecting());
  MOZ_ASSERT(toSpace.mallocedBuffers.has(buffer));
  MOZ_ASSERT(nbytes > 0);
  removeMallocedBufferBytes(nbytes);
  toSpace.mallocedBuffers.remove(buffer);
}

void js::Nursery::removeMallocedBufferDuringMinorGC(void* buffer) {
  MOZ_ASSERT(JS::RuntimeHeapIsMinorCollecting());
  MOZ_ASSERT(fromSpace.mallocedBuffers.has(buffer));
  fromSpace.mallocedBuffers.remove(buffer);
}

inline void js::Nursery::removeExtensibleStringBuffer(JSLinearString* s,
                                                      bool updateMallocBytes) {
  MOZ_ASSERT(gc::IsInsideNursery(s));
  extensibleStringBuffers_.remove(s);

  if (updateMallocBytes) {
    size_t nbytes = s->stringBuffer()->AllocationSize();
    removeMallocedBufferBytes(nbytes);
  }
}

inline bool js::Nursery::shouldTenure(gc::Cell* cell) {
  MOZ_ASSERT(semispaceEnabled());
  MOZ_ASSERT(inCollectedRegion(cell));

  size_t offset = fromSpace.offsetFromAddress(uintptr_t(cell));
  MOZ_ASSERT(offset >=
             fromSpace.offsetFromExclusiveAddress(fromSpace.startPosition_));
  return offset <= tenureThreshold_;
}

inline bool js::Nursery::inCollectedRegion(const gc::Cell* cell) const {
  return gc::InCollectedNurseryRegion(cell);
}

inline bool js::Nursery::inCollectedRegion(void* ptr) const {
  if (!semispaceEnabled()) {
    return toSpace.isInside(ptr);
  }

  return fromSpace.isInside(ptr);
}

inline size_t js::Nursery::Space::offsetFromExclusiveAddress(
    uintptr_t addr) const {
  if ((addr & gc::ChunkMask) == 0) {
    // |addr| points one past the end of the previous chunk.
    return offsetFromAddress(addr - 1) + 1;
  }

  return offsetFromAddress(addr);
}

inline size_t js::Nursery::Space::offsetFromAddress(uintptr_t addr) const {
  gc::ChunkBase* chunk =
      gc::detail::GetCellChunkBase(reinterpret_cast<gc::Cell*>(addr));
  MOZ_ASSERT(chunk->getKind() == kind);
  MOZ_ASSERT(findChunkIndex(addr & ~gc::ChunkMask) == chunk->nurseryChunkIndex);

  uint32_t offset = addr & gc::ChunkMask;
  MOZ_ASSERT(offset >= sizeof(gc::ChunkBase));
  return (chunk->nurseryChunkIndex << gc::ChunkShift) | offset;
}

MOZ_ALWAYS_INLINE /* static */ bool js::Nursery::getForwardedPointer(
    js::gc::Cell** ref) {
  js::gc::Cell* cell = (*ref);
  MOZ_ASSERT(IsInsideNursery(cell));
  if (!cell->isForwarded()) {
    return false;
  }
  const gc::RelocationOverlay* overlay = gc::RelocationOverlay::fromCell(cell);
  *ref = overlay->forwardingAddress();
  return true;
}

inline void js::Nursery::maybeSetForwardingPointer(JSTracer* trc, void* oldData,
                                                   void* newData, bool direct) {
  if (trc->isTenuringTracer()) {
    setForwardingPointerWhileTenuring(oldData, newData, direct);
  }
}

inline void js::Nursery::setForwardingPointerWhileTenuring(void* oldData,
                                                           void* newData,
                                                           bool direct) {
  if (isInside(oldData)) {
    setForwardingPointer(oldData, newData, direct);
  }
}

inline void js::Nursery::setSlotsForwardingPointer(HeapSlot* oldSlots,
                                                   HeapSlot* newSlots,
                                                   uint32_t nslots) {
  // Slot arrays always have enough space for a forwarding pointer, since the
  // number of slots is never zero.
  MOZ_ASSERT(nslots > 0);
  setDirectForwardingPointer(oldSlots, newSlots);
}

inline void js::Nursery::setElementsForwardingPointer(ObjectElements* oldHeader,
                                                      ObjectElements* newHeader,
                                                      uint32_t capacity) {
  // Only use a direct forwarding pointer if there is enough space for one.
  setForwardingPointer(oldHeader->elements(), newHeader->elements(),
                       capacity > 0);
}

inline void js::Nursery::setForwardingPointer(void* oldData, void* newData,
                                              bool direct) {
  if (direct) {
    setDirectForwardingPointer(oldData, newData);
    return;
  }

  setIndirectForwardingPointer(oldData, newData);
}

inline void js::Nursery::setDirectForwardingPointer(void* oldData,
                                                    void* newData) {
  MOZ_ASSERT(isInside(oldData));
  MOZ_ASSERT_IF(isInside(newData), !inCollectedRegion(newData));

  new (oldData) BufferRelocationOverlay{newData};
}

inline void* js::Nursery::tryAllocateCell(gc::AllocSite* site, size_t size,
                                          JS::TraceKind kind) {
  // Ensure there's enough space to replace the contents with a
  // RelocationOverlay.
  // MOZ_ASSERT(size >= sizeof(RelocationOverlay));
  MOZ_ASSERT(size % gc::CellAlignBytes == 0);
  MOZ_ASSERT(size_t(kind) < gc::NurseryTraceKinds);
  MOZ_ASSERT_IF(kind == JS::TraceKind::String, canAllocateStrings());
  MOZ_ASSERT_IF(kind == JS::TraceKind::BigInt, canAllocateBigInts());

  void* ptr = tryAllocate(sizeof(gc::NurseryCellHeader) + size);
  if (MOZ_UNLIKELY(!ptr)) {
    return nullptr;
  }

  new (ptr) gc::NurseryCellHeader(site, kind);

  void* cell =
      reinterpret_cast<void*>(uintptr_t(ptr) + sizeof(gc::NurseryCellHeader));
  if (!cell) {
    MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
        "Successful allocation cannot result in nullptr");
  }

  // Update the allocation site. This code is also inlined in
  // MacroAssembler::updateAllocSite.
  uint32_t allocCount = site->incAllocCount();
  if (allocCount == gc::NormalSiteAttentionThreshold) {
    pretenuringNursery.insertIntoAllocatedList(site);
  }
  MOZ_ASSERT_IF(
      site->isNormal() && allocCount >= gc::NormalSiteAttentionThreshold,
      site->isInAllocatedList());

  gc::gcprobes::NurseryAlloc(cell, kind);
  return cell;
}

inline void* js::Nursery::tryAllocate(size_t size) {
  MOZ_ASSERT(isEnabled());
  MOZ_ASSERT_IF(JS::RuntimeHeapIsBusy(), JS::RuntimeHeapIsMinorCollecting());
  MOZ_ASSERT_IF(currentChunk() == startChunk(), position() >= startPosition());
  MOZ_ASSERT(size % gc::CellAlignBytes == 0);
  MOZ_ASSERT(position() % gc::CellAlignBytes == 0);

  if (MOZ_UNLIKELY(currentEnd() < position() + size)) {
    return nullptr;
  }

  void* ptr = reinterpret_cast<void*>(position());
  if (!ptr) {
    MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE(
        "Successful allocation cannot result in nullptr");
  }

  toSpace.position_ = position() + size;

  DebugOnlyPoison(ptr, JS_ALLOCATED_NURSERY_PATTERN, size,
                  MemCheckKind::MakeUndefined);

  return ptr;
}

inline void* js::Nursery::allocateBuffer(Zone* zone, js::gc::Cell* owner,
                                         size_t nbytes, size_t maxNurserySize) {
  MOZ_ASSERT(owner);
  MOZ_ASSERT(zone == owner->zone());
  MOZ_ASSERT(nbytes > 0);
  MOZ_ASSERT(nbytes <= SIZE_MAX - gc::CellAlignBytes);
  nbytes = RoundUp(nbytes, gc::CellAlignBytes);

  if (!IsInsideNursery(owner)) {
    return gc::AllocBuffer(zone, nbytes, false);
  }

  if (nbytes <= maxNurserySize) {
    void* buffer = allocateInternalBuffer(zone, nbytes);
    if (buffer) {
      return buffer;
    }
  }

  return gc::AllocBuffer(zone, nbytes, true);
}

namespace js {

// The allocation methods below will not run the garbage collector. If the
// nursery cannot accomodate the allocation, the malloc heap will be used
// instead.

template <typename T>
static inline T* AllocNurseryOrMallocBuffer(Nursery& nursery, gc::Cell* cell,
                                            uint32_t count) {
  size_t nbytes = RoundUp(count * sizeof(T), sizeof(Value));
  return static_cast<T*>(nursery.allocNurseryOrMallocBuffer(
      cell->zone(), cell, nbytes, js::MallocArena));
}

template <typename T>
static inline T* AllocNurseryOrMallocBuffer(JSContext* cx, gc::Cell* cell,
                                            uint32_t count) {
  T* buffer = AllocNurseryOrMallocBuffer<T>(cx->nursery(), cell, count);
  if (!buffer) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  return buffer;
}

template <typename T>
static inline T* AllocateCellBuffer(
    Nursery& nursery, JS::Zone* zone, gc::Cell* cell, uint32_t count,
    size_t maxNurserySize = Nursery::MaxNurseryBufferSize) {
  MOZ_ASSERT(zone == cell->zone());

  size_t nbytes = RoundUp(count * sizeof(T), sizeof(Value));
  return static_cast<T*>(
      nursery.allocateBuffer(zone, cell, nbytes, maxNurserySize));
}

template <typename T>
static inline T* AllocateCellBuffer(
    JSContext* cx, gc::Cell* cell, uint32_t count,
    size_t maxNurserySize = Nursery::MaxNurseryBufferSize) {
  T* buffer = AllocateCellBuffer<T>(cx->nursery(), cx->zone(), cell, count,
                                    maxNurserySize);
  if (!buffer) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  return buffer;
}

// If this returns null then the old buffer will be left alone.
template <typename T>
static inline T* ReallocNurseryOrMallocBuffer(JSContext* cx, gc::Cell* cell,
                                              T* oldBuffer, uint32_t oldCount,
                                              uint32_t newCount,
                                              arena_id_t arenaId) {
  size_t oldBytes = RoundUp(oldCount * sizeof(T), sizeof(Value));
  size_t newBytes = RoundUp(newCount * sizeof(T), sizeof(Value));

  T* buffer = static_cast<T*>(cx->nursery().reallocNurseryOrMallocBuffer(
      cell->zone(), cell, oldBuffer, oldBytes, newBytes, arenaId));
  if (!buffer) {
    ReportOutOfMemory(cx);
  }

  return buffer;
}

// If this returns null then the old buffer will be left alone.
template <typename T>
static inline T* ReallocateCellBuffer(
    Nursery& nursery, JS::Zone* zone, gc::Cell* cell, T* oldBuffer,
    uint32_t oldCount, uint32_t newCount,
    size_t maxNurserySize = Nursery::MaxNurseryBufferSize) {
  MOZ_ASSERT(zone == cell->zone());

  size_t oldBytes = RoundUp(oldCount * sizeof(T), sizeof(Value));
  size_t newBytes = RoundUp(newCount * sizeof(T), sizeof(Value));

  return static_cast<T*>(nursery.reallocateBuffer(
      zone, cell, oldBuffer, oldBytes, newBytes, maxNurserySize));
}

// If this returns null then the old buffer will be left alone.
template <typename T>
static inline T* ReallocateCellBuffer(
    JSContext* cx, gc::Cell* cell, T* oldBuffer, uint32_t oldCount,
    uint32_t newCount, size_t maxNurserySize = Nursery::MaxNurseryBufferSize) {
  T* buffer =
      ReallocateCellBuffer<T>(cx->nursery(), cx->zone(), cell, oldBuffer,
                              oldCount, newCount, maxNurserySize);
  if (!buffer) {
    ReportOutOfMemory(cx);
  }

  return buffer;
}

template <typename T>
static inline void FreeCellBuffer(Nursery& nursery, JS::Zone* zone,
                                  gc::Cell* cell, T* buffer, uint32_t count) {
  MOZ_ASSERT(zone == cell->zone());

  size_t bytes = RoundUp(count * sizeof(T), sizeof(Value));

  nursery.freeBuffer(zone, cell, buffer, bytes);
}

template <typename T>
static inline void FreeCellBuffer(JSContext* cx, gc::Cell* cell, T* buffer,
                                  uint32_t count) {
  FreeCellBuffer<T>(cx->nursery(), cx->zone(), cell, buffer, count);
}

}  // namespace js

#endif /* gc_Nursery_inl_h */
