summaryrefslogtreecommitdiffstats
path: root/js/src/gc/MallocedBlockCache.cpp
blob: 602d1a80b0a5a6dc95ee6e70fc6d3eb7a708c1fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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;
}