/* -*- 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 MOZ_ALWAYS_INLINE WasmStructObject* WasmStructObject::createStructIL( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, 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 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( typeDefData->allocKind, initialHeap, typeDefData->clasp, &typeDefData->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 MOZ_ALWAYS_INLINE WasmStructObject* WasmStructObject::createStructOOL( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, 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 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 structObj(cx); structObj = (WasmStructObject*)cx->newCell( typeDefData->allocKind, initialHeap, typeDefData->clasp, &typeDefData->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)); if (CanChangeToBackgroundAllocKind(allocKind, &WasmArrayObject::class_)) { allocKind = ForegroundToBackgroundAllocKind(allocKind); } return allocKind; } /* static */ inline gc::AllocKind WasmArrayObject::allocKindForIL(uint32_t storageBytes) { gc::AllocKind allocKind = gc::GetGCObjectKindForBytes(sizeof(WasmArrayObject) + storageBytes); if (CanChangeToBackgroundAllocKind(allocKind, &WasmArrayObject::class_)) { allocKind = ForegroundToBackgroundAllocKind(allocKind); } return allocKind; } inline gc::AllocKind WasmArrayObject::allocKind() const { if (isDataInline()) { uint32_t storageBytes = calcStorageBytes( typeDef().arrayType().elementType_.size(), numElements_); return allocKindForIL(storageBytes); } return allocKindForOOL(); } /* static */ template MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayOOL( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, 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 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 arrayObj(cx); arrayObj = (WasmArrayObject*)cx->newCell( allocKind, initialHeap, typeDefData->clasp, &typeDefData->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( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, js::gc::Heap initialHeap, uint32_t numElements, uint32_t storageBytes); template WasmArrayObject* WasmArrayObject::createArrayOOL( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, js::gc::Heap initialHeap, uint32_t numElements, uint32_t storageBytes); /* static */ template MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArrayIL( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, 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 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( allocKind, initialHeap, typeDefData->clasp, &typeDefData->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( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, js::gc::Heap initialHeap, uint32_t numElements, uint32_t storageBytes); template WasmArrayObject* WasmArrayObject::createArrayIL( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, js::gc::Heap initialHeap, uint32_t numElements, uint32_t storageBytes); /* static */ template MOZ_ALWAYS_INLINE WasmArrayObject* WasmArrayObject::createArray( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, js::gc::Heap initialHeap, uint32_t numElements) { MOZ_ASSERT(typeDefData->arrayElemSize == typeDefData->typeDef->arrayType().elementType_.size()); CheckedUint32 storageBytes = calcStorageBytesChecked(typeDefData->arrayElemSize, numElements); if (!storageBytes.isValid() || storageBytes.value() > uint32_t(wasm::MaxArrayPayloadBytes)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_ARRAY_IMP_LIMIT); return nullptr; } if (storageBytes.value() <= WasmArrayObject_MaxInlineBytes) { return createArrayIL(cx, typeDefData, initialHeap, numElements, storageBytes.value()); } return createArrayOOL(cx, typeDefData, initialHeap, numElements, storageBytes.value()); } template WasmArrayObject* WasmArrayObject::createArray( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, js::gc::Heap initialHeap, uint32_t numElements); template WasmArrayObject* WasmArrayObject::createArray( JSContext* cx, wasm::TypeDefInstanceData* typeDefData, js::gc::Heap initialHeap, uint32_t numElements); } // namespace js #endif /* wasm_WasmGcObject_inl_h */