/* * 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 bool js::Nursery::isInside(const SharedMem& 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(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(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(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 static inline T* AllocNurseryOrMallocBuffer(Nursery& nursery, gc::Cell* cell, uint32_t count) { size_t nbytes = RoundUp(count * sizeof(T), sizeof(Value)); return static_cast(nursery.allocNurseryOrMallocBuffer( cell->zone(), cell, nbytes, js::MallocArena)); } template static inline T* AllocNurseryOrMallocBuffer(JSContext* cx, gc::Cell* cell, uint32_t count) { T* buffer = AllocNurseryOrMallocBuffer(cx->nursery(), cell, count); if (!buffer) { ReportOutOfMemory(cx); return nullptr; } return buffer; } template 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( nursery.allocateBuffer(zone, cell, nbytes, maxNurserySize)); } template static inline T* AllocateCellBuffer( JSContext* cx, gc::Cell* cell, uint32_t count, size_t maxNurserySize = Nursery::MaxNurseryBufferSize) { T* buffer = AllocateCellBuffer(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 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(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 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(nursery.reallocateBuffer( zone, cell, oldBuffer, oldBytes, newBytes, maxNurserySize)); } // If this returns null then the old buffer will be left alone. template 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(cx->nursery(), cx->zone(), cell, oldBuffer, oldCount, newCount, maxNurserySize); if (!buffer) { ReportOutOfMemory(cx); } return buffer; } template 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 static inline void FreeCellBuffer(JSContext* cx, gc::Cell* cell, T* buffer, uint32_t count) { FreeCellBuffer(cx->nursery(), cx->zone(), cell, buffer, count); } } // namespace js #endif /* gc_Nursery_inl_h */