summaryrefslogtreecommitdiffstats
path: root/js/src/gc/MallocedBlockCache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/gc/MallocedBlockCache.cpp')
-rw-r--r--js/src/gc/MallocedBlockCache.cpp144
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;
+}