/* -*- 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/. */ /* * Inline definitions of the CellAllocator methods. * * This is included from JSContext-inl.h for the definiton of JSContext::newCell * and shouldn't need to be included elsewhere. * */ #ifndef gc_Allocator_inl_h #define gc_Allocator_inl_h #include "gc/Allocator.h" #include "gc/Barrier.h" #include "gc/Cell.h" #include "gc/Zone.h" #include "js/Class.h" #include "js/RootingAPI.h" #include "gc/Nursery-inl.h" // Note on memory fences (MemoryReleaseFence) and concurrent marking. // // This code uses memory fences to ensure that concurrent marking doesn't read // uninitialized memory. The aim here is to prevent the compiler or hardware // re-ordering memory accesses so that the mutator cannot write a pointer to the // new GC thing into the heap before the initialization done by the constructor // has happened. // // This assumes that a freshly constructed GC thing is safe to mark before any // subsequent initialization. // // The release fence here pairs with acquire fences in the marking code. // // TODO: According to the C++ specification this also requires that the heap // write is an atomic access which is not currently the case. namespace js { namespace gc { template T* CellAllocator::NewCell(JSContext* cx, Args&&... args) { static_assert(std::is_base_of_v); // Objects. See the valid parameter list in NewObject, above. if constexpr (std::is_base_of_v) { return NewObject(cx, std::forward(args)...); } // BigInt else if constexpr (std::is_base_of_v) { return NewBigInt(cx, std::forward(args)...); } // GetterSetter else if constexpr (std::is_base_of_v) { return NewGetterSetter(cx, std::forward(args)...); } // "Normal" strings (all of which can be nursery allocated). Atoms and // external strings will fall through to the generic code below. All other // strings go through NewString, which will forward the arguments to the // appropriate string class's constructor. else if constexpr (std::is_base_of_v && !std::is_base_of_v && !std::is_base_of_v) { return NewString(cx, std::forward(args)...); } else { // Allocate a new tenured GC thing that's not nursery-allocatable. Use // cx->newCell(...), where the parameters are forwarded to the type's // constructor. return NewTenuredCell(cx, std::forward(args)...); } } template /* static */ T* CellAllocator::NewString(JSContext* cx, gc::Heap heap, Args&&... args) { static_assert(std::is_base_of_v); gc::AllocKind kind = gc::MapTypeToAllocKind::kind; void* ptr = AllocNurseryOrTenuredCell( cx, kind, sizeof(T), heap, nullptr); if (MOZ_UNLIKELY(!ptr)) { return nullptr; } T* string = new (mozilla::KnownNotNull, ptr) T(std::forward(args)...); MemoryReleaseFence(cx->zone()); // See note above. return string; } template /* static */ T* CellAllocator::NewBigInt(JSContext* cx, Heap heap) { void* ptr = AllocNurseryOrTenuredCell( cx, gc::AllocKind::BIGINT, sizeof(T), heap, nullptr); if (MOZ_UNLIKELY(!ptr)) { return nullptr; } T* bigInt = new (mozilla::KnownNotNull, ptr) T(); MemoryReleaseFence(cx->zone()); // See note above. return bigInt; } template /* static */ T* CellAllocator::NewGetterSetter(JSContext* cx, gc::Heap heap, Args&&... args) { static_assert(std::is_base_of_v); void* ptr = AllocNurseryOrTenuredCell( cx, gc::AllocKind::GETTER_SETTER, sizeof(T), heap, nullptr); if (MOZ_UNLIKELY(!ptr)) { return nullptr; } T* gs = new (mozilla::KnownNotNull, ptr) T(std::forward(args)...); MemoryReleaseFence(cx->zone()); // See note above. return gs; } template /* static */ T* CellAllocator::NewObject(JSContext* cx, gc::AllocKind kind, gc::Heap heap, const JSClass* clasp, gc::AllocSite* site) { MOZ_ASSERT(IsObjectAllocKind(kind)); MOZ_ASSERT_IF(heap != gc::Heap::Tenured && clasp->hasFinalize() && !clasp->isProxyObject(), CanNurseryAllocateFinalizedClass(clasp)); size_t thingSize = JSObject::thingSize(kind); void* cell = AllocNurseryOrTenuredCell( cx, kind, thingSize, heap, site); if (MOZ_UNLIKELY(!cell)) { return nullptr; } T* object = new (mozilla::KnownNotNull, cell) T(); MemoryReleaseFence(cx->zone()); // See note above. return object; } template /* static */ T* CellAllocator::NewTenuredCell(JSContext* cx, Args&&... args) { gc::AllocKind kind = gc::MapTypeToAllocKind::kind; MOZ_ASSERT(Arena::thingSize(kind) == sizeof(T)); void* ptr = AllocTenuredCell(cx, kind); if (MOZ_UNLIKELY(!ptr)) { return nullptr; } T* cell = new (mozilla::KnownNotNull, ptr) T(std::forward(args)...); MemoryReleaseFence(cx->zone()); // See note above. return cell; } #if defined(DEBUG) || defined(JS_GC_ZEAL) || defined(JS_OOM_BREAKPOINT) // This serves as a single point to perform some unrelated checks that happens // before every allocation. Performs the following: // // - checks we can't GC inside a JS::AutoAssertNoGC region // - runs a zeal GC if needed // // This is a no-op in release builds. // // This is only called on paths where GC is allowed. inline void PreAllocGCChecks(JSContext* cx) { // Crash if we could perform a GC action when it is not safe. if (!cx->suppressGC) { cx->verifyIsSafeToGC(); } # ifdef JS_GC_ZEAL GCRuntime* gc = &cx->runtime()->gc; if (gc->needZealousGC()) { gc->runDebugGC(); } # endif } inline bool CheckForSimulatedFailure(JSContext* cx, AllowGC allowGC) { // For testing out of memory conditions. if (js::oom::ShouldFailWithOOM()) { // If we are doing a fallible allocation, percolate up the OOM instead of // reporting it. if (allowGC) { ReportOutOfMemory(cx); } return false; } return true; } #else inline void PreAllocGCChecks(JSContext* cx) {} inline bool CheckForSimulatedFailure(JSContext* cx, AllowGC allowGC) { return true; } #endif // DEBUG || JS_GC_ZEAL || JS_OOM_BREAKPOINT template /* static */ void* CellAllocator::AllocNurseryOrTenuredCell(JSContext* cx, gc::AllocKind allocKind, size_t thingSize, gc::Heap heap, AllocSite* site) { MOZ_ASSERT(IsNurseryAllocable(allocKind)); MOZ_ASSERT(MapAllocToTraceKind(allocKind) == traceKind); MOZ_ASSERT(thingSize == Arena::thingSize(allocKind)); MOZ_ASSERT_IF(site && site->initialHeap() == Heap::Tenured, heap == Heap::Tenured); MOZ_ASSERT(!cx->zone()->isAtomsZone()); MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); if constexpr (allowGC) { PreAllocGCChecks(cx); } if (!CheckForSimulatedFailure(cx, allowGC)) { return nullptr; } JS::Zone* zone = cx->zone(); gc::Heap minHeapToTenure = CheckedHeap(zone->minHeapToTenure(traceKind)); if (CheckedHeap(heap) < minHeapToTenure) { if (!site) { site = zone->unknownAllocSite(traceKind); } #ifdef JS_GC_ZEAL site = MaybeGenerateMissingAllocSite(cx, traceKind, site); #endif void* ptr = cx->nursery().tryAllocateCell(site, thingSize, traceKind); if (MOZ_LIKELY(ptr)) { return ptr; } return RetryNurseryAlloc(cx, traceKind, allocKind, thingSize, site); } return AllocTenuredCellForNurseryAlloc(cx, allocKind); } /* static */ MOZ_ALWAYS_INLINE gc::Heap CellAllocator::CheckedHeap(gc::Heap heap) { if (heap > Heap::Tenured) { // This helps the compiler to see that nursery allocation is never // possible if Heap::Tenured is specified. MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad gc::Heap value"); } return heap; } template T* NewSizedBuffer(JS::Zone* zone, size_t bytes, bool nurseryOwned, Args&&... args) { MOZ_ASSERT(sizeof(T) <= bytes); void* ptr = AllocBuffer(zone, bytes, nurseryOwned); if (!ptr) { return nullptr; } T* buffer = new (ptr) T(std::forward(args)...); MemoryReleaseFence(zone); // See note above. return buffer; } } // namespace gc } // namespace js #endif // gc_Allocator_inl_h