/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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 wasm_WasmGcObject_inl_h
#define wasm_WasmGcObject_inl_h

#include "wasm/WasmGcObject.h"

#include "mozilla/Attributes.h"
#include "mozilla/DebugOnly.h"
#include "util/Memory.h"

#include "gc/Nursery-inl.h"
#include "gc/ObjectKind-inl.h"
#include "vm/JSContext-inl.h"

//=========================================================================
// WasmStructObject inlineable allocation methods

namespace js {

/* static */
template <bool ZeroFields>
MOZ_ALWAYS_INLINE WasmStructObject* WasmStructObject::createStructIL(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    gc::AllocSite* allocSite, js::gc::Heap initialHeap) {
  // It is up to our caller to ensure that `typeDefData` refers to a type that
  // doesn't need OOL storage.

  MOZ_ASSERT(IsWasmGcObjectClass(typeDefData->clasp));
  MOZ_ASSERT(!typeDefData->clasp->isNativeObject());
  debugCheckNewObject(typeDefData->shape, typeDefData->allocKind, initialHeap);

  mozilla::DebugOnly<const wasm::TypeDef*> typeDef = typeDefData->typeDef;
  MOZ_ASSERT(typeDef->kind() == wasm::TypeDefKind::Struct);

  // This doesn't need to be rooted, since all we do with it prior to
  // return is to zero out the fields (and then only if ZeroFields is true).
  WasmStructObject* structObj = (WasmStructObject*)cx->newCell<WasmGcObject>(
      typeDefData->allocKind, initialHeap, typeDefData->clasp, allocSite);
  if (MOZ_UNLIKELY(!structObj)) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  MOZ_ASSERT((uintptr_t(structObj->inlineData()) % sizeof(uintptr_t)) == 0);
  structObj->initShape(typeDefData->shape);
  structObj->superTypeVector_ = typeDefData->superTypeVector;
  structObj->outlineData_ = nullptr;
  if constexpr (ZeroFields) {
    uint32_t totalBytes = typeDefData->structTypeSize;
    MOZ_ASSERT(totalBytes == typeDef->structType().size_);
    MOZ_ASSERT(totalBytes <= WasmStructObject_MaxInlineBytes);
    MOZ_ASSERT((totalBytes % sizeof(uintptr_t)) == 0);
    memset(structObj->inlineData(), 0, totalBytes);
  }

  js::gc::gcprobes::CreateObject(structObj);
  probes::CreateObject(cx, structObj);

  return structObj;
}

/* static */
template <bool ZeroFields>
MOZ_ALWAYS_INLINE WasmStructObject* WasmStructObject::createStructOOL(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    gc::AllocSite* allocSite, js::gc::Heap initialHeap) {
  // It is up to our caller to ensure that `typeDefData` refers to a type that
  // needs OOL storage.

  MOZ_ASSERT(IsWasmGcObjectClass(typeDefData->clasp));
  MOZ_ASSERT(!typeDefData->clasp->isNativeObject());
  debugCheckNewObject(typeDefData->shape, typeDefData->allocKind, initialHeap);

  mozilla::DebugOnly<const wasm::TypeDef*> typeDef = typeDefData->typeDef;
  MOZ_ASSERT(typeDef->kind() == wasm::TypeDefKind::Struct);

  uint32_t totalBytes = typeDefData->structTypeSize;
  MOZ_ASSERT(totalBytes == typeDef->structType().size_);
  MOZ_ASSERT(totalBytes > WasmStructObject_MaxInlineBytes);
  MOZ_ASSERT((totalBytes % sizeof(uintptr_t)) == 0);

  uint32_t inlineBytes, outlineBytes;
  WasmStructObject::getDataByteSizes(totalBytes, &inlineBytes, &outlineBytes);
  MOZ_ASSERT(inlineBytes == WasmStructObject_MaxInlineBytes);
  MOZ_ASSERT(outlineBytes > 0);

  // Allocate the outline data area before allocating the object so that we can
  // infallibly initialize the outline data area.
  Nursery& nursery = cx->nursery();
  PointerAndUint7 outlineData =
      nursery.mallocedBlockCache().alloc(outlineBytes);
  if (MOZ_UNLIKELY(!outlineData.pointer())) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  // See corresponding comment in WasmArrayObject::createArray.
  Rooted<WasmStructObject*> structObj(cx);
  structObj = (WasmStructObject*)cx->newCell<WasmGcObject>(
      typeDefData->allocKind, initialHeap, typeDefData->clasp, allocSite);
  if (MOZ_UNLIKELY(!structObj)) {
    ReportOutOfMemory(cx);
    if (outlineData.pointer()) {
      nursery.mallocedBlockCache().free(outlineData);
    }
    return nullptr;
  }

  MOZ_ASSERT((uintptr_t(structObj->inlineData()) % sizeof(uintptr_t)) == 0);
  structObj->initShape(typeDefData->shape);
  structObj->superTypeVector_ = typeDefData->superTypeVector;

  // Initialize the outline data fields
  structObj->outlineData_ = (uint8_t*)outlineData.pointer();
  if constexpr (ZeroFields) {
    memset(structObj->inlineData(), 0, inlineBytes);
    memset(outlineData.pointer(), 0, outlineBytes);
  }

  if (MOZ_LIKELY(js::gc::IsInsideNursery(structObj))) {
    // See corresponding comment in WasmArrayObject::createArrayNonEmpty.
    if (MOZ_UNLIKELY(!nursery.registerTrailer(outlineData, outlineBytes))) {
      nursery.mallocedBlockCache().free(outlineData);
      ReportOutOfMemory(cx);
      return nullptr;
    }
  } else {
    // See corresponding comment in WasmArrayObject::createArrayNonEmpty.
    MOZ_ASSERT(structObj->isTenured());
    AddCellMemory(structObj, outlineBytes + wasm::TrailerBlockOverhead,
                  MemoryUse::WasmTrailerBlock);
  }

  js::gc::gcprobes::CreateObject(structObj);
  probes::CreateObject(cx, structObj);

  return structObj;
}

//=========================================================================
// WasmArrayObject inlineable allocation methods

/* static */
inline gc::AllocKind WasmArrayObject::allocKindForOOL() {
  gc::AllocKind allocKind =
      gc::GetGCObjectKindForBytes(sizeof(WasmArrayObject));
  return gc::GetFinalizedAllocKindForClass(allocKind, &WasmArrayObject::class_);
}

/* static */
inline gc::AllocKind WasmArrayObject::allocKindForIL(uint32_t storageBytes) {
  gc::AllocKind allocKind =
      gc::GetGCObjectKindForBytes(sizeof(WasmArrayObject) + storageBytes);
  return gc::GetFinalizedAllocKindForClass(allocKind, &WasmArrayObject::class_);
}

inline gc::AllocKind WasmArrayObject::allocKind() const {
  if (isDataInline()) {
    // numElements_ was validated to not overflow when constructing this object
    uint32_t storageBytes = calcStorageBytesUnchecked(
        typeDef().arrayType().elementType().size(), numElements_);
    return allocKindForIL(storageBytes);
  }

  return allocKindForOOL();
}

/* static */
template <bool ZeroFields>
MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayOOL(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    js::gc::AllocSite* allocSite, js::gc::Heap initialHeap,
    uint32_t numElements, uint32_t storageBytes) {
  STATIC_ASSERT_WASMARRAYELEMENTS_NUMELEMENTS_IS_U32;

  MOZ_ASSERT(IsWasmGcObjectClass(typeDefData->clasp));
  MOZ_ASSERT(!typeDefData->clasp->isNativeObject());
  MOZ_ASSERT(typeDefData->allocKind == gc::AllocKind::INVALID);
  gc::AllocKind allocKind = allocKindForOOL();
  debugCheckNewObject(typeDefData->shape, allocKind, initialHeap);

  mozilla::DebugOnly<const wasm::TypeDef*> typeDef = typeDefData->typeDef;
  MOZ_ASSERT(typeDef->kind() == wasm::TypeDefKind::Array);

  // This routine is for large arrays with out-of-line data only. For small
  // arrays use createArrayIL.
  MOZ_ASSERT(storageBytes > WasmArrayObject_MaxInlineBytes);

  // Allocate the outline data before allocating the object so that we can
  // infallibly initialize the pointer on the array object after it is
  // allocated.
  Nursery& nursery = cx->nursery();
  PointerAndUint7 outlineAlloc(nullptr, 0);
  outlineAlloc = nursery.mallocedBlockCache().alloc(storageBytes);
  if (MOZ_UNLIKELY(!outlineAlloc.pointer())) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  // It's unfortunate that `arrayObj` has to be rooted, since this is a hot
  // path and rooting costs around 15 instructions.  It is the call to
  // registerTrailer that makes it necessary.
  Rooted<WasmArrayObject*> arrayObj(cx);
  arrayObj = (WasmArrayObject*)cx->newCell<WasmGcObject>(
      allocKind, initialHeap, typeDefData->clasp, allocSite);
  if (MOZ_UNLIKELY(!arrayObj)) {
    ReportOutOfMemory(cx);
    if (outlineAlloc.pointer()) {
      nursery.mallocedBlockCache().free(outlineAlloc);
    }
    return nullptr;
  }

  DataHeader* outlineHeader = (DataHeader*)outlineAlloc.pointer();
  uint8_t* outlineData = (uint8_t*)(outlineHeader + 1);
  *outlineHeader = DataIsOOL;

  arrayObj->initShape(typeDefData->shape);
  arrayObj->superTypeVector_ = typeDefData->superTypeVector;
  arrayObj->numElements_ = numElements;
  arrayObj->data_ = outlineData;
  if constexpr (ZeroFields) {
    uint32_t dataBytes = storageBytes - sizeof(DataHeader);
    MOZ_ASSERT(dataBytes >= numElements * typeDefData->arrayElemSize);
    memset(arrayObj->data_, 0, dataBytes);
  }

  MOZ_ASSERT(!arrayObj->isDataInline());

  if (MOZ_LIKELY(js::gc::IsInsideNursery(arrayObj))) {
    // We need to register the OOL area with the nursery, so it will be freed
    // after GCing of the nursery if `arrayObj_` doesn't make it into the
    // tenured heap.  Note, the nursery will keep a running total of the
    // current trailer block sizes, so it can decide to do a (minor)
    // collection if that becomes excessive.
    if (MOZ_UNLIKELY(!nursery.registerTrailer(outlineAlloc, storageBytes))) {
      nursery.mallocedBlockCache().free(outlineAlloc);
      ReportOutOfMemory(cx);
      return nullptr;
    }
  } else {
    MOZ_ASSERT(arrayObj->isTenured());
    // Register the trailer size with the major GC mechanism, so that can also
    // is able to decide if that space use warrants a (major) collection.
    AddCellMemory(arrayObj, storageBytes + wasm::TrailerBlockOverhead,
                  MemoryUse::WasmTrailerBlock);
  }

  js::gc::gcprobes::CreateObject(arrayObj);
  probes::CreateObject(cx, arrayObj);

  return arrayObj;
}

template WasmArrayObject* WasmArrayObject::createArrayOOL<true>(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    js::gc::AllocSite* allocSite, js::gc::Heap initialHeap,
    uint32_t numElements, uint32_t storageBytes);
template WasmArrayObject* WasmArrayObject::createArrayOOL<false>(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    js::gc::AllocSite* allocSite, js::gc::Heap initialHeap,
    uint32_t numElements, uint32_t storageBytes);

/* static */
template <bool ZeroFields>
MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayIL(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    js::gc::AllocSite* allocSite, js::gc::Heap initialHeap,
    uint32_t numElements, uint32_t storageBytes) {
  STATIC_ASSERT_WASMARRAYELEMENTS_NUMELEMENTS_IS_U32;

  MOZ_ASSERT(IsWasmGcObjectClass(typeDefData->clasp));
  MOZ_ASSERT(!typeDefData->clasp->isNativeObject());
  MOZ_ASSERT(typeDefData->allocKind == gc::AllocKind::INVALID);
  gc::AllocKind allocKind = allocKindForIL(storageBytes);
  debugCheckNewObject(typeDefData->shape, allocKind, initialHeap);

  mozilla::DebugOnly<const wasm::TypeDef*> typeDef = typeDefData->typeDef;
  MOZ_ASSERT(typeDef->kind() == wasm::TypeDefKind::Array);

  MOZ_ASSERT(storageBytes <= WasmArrayObject_MaxInlineBytes);

  // There's no need for `arrayObj` to be rooted, since the only thing we're
  // going to do is fill in some bits of it, then return it.
  WasmArrayObject* arrayObj = (WasmArrayObject*)cx->newCell<WasmGcObject>(
      allocKind, initialHeap, typeDefData->clasp, allocSite);
  if (MOZ_UNLIKELY(!arrayObj)) {
    ReportOutOfMemory(cx);
    return nullptr;
  }

  arrayObj->initShape(typeDefData->shape);
  arrayObj->superTypeVector_ = typeDefData->superTypeVector;
  arrayObj->numElements_ = numElements;

  DataHeader* inlineHeader =
      WasmArrayObject::addressOfInlineDataHeader(arrayObj);
  uint8_t* inlineData = WasmArrayObject::addressOfInlineData(arrayObj);
  *inlineHeader = DataIsIL;
  arrayObj->data_ = inlineData;

  if constexpr (ZeroFields) {
    uint32_t dataBytes = storageBytes - sizeof(DataHeader);
    MOZ_ASSERT(dataBytes >= numElements * typeDefData->arrayElemSize);

    if (numElements > 0) {
      memset(arrayObj->data_, 0, dataBytes);
    }
  }

  MOZ_ASSERT(arrayObj->isDataInline());

  js::gc::gcprobes::CreateObject(arrayObj);
  probes::CreateObject(cx, arrayObj);

  return arrayObj;
}

template WasmArrayObject* WasmArrayObject::createArrayIL<true>(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    js::gc::AllocSite* allocSite, js::gc::Heap initialHeap,
    uint32_t numElements, uint32_t storageBytes);
template WasmArrayObject* WasmArrayObject::createArrayIL<false>(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    js::gc::AllocSite* allocSite, js::gc::Heap initialHeap,
    uint32_t numElements, uint32_t storageBytes);

/* static */
template <bool ZeroFields>
MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArray(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    js::gc::AllocSite* allocSite, js::gc::Heap initialHeap,
    uint32_t numElements) {
  MOZ_ASSERT(typeDefData->arrayElemSize ==
             typeDefData->typeDef->arrayType().elementType().size());
  mozilla::CheckedUint32 storageBytes =
      calcStorageBytesChecked(typeDefData->arrayElemSize, numElements);
  if (!storageBytes.isValid() ||
      storageBytes.value() > uint32_t(wasm::MaxArrayPayloadBytes)) {
    js::ReportOversizedAllocation(cx, JSMSG_WASM_ARRAY_IMP_LIMIT);
    wasm::MarkPendingExceptionAsTrap(cx);
    return nullptr;
  }

  if (storageBytes.value() <= WasmArrayObject_MaxInlineBytes) {
    return createArrayIL<ZeroFields>(cx, typeDefData, allocSite, initialHeap,
                                     numElements, storageBytes.value());
  }

  return createArrayOOL<ZeroFields>(cx, typeDefData, allocSite, initialHeap,
                                    numElements, storageBytes.value());
}

template WasmArrayObject* WasmArrayObject::createArray<true>(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    js::gc::AllocSite* allocSite, js::gc::Heap initialHeap,
    uint32_t numElements);
template WasmArrayObject* WasmArrayObject::createArray<false>(
    JSContext* cx, wasm::TypeDefInstanceData* typeDefData,
    js::gc::AllocSite* allocSite, js::gc::Heap initialHeap,
    uint32_t numElements);

}  // namespace js

#endif /* wasm_WasmGcObject_inl_h */
