diff options
Diffstat (limited to 'js/src/gc/MallocedBlockCache.cpp')
-rw-r--r-- | js/src/gc/MallocedBlockCache.cpp | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/js/src/gc/MallocedBlockCache.cpp b/js/src/gc/MallocedBlockCache.cpp new file mode 100644 index 0000000000..602d1a80b0 --- /dev/null +++ b/js/src/gc/MallocedBlockCache.cpp @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sw=2 et 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/. */ + +#include "gc/MallocedBlockCache.h" +#include "mozilla/MemoryChecking.h" + +using js::PointerAndUint7; +using js::gc::MallocedBlockCache; + +MallocedBlockCache::~MallocedBlockCache() { clear(); } + +PointerAndUint7 MallocedBlockCache::alloc(size_t size) { + // Figure out which free list can give us a block of size `size`, after it + // has been rounded up to a multiple of `step`. + // + // Example mapping for STEP = 16 and NUM_LISTS = 8, after rounding up: + // 0 never holds any blocks (denotes "too large") + // 1 holds blocks of size 16 + // 2 holds blocks of size 32 + // 3 holds blocks of size 48 + // 4 holds blocks of size 64 + // 5 holds blocks of size 80 + // 6 holds blocks of size 96 + // 7 holds blocks of size 112 + // + // For a request of size n: + // * if n == 0, fail + // * else + // round n up to a multiple of STEP + // let i = n / STEP + // if i >= NUM_LISTS + // alloc direct from js_malloc, and return listID = 0 + // if lists[i] is nonempty, use lists[i] and return listID = i. + // else + // let p = js_malloc(n) + // return p and listID = i. + + // We're never expected to handle zero-sized blocks. + MOZ_ASSERT(size > 0); + + size = js::RoundUp(size, STEP); + size_t i = size / STEP; + + // Too large to cache; go straight to js_malloc. + if (MOZ_UNLIKELY(i >= NUM_LISTS)) { + void* p = js_malloc(size); + // If p is nullptr, that fact is carried into the PointerAndUint7, and the + // caller is expected to check that. + return PointerAndUint7(p, OVERSIZE_BLOCK_LIST_ID); + } + + // The case we hope is common. First, see if we can pull a block from the + // relevant list. + MOZ_ASSERT(i >= 1 && i < NUM_LISTS); + // Check that i is the right list + MOZ_ASSERT(i * STEP == size); + if (MOZ_LIKELY(!lists[i].empty())) { + void* block = lists[i].popCopy(); + return PointerAndUint7(block, i); + } + + // No luck. + void* p = js_malloc(size); + if (MOZ_UNLIKELY(!p)) { + return PointerAndUint7(nullptr, 0); // OOM + } + return PointerAndUint7(p, i); +} + +void MallocedBlockCache::free(PointerAndUint7 blockAndListID) { + // This is a whole lot simpler than the ::alloc case, since we are given the + // listId and don't have to compute it (not that we have any way to). + void* block = blockAndListID.pointer(); + uint32_t listID = blockAndListID.uint7(); + MOZ_ASSERT(block); + MOZ_ASSERT(listID < NUM_LISTS); + if (MOZ_UNLIKELY(listID == OVERSIZE_BLOCK_LIST_ID)) { + // It was too large for recycling; go straight to js_free. + js_free(block); + return; + } + + // Put it back on list `listId`, first poisoning it for safety. + memset(block, JS_NOTINUSE_TRAILER_PATTERN, listID * STEP); + MOZ_MAKE_MEM_UNDEFINED(block, listID * STEP); + if (MOZ_UNLIKELY(!lists[listID].append(block))) { + // OOM'd while doing admin. Hand it off to js_free and forget about the + // OOM. + js_free(block); + } +} + +void MallocedBlockCache::preen(float percentOfBlocksToDiscard) { + MOZ_ASSERT(percentOfBlocksToDiscard >= 0.0 && + percentOfBlocksToDiscard <= 100.0); + MOZ_ASSERT(lists[OVERSIZE_BLOCK_LIST_ID].empty()); + for (size_t listID = 1; listID < NUM_LISTS; listID++) { + MallocedBlockVector& list = lists[listID]; + size_t numToFree = + size_t(float(list.length()) * (percentOfBlocksToDiscard / 100.0)); + MOZ_RELEASE_ASSERT(numToFree <= list.length()); + while (numToFree > 0) { + void* block = list.popCopy(); + MOZ_ASSERT(block); + js_free(block); + numToFree--; + } + } +} + +void MallocedBlockCache::clear() { + MOZ_ASSERT(lists[OVERSIZE_BLOCK_LIST_ID].empty()); + for (size_t i = 1; i < NUM_LISTS; i++) { + MallocedBlockVector& list = lists[i]; + for (size_t j = 0; j < list.length(); j++) { + MOZ_ASSERT(list[j]); + js_free(list[j]); + list[j] = nullptr; // for safety + } + list.clear(); + } +} + +size_t MallocedBlockCache::sizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf) const { + MOZ_ASSERT(lists[OVERSIZE_BLOCK_LIST_ID].empty()); + size_t nBytes = 0; + for (size_t listID = 0; listID < NUM_LISTS; listID++) { + const MallocedBlockVector& list = lists[listID]; + nBytes += list.sizeOfExcludingThis(mallocSizeOf); + // The payload size of each block in `list` is the same. Hence, we could + // possibly do better here (measure once and multiply by the length) if we + // believe that the metadata size for each block is also the same. + for (size_t i = 0; i < list.length(); i++) { + MOZ_ASSERT(list[i]); + nBytes += mallocSizeOf(list[i]); + } + } + return nBytes; +} |