/* -*- 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/. */ /* * JS allocation policies. * * The allocators here are for system memory with lifetimes which are not * managed by the GC. See the comment at the top of vm/MallocProvider.h. */ #ifndef js_AllocPolicy_h #define js_AllocPolicy_h #include "mozilla/MemoryReporting.h" // For MallocSizeOf #include "mozilla/mozalloc.h" // For InfallibleAllocPolicy #include "js/TypeDecls.h" #include "js/Utility.h" class JS_PUBLIC_API JSTracer; extern MOZ_COLD JS_PUBLIC_API void JS_ReportOutOfMemory(JSContext* cx); namespace js { class FrontendContext; enum class AllocFunction { Malloc, Calloc, Realloc }; namespace gc { class Cell; } // namespace gc // Base class for JS engine allocation policies. This provides default // implementations of some methods that may be overridden. // // See mfbt/AllocPolicy.h for more details about allocation policies. class AllocPolicyBase { public: // Report allocation overflow. The default behaviour is to ignore it. void reportAllocOverflow() const {} // Used to trigger OOMs deterministically during testing. The default // behaviour is to support this feature. bool checkSimulatedOOM() const { return !js::oom::ShouldFailWithOOM(); } // For allocation policies that support allocating GCed memory, update // information about their owning GC thing. For policies that use malloc, this // is a no-op. void updateOwningGCThing(gc::Cell* maybeOwner) {} // For allocation policies that support allocating GCed memory, trace an // allocation. For policies that use malloc, this is a no-op. template void traceOwnedAlloc(JSTracer* trc, gc::Cell* maybeOwner, T** ptrp, const char* name) {} // For memory reporting, get the size of an allocation made with this policy. // The parameter |mallocSizeOf| is only used for policies that use malloc. size_t getAllocSize(void* ptr, mozilla::MallocSizeOf mallocSizeOf) { return mallocSizeOf(ptr); } }; // Trace all owned allocations used by a container by calling the alloc // policy's traceOwnedAlloc method for each on them, and update alloc policy // information about the owning GC thing. // // Requires the following methods on the container: // - allocPolicy -- gets the AllocPolicy // - traceOwnedAllocs -- calls the passed closure on all allocations template void TraceOwnedAllocs(JSTracer* trc, gc::Cell* maybeOwner, Container& container, const char* name) { auto& allocPolicy = container.allocPolicy(); allocPolicy.updateOwningGCThing(maybeOwner); container.traceOwnedAllocs([&](auto** ptrp) { allocPolicy.traceOwnedAlloc(trc, maybeOwner, ptrp, name); }); } // For containers implementing |traceOwnedAllocs| get the total size of owned // allocations. template size_t SizeOfOwnedAllocs(Container& container, mozilla::MallocSizeOf mallocSizeOf) { size_t size = 0; auto& allocPolicy = container.allocPolicy(); container.traceOwnedAllocs([&](auto** ptrp) { size += allocPolicy.getAllocSize(*ptrp, mallocSizeOf); }); return size; } // Base class for allocation policies that allocate using a specified malloc // arena. class MallocArenaAllocPolicyBase : public AllocPolicyBase { public: template T* maybe_pod_arena_malloc(arena_id_t arenaId, size_t numElems) { return js_pod_arena_malloc(arenaId, numElems); } template T* maybe_pod_arena_calloc(arena_id_t arenaId, size_t numElems) { return js_pod_arena_calloc(arenaId, numElems); } template T* maybe_pod_arena_realloc(arena_id_t arenaId, T* p, size_t oldSize, size_t newSize) { return js_pod_arena_realloc(arenaId, p, oldSize, newSize); } template T* pod_arena_malloc(arena_id_t arenaId, size_t numElems) { return maybe_pod_arena_malloc(arenaId, numElems); } template T* pod_arena_calloc(arena_id_t arenaId, size_t numElems) { return maybe_pod_arena_calloc(arenaId, numElems); } template T* pod_arena_realloc(arena_id_t arenaId, T* p, size_t oldSize, size_t newSize) { return maybe_pod_arena_realloc(arenaId, p, oldSize, newSize); } }; // Base class for allocation policies providing allocation methods using the // default malloc arena. class MallocAllocPolicyBase : public MallocArenaAllocPolicyBase { public: template T* maybe_pod_malloc(size_t numElems) { return maybe_pod_arena_malloc(js::MallocArena, numElems); } template T* maybe_pod_calloc(size_t numElems) { return maybe_pod_arena_calloc(js::MallocArena, numElems); } template T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) { return maybe_pod_arena_realloc(js::MallocArena, p, oldSize, newSize); } template T* pod_malloc(size_t numElems) { return pod_arena_malloc(js::MallocArena, numElems); } template T* pod_calloc(size_t numElems) { return pod_arena_calloc(js::MallocArena, numElems); } template T* pod_realloc(T* p, size_t oldSize, size_t newSize) { return pod_arena_realloc(js::MallocArena, p, oldSize, newSize); } template void free_(T* p, size_t numElems = 0) { js_free(p); } }; /* * Base class allocation policies providing allocation methods for allocations * off the main thread. */ class BackgroundAllocPolicyBase : public MallocArenaAllocPolicyBase { public: template T* maybe_pod_malloc(size_t numElems) { return maybe_pod_arena_malloc(js::BackgroundMallocArena, numElems); } template T* maybe_pod_calloc(size_t numElems) { return maybe_pod_arena_calloc(js::BackgroundMallocArena, numElems); } template T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) { return maybe_pod_arena_realloc(js::BackgroundMallocArena, p, oldSize, newSize); } template T* pod_malloc(size_t numElems) { return pod_arena_malloc(js::BackgroundMallocArena, numElems); } template T* pod_calloc(size_t numElems) { return pod_arena_calloc(js::BackgroundMallocArena, numElems); } template T* pod_realloc(T* p, size_t oldSize, size_t newSize) { return pod_arena_realloc(js::BackgroundMallocArena, p, oldSize, newSize); } template void free_(T* p, size_t numElems = 0) { js_free(p); } }; /* Policy for using system memory functions and doing no error reporting. */ class SystemAllocPolicy : public MallocAllocPolicyBase {}; class BackgroundSystemAllocPolicy : public BackgroundAllocPolicyBase {}; MOZ_COLD JS_PUBLIC_API void ReportOutOfMemory(JSContext* cx); MOZ_COLD JS_PUBLIC_API void ReportOutOfMemory(FrontendContext* fc); // An out of memory condition which is easily user generatable and should // be specially handled to try and avoid a tab crash. MOZ_COLD JS_PUBLIC_API void ReportLargeOutOfMemory(JSContext* cx); /* * Allocation policy that calls the system memory functions and reports errors * to the context. Since the JSContext given on construction is stored for * the lifetime of the container, this policy may only be used for containers * whose lifetime is a shorter than the given JSContext. * * FIXME bug 647103 - rewrite this in terms of temporary allocation functions, * not the system ones. */ class JS_PUBLIC_API TempAllocPolicy : public MallocAllocPolicyBase { // Type tag for context_bits_ static constexpr uintptr_t JsContextTag = 0x1; // Either a JSContext* (if JsContextTag is set), or FrontendContext* uintptr_t const context_bits_; MOZ_ALWAYS_INLINE bool hasJSContext() const { return (context_bits_ & JsContextTag) == JsContextTag; } MOZ_ALWAYS_INLINE JSContext* cx() const { MOZ_ASSERT(hasJSContext()); return reinterpret_cast(context_bits_ ^ JsContextTag); } MOZ_ALWAYS_INLINE FrontendContext* fc() const { MOZ_ASSERT(!hasJSContext()); return reinterpret_cast(context_bits_); } /* * Non-inline helper to call JSRuntime::onOutOfMemory with minimal * code bloat. */ void* onOutOfMemory(arena_id_t arenaId, AllocFunction allocFunc, size_t nbytes, void* reallocPtr = nullptr); template T* onOutOfMemoryTyped(arena_id_t arenaId, AllocFunction allocFunc, size_t numElems, void* reallocPtr = nullptr) { size_t bytes; if (MOZ_UNLIKELY(!CalculateAllocSize(numElems, &bytes))) { return nullptr; } return static_cast( onOutOfMemory(arenaId, allocFunc, bytes, reallocPtr)); } public: MOZ_IMPLICIT TempAllocPolicy(JSContext* cx) : context_bits_(uintptr_t(cx) | JsContextTag) { MOZ_ASSERT((uintptr_t(cx) & JsContextTag) == 0); } MOZ_IMPLICIT TempAllocPolicy(FrontendContext* fc) : context_bits_(uintptr_t(fc)) { MOZ_ASSERT((uintptr_t(fc) & JsContextTag) == 0); } template T* pod_arena_malloc(arena_id_t arenaId, size_t numElems) { T* p = this->maybe_pod_arena_malloc(arenaId, numElems); if (MOZ_UNLIKELY(!p)) { p = onOutOfMemoryTyped(arenaId, AllocFunction::Malloc, numElems); } return p; } template T* pod_arena_calloc(arena_id_t arenaId, size_t numElems) { T* p = this->maybe_pod_arena_calloc(arenaId, numElems); if (MOZ_UNLIKELY(!p)) { p = onOutOfMemoryTyped(arenaId, AllocFunction::Calloc, numElems); } return p; } template T* pod_arena_realloc(arena_id_t arenaId, T* prior, size_t oldSize, size_t newSize) { T* p2 = this->maybe_pod_arena_realloc(arenaId, prior, oldSize, newSize); if (MOZ_UNLIKELY(!p2)) { p2 = onOutOfMemoryTyped(arenaId, AllocFunction::Realloc, newSize, prior); } return p2; } template T* pod_malloc(size_t numElems) { return pod_arena_malloc(js::MallocArena, numElems); } template T* pod_calloc(size_t numElems) { return pod_arena_calloc(js::MallocArena, numElems); } template T* pod_realloc(T* prior, size_t oldSize, size_t newSize) { return pod_arena_realloc(js::MallocArena, prior, oldSize, newSize); } template void free_(T* p, size_t numElems = 0) { js_free(p); } void reportAllocOverflow() const; bool checkSimulatedOOM() const { if (js::oom::ShouldFailWithOOM()) { if (hasJSContext()) { ReportOutOfMemory(cx()); } else { ReportOutOfMemory(fc()); } return false; } return true; } }; /* * A replacement for mozilla::MallocAllocPolicy that allocates in the JS heap * and adds no extra behaviours. * * This is currently used for allocating source buffers for parsing. Since these * are temporary and will not be freed by GC, the memory is not tracked by the * usual accounting. */ class MallocAllocPolicy : public MallocAllocPolicyBase { public: // Simulated OOM is not supported. [[nodiscard]] bool checkSimulatedOOM() const { return true; } }; } /* namespace js */ class JSInfallibleAllocPolicy : public js::AllocPolicyBase, public ::InfallibleAllocPolicy { public: using ::InfallibleAllocPolicy::reportAllocOverflow; // Simulated OOM is not supported. using ::InfallibleAllocPolicy::checkSimulatedOOM; }; #endif /* js_AllocPolicy_h */