Coverage Report

Created: 2025-09-04 06:34

/src/hermes/lib/VM/gcs/CardTableNC.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) Meta Platforms, Inc. and affiliates.
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
8
#define DEBUG_TYPE "gc"
9
10
#include "hermes/VM/CardTableNC.h"
11
12
#include "hermes/Support/OSCompat.h"
13
14
#include <algorithm>
15
#include <cassert>
16
#include <cstdint>
17
#include <functional>
18
#pragma GCC diagnostic push
19
20
#ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32
21
#pragma GCC diagnostic ignored "-Wshorten-64-to-32"
22
#endif
23
namespace hermes {
24
namespace vm {
25
26
0
void CardTable::dirtyCardsForAddressRange(const void *low, const void *high) {
27
  // If high is in the middle of some card, ensure that we dirty that card.
28
0
  high = reinterpret_cast<const char *>(high) + kCardSize - 1;
29
0
  dirtyRange(addressToIndex(low), addressToIndex(high));
30
0
}
31
32
OptValue<size_t> CardTable::findNextCardWithStatus(
33
    CardStatus status,
34
    size_t fromIndex,
35
212
    size_t endIndex) const {
36
305k
  for (size_t idx = fromIndex; idx < endIndex; idx++)
37
305k
    if (cards_[idx].load(std::memory_order_relaxed) == status)
38
174
      return idx;
39
40
38
  return llvh::None;
41
212
}
42
43
38
void CardTable::clear() {
44
38
  cleanRange(kFirstUsedIndex, kValidIndices);
45
38
}
46
47
0
void CardTable::updateAfterCompaction(const void *newLevel) {
48
0
  const char *newLevelPtr = static_cast<const char *>(newLevel);
49
0
  size_t firstCleanCardIndex = addressToIndex(newLevelPtr + kCardSize - 1);
50
0
  assert(
51
0
      firstCleanCardIndex <= kValidIndices &&
52
0
      firstCleanCardIndex >= kFirstUsedIndex && "Invalid index.");
53
  // Dirty the occupied cards (below the level), and clean the cards above the
54
  // level.
55
0
  dirtyRange(kFirstUsedIndex, firstCleanCardIndex);
56
0
  cleanRange(firstCleanCardIndex, kValidIndices);
57
0
}
58
59
38
void CardTable::cleanRange(size_t from, size_t to) {
60
38
  cleanOrDirtyRange(from, to, CardStatus::Clean);
61
38
}
62
63
0
void CardTable::dirtyRange(size_t from, size_t to) {
64
0
  cleanOrDirtyRange(from, to, CardStatus::Dirty);
65
0
}
66
67
void CardTable::cleanOrDirtyRange(
68
    size_t from,
69
    size_t to,
70
38
    CardStatus cleanOrDirty) {
71
310k
  for (size_t index = from; index < to; index++) {
72
310k
    cards_[index].store(cleanOrDirty, std::memory_order_relaxed);
73
310k
  }
74
38
}
75
76
void CardTable::updateBoundaries(
77
    CardTable::Boundary *boundary,
78
    const char *start,
79
8.48k
    const char *end) {
80
8.48k
  assert(boundary != nullptr && "Need a boundary cursor");
81
8.48k
  assert(
82
8.48k
      base() <= start && end <= AlignedStorage::end(base()) &&
83
8.48k
      "Precondition: [start, end) must be covered by this table.");
84
8.48k
  assert(
85
8.48k
      boundary->index() == addressToIndex(boundary->address()) &&
86
8.48k
      "Precondition: boundary's index must correspond to its address in this table.");
87
  // We must always have just crossed the boundary of the next card:
88
8.48k
  assert(
89
8.48k
      start <= boundary->address() && boundary->address() < end &&
90
8.48k
      "Precondition: must have crossed boundary.");
91
  // The object may be large, and may cross multiple cards, but first
92
  // handle the first card.
93
8.48k
  boundaries_[boundary->index()] =
94
8.48k
      (boundary->address() - start) >> LogHeapAlign;
95
8.48k
  boundary->bump();
96
97
  // Now we must fill in the remainder of the card boundaries crossed by the
98
  // allocation.  We use a logarithmic scheme, so we fill in one card
99
  // with -1, 2 with -2, etc., where each negative value k indicates
100
  // that we should go backwards by 2^(-k - 1) cards, and consult the
101
  // table there.
102
8.48k
  int8_t currentExp = 0;
103
8.48k
  unsigned currentIndexDelta = 1;
104
8.48k
  unsigned numWithCurrentExp = 0;
105
398k
  while (boundary->address() < end) {
106
389k
    boundaries_[boundary->index()] = encodeExp(currentExp);
107
389k
    numWithCurrentExp++;
108
389k
    if (numWithCurrentExp == currentIndexDelta) {
109
3.58k
      numWithCurrentExp = 0;
110
3.58k
      currentExp++;
111
3.58k
      currentIndexDelta *= 2;
112
      // Note that 7 bits handles object sizes up to 2^128, so we
113
      // don't have to worry about overflow of the int8_t.
114
3.58k
    }
115
389k
    boundary->bump();
116
389k
  }
117
8.48k
}
118
119
609k
GCCell *CardTable::firstObjForCard(unsigned index) const {
120
609k
  int8_t val = boundaries_[index];
121
122
  // If val is negative, it means skip backwards some number of cards.
123
  // In general, for an object crossing 2^N cards, a query for one of
124
  // those cards will examine at most N entries in the table.
125
3.76M
  while (val < 0) {
126
3.15M
    index -= 1 << decodeExp(val);
127
3.15M
    val = boundaries_[index];
128
3.15M
  }
129
130
609k
  char *boundary = const_cast<char *>(indexToAddress(index));
131
609k
  char *resPtr = boundary - (val << LogHeapAlign);
132
609k
  return reinterpret_cast<GCCell *>(resPtr);
133
609k
}
134
135
#ifdef HERMES_EXTRA_DEBUG
136
static void
137
0
protectBoundaryTableWork(void *table, size_t sz, oscompat::ProtectMode mode) {
138
0
  assert((reinterpret_cast<uintptr_t>(table) % oscompat::page_size()) == 0);
139
0
  assert((sz % oscompat::page_size()) == 0);
140
0
  bool res = oscompat::vm_protect(table, sz, mode);
141
0
  (void)res;
142
0
  assert(res);
143
0
}
144
145
0
void CardTable::protectBoundaryTable() {
146
0
  protectBoundaryTableWork(
147
0
      &boundaries_[0], kValidIndices, oscompat::ProtectMode::None);
148
0
}
149
150
0
void CardTable::unprotectBoundaryTable() {
151
0
  protectBoundaryTableWork(
152
0
      &boundaries_[0], kValidIndices, oscompat::ProtectMode::ReadWrite);
153
0
}
154
#endif // HERMES_EXTRA_DEBUG
155
156
#ifdef HERMES_SLOW_DEBUG
157
76
void CardTable::verifyBoundaries(char *start, char *level) const {
158
  // Start should be card-aligned.
159
76
  assert(isCardAligned(start));
160
609k
  for (unsigned index = addressToIndex(start); index < kValidIndices; index++) {
161
609k
    const char *boundary = indexToAddress(index);
162
609k
    if (level <= boundary) {
163
0
      break;
164
0
    }
165
609k
    GCCell *cell = firstObjForCard(index);
166
    // Should be a valid cell.
167
609k
    assert(
168
609k
        cell->isValid() &&
169
609k
        "Card object boundary is broken: firstObjForCard yields invalid cell.");
170
609k
    char *cellPtr = reinterpret_cast<char *>(cell);
171
    // And it should extend across the card boundary.
172
609k
    assert(
173
609k
        cellPtr <= boundary &&
174
609k
        boundary < (cellPtr + cell->getAllocatedSize()) &&
175
609k
        "Card object boundary is broken: first obj doesn't extend into card");
176
609k
  }
177
76
}
178
#endif // HERMES_SLOW_DEBUG
179
180
} // namespace vm
181
} // namespace hermes
182
#undef DEBUG_TYPE