/src/hermes/include/hermes/VM/CardTableNC.h
Line | Count | Source |
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 | | #ifndef HERMES_VM_CARDTABLE_H |
9 | | #define HERMES_VM_CARDTABLE_H |
10 | | |
11 | | #include "hermes/Support/ErrorHandling.h" |
12 | | #include "hermes/Support/OSCompat.h" |
13 | | #include "hermes/Support/OptValue.h" |
14 | | #include "hermes/VM/AlignedStorage.h" |
15 | | #include "hermes/VM/ExpectedPageSize.h" |
16 | | #include "hermes/VM/GCCell.h" |
17 | | #include "hermes/VM/PointerBase.h" |
18 | | |
19 | | #include "llvh/Support/MathExtras.h" |
20 | | |
21 | | #include <cassert> |
22 | | |
23 | | namespace hermes { |
24 | | namespace vm { |
25 | | |
26 | | /// The card table optimizes young gen collections by restricting the amount of |
27 | | /// heap belonging to the old gen that must be scanned. The card table expects |
28 | | /// to be constructed inside an AlignedHeapSegment's storage, at some position |
29 | | /// before the allocation region, and covers the extent of that storage's |
30 | | /// memory. |
31 | | /// |
32 | | /// Also supports the following query: Given a card in the heap that intersects |
33 | | /// with the used portion of its segment, find its "crossing object" -- the |
34 | | /// object whose extent [obj-start, end) contains the start of the card. This |
35 | | /// allows us to scan dirty cards: finding the crossing object allows us to then |
36 | | /// do object-to-object traversal to the end of the card. |
37 | | class CardTable { |
38 | | public: |
39 | | /// Points at the start of a card. |
40 | | class Boundary { |
41 | | friend class CardTable; |
42 | | |
43 | | public: |
44 | | Boundary() = default; |
45 | | |
46 | | /// Move boundary to the edge at the next highest address. |
47 | | inline void bump(); |
48 | | |
49 | | /// The index for the card starting at \c address(), in the table covering |
50 | | /// that address. |
51 | | inline size_t index() const; |
52 | | |
53 | | /// The (inclusive) start address of the card. |
54 | | inline const char *address() const; |
55 | | |
56 | | private: |
57 | | inline Boundary(size_t index, const char *address); |
58 | | |
59 | | size_t index_{0}; |
60 | | const char *address_{nullptr}; |
61 | | }; |
62 | | |
63 | | /// The size (and base-two log of the size) of cards used in the card table. |
64 | | static constexpr size_t kLogCardSize = 9; // ==> 512-byte cards. |
65 | | static constexpr size_t kCardSize = 1 << kLogCardSize; // ==> 512-byte cards. |
66 | | |
67 | | /// The number of valid indices into the card table. |
68 | | static constexpr size_t kValidIndices = |
69 | | AlignedStorage::size() >> kLogCardSize; |
70 | | |
71 | | /// The size of the card table. |
72 | | static constexpr size_t kCardTableSize = kValidIndices; |
73 | | |
74 | | /// For convenience, this is a conversion factor to determine how many bytes |
75 | | /// in the heap correspond to a single byte in the card table. This is |
76 | | /// distinct from kCardSize, which tells us how many bytes in the heap |
77 | | /// correspond to a single byte in the card table. However, since each index |
78 | | /// corresponds to a single byte for now, they are the same value. This is |
79 | | /// guaranteed by a static_assert below. |
80 | | static constexpr size_t kHeapBytesPerCardByte = kCardSize; |
81 | | |
82 | | /// A prefix of every segment is occupied by auxilary data |
83 | | /// structures. The card table is the first such data structure. |
84 | | /// The card table maps to the segment. Only the suffix of the card |
85 | | /// table that maps to the suffix of entire segment that is used for |
86 | | /// allocation is ever used; the prefix that maps to the card table |
87 | | /// itself is not used. (Nor is the portion that of the card table |
88 | | /// that maps to the other auxiliary data structure, the mark bit |
89 | | /// array, but we don't attempt to calculate that here.) |
90 | | /// It is useful to know the size of this unused region of |
91 | | /// the card table, so it can be used for other purposes. |
92 | | /// Note that the total size of the card table is 2 times |
93 | | /// kCardTableSize, since the CardTable contains two byte arrays of |
94 | | /// that size (cards_ and _boundaries_). |
95 | | static constexpr size_t kFirstUsedIndex = |
96 | | (2 * kCardTableSize) >> kLogCardSize; |
97 | | |
98 | 226 | CardTable() = default; |
99 | | /// CardTable is not copyable or movable: It must be constructed in-place. |
100 | | CardTable(const CardTable &) = delete; |
101 | | CardTable(CardTable &&) = delete; |
102 | | CardTable &operator=(const CardTable &) = delete; |
103 | | CardTable &operator=(CardTable &&) = delete; |
104 | | |
105 | | /// Returns the card table index corresponding to a byte at the given address. |
106 | | /// \pre \p addr must be within the bounds of the segment owning this card |
107 | | /// table or at most 1 card after it, that is to say |
108 | | /// |
109 | | /// segment.lowLim() <= addr < segment.hiLim() + kCardSize |
110 | | /// |
111 | | /// Note that we allow the extra card after the segment in order to simplify |
112 | | /// the logic for callers that are using this function to generate an open |
113 | | /// interval of card indices. See \c dirtyCardsForAddressRange for an example |
114 | | /// of how this is used. |
115 | | inline size_t addressToIndex(const void *addr) const LLVM_NO_SANITIZE("null"); |
116 | | |
117 | | /// Returns the address corresponding to the given card table |
118 | | /// index. |
119 | | /// |
120 | | /// \pre \p index is bounded: |
121 | | /// |
122 | | /// 0 <= index <= kValidIndices |
123 | | inline const char *indexToAddress(size_t index) const; |
124 | | |
125 | | /// Make the card table entry for the given address dirty. |
126 | | /// \pre \p addr is required to be an address covered by the card table. |
127 | | inline void dirtyCardForAddress(const void *addr); |
128 | | |
129 | | /// Make the card table entries for cards that intersect the given address |
130 | | /// range dirty. The range is a closed interval [low, high]. |
131 | | /// \pre \p low and \p high are required to be addresses covered by the card |
132 | | /// table. |
133 | | void dirtyCardsForAddressRange(const void *low, const void *high); |
134 | | |
135 | | /// Returns whether the card table entry for the given address is dirty. |
136 | | /// \pre \p addr is required to be an address covered by the card table. |
137 | | inline bool isCardForAddressDirty(const void *addr) const; |
138 | | |
139 | | /// Returns whether the card table entry for the given index is dirty. |
140 | | /// \pre \p index is required to be a valid card table index. |
141 | | inline bool isCardForIndexDirty(const size_t index) const; |
142 | | |
143 | | /// If there is a dirty card at or after \p fromIndex, at an index less than |
144 | | /// \p endIndex, returns the index of the dirty card, else returns none. |
145 | | inline OptValue<size_t> findNextDirtyCard(size_t fromIndex, size_t endIndex) |
146 | | const; |
147 | | |
148 | | /// If there is a card card at or after \p fromIndex, at an index less than |
149 | | /// \p endIndex, returns the index of the clean card, else returns none. |
150 | | inline OptValue<size_t> findNextCleanCard(size_t fromIndex, size_t endIndex) |
151 | | const; |
152 | | |
153 | | /// \return The first boundary that could be crossed by a suitably large |
154 | | /// allocation starting at \p level. |
155 | | inline Boundary nextBoundary(const char *level) const; |
156 | | |
157 | | /// Clears the card table. |
158 | | void clear(); |
159 | | |
160 | | /// Modify the card table to be (conservatively) valid after old generation |
161 | | /// compaction: dirty all cards containing objects after compaction (up to \p |
162 | | /// newLevel), clean all now-unoccupied cards. This keeps us from having to |
163 | | /// track what cards contain old-to-young pointers after compaction -- we |
164 | | /// assume any card might. |
165 | | /// |
166 | | /// TODO (T26751833) figure out if this is a performance problem, and do |
167 | | /// better if necessary. |
168 | | void updateAfterCompaction(const void *newLevel); |
169 | | |
170 | | /// An allocation of [start, end) has crossed at least one card boundary. |
171 | | /// Update the boundaries table appropriately to describe the allocation. |
172 | | /// |
173 | | /// \pre boundary is not null. |
174 | | /// \pre [start, end) must be covered by this table. |
175 | | /// \pre boundary's index must correspond to its address in this table. |
176 | | /// \pre [start, end) crosses \p *boundary. |
177 | | /// |
178 | | /// \post for all card indices I s.t. start <= indexToAddress(I) < end, |
179 | | /// firstObjForCard(I) == start |
180 | | /// |
181 | | /// \post *boundary == nextBoundary(end) |
182 | | void updateBoundaries(Boundary *boundary, const char *start, const char *end); |
183 | | |
184 | | /// Returns the start address of the first object that extends onto the card |
185 | | /// with the given index. (If an object is allocated at a card boundary, that |
186 | | /// is the first object.) |
187 | | GCCell *firstObjForCard(unsigned index) const; |
188 | | |
189 | | #ifdef HERMES_EXTRA_DEBUG |
190 | | /// Temporary debugging hack: yield the numeric value of the boundaries_ array |
191 | | /// for the given \p index. |
192 | | /// TODO(T48709128): remove this when the problem is diagnosed. |
193 | 0 | int8_t cardObjectTableValue(unsigned index) const { |
194 | 0 | return boundaries_[index]; |
195 | 0 | } |
196 | | |
197 | | /// These methods protect and unprotect, respectively, the memory |
198 | | /// that comprises the card boundary table. They require that the |
199 | | /// start of the boundary table is page-aligned, and its size is a |
200 | | /// multiple of the page size. |
201 | | /// TODO(T48709128): remove this when the problem is diagnosed. |
202 | | void protectBoundaryTable(); |
203 | | void unprotectBoundaryTable(); |
204 | | #endif // HERMES_EXTRA_DEBUG |
205 | | |
206 | | #ifdef HERMES_SLOW_DEBUG |
207 | | /// Asserts that for every card covering [start, level), what we believe to |
208 | | /// be its "crossing object" |
209 | | /// |
210 | | /// 1. Is valid. |
211 | | /// 2. Starts on or before the start of the card, and ends after the start of |
212 | | /// the card. |
213 | | /// |
214 | | /// \pre start is card-aligned. |
215 | | void verifyBoundaries(char *start, char *level) const; |
216 | | #endif // HERMES_SLOW_DEBUG |
217 | | |
218 | | private: |
219 | | enum class CardStatus : char { Clean = 0, Dirty = 1 }; |
220 | | |
221 | | /// \return The lowest address whose card can be dirtied in this array. i.e. |
222 | | /// The smallest address such that |
223 | | /// |
224 | | /// addressToIndex(base()) == 0 |
225 | | /// |
226 | | /// Note that the base address will be strictly less than the address |
227 | | /// corresponding to the start of the allocation region (by at least the width |
228 | | /// of the card table). |
229 | | inline const char *base() const LLVM_NO_SANITIZE("null"); |
230 | | |
231 | | /// The encoding scheme for the logarithmic-time object boundary queries for |
232 | | /// large objects. encodeExp encodes an exponent to a (negative) table value, |
233 | | /// and decodeExp is the inverse function. |
234 | | static inline int8_t encodeExp(int8_t exp); |
235 | | static inline int8_t decodeExp(int8_t encodedVal); |
236 | | |
237 | | /// Returns true iff ptr is card-aligned. |
238 | | static inline bool isCardAligned(const void *ptr); |
239 | | |
240 | | /// \return the index of the first card in the range [fromIndex, endIndex) |
241 | | /// with value \p status, or none if one does not exist. |
242 | | OptValue<size_t> findNextCardWithStatus( |
243 | | CardStatus status, |
244 | | size_t fromIndex, |
245 | | size_t endIndex) const; |
246 | | |
247 | | /// Clean, or dirty, the indicated index ranges in the card table. |
248 | | /// Ranges are half-open: [from, to). |
249 | | void cleanRange(size_t from, size_t to); |
250 | | void dirtyRange(size_t from, size_t to); |
251 | | |
252 | | void cleanOrDirtyRange(size_t from, size_t to, CardStatus cleanOrDirty); |
253 | | |
254 | | /// This needs to be atomic so that the background thread in Hades can safely |
255 | | /// dirty cards when compacting. |
256 | | std::array<AtomicIfConcurrentGC<CardStatus>, kCardTableSize> cards_{}; |
257 | | |
258 | | /// See the comment at kHeapBytesPerCardByte above to see why this is |
259 | | /// necessary. |
260 | | static_assert( |
261 | | sizeof(cards_[0]) == 1, |
262 | | "Validate assumption that card table entries are one byte"); |
263 | | |
264 | | /// Each card has a corresponding signed byte in the boundaries_ table. A |
265 | | /// non-negative entry, K, indicates that the crossing object starts K * |
266 | | /// HeapAlign bytes before the start of the card. A negative entry, L, |
267 | | /// indicates that we must seek backwards by 2^(-L-1) indices and consult the |
268 | | /// entry there. |
269 | | /// |
270 | | /// This scheme allows the start of a large object to be found in logarithmic |
271 | | /// time: If we allocate a large object that crosses many cards, the first |
272 | | /// crossed cards gets a non-negative value, and each subsequent one uses the |
273 | | /// maximum exponent that stays within the card range for the object. |
274 | | int8_t boundaries_[kCardTableSize]; |
275 | | }; |
276 | | |
277 | | /// Implementations of inlines. |
278 | 942k | inline void CardTable::Boundary::bump() { |
279 | 942k | index_++; |
280 | 942k | address_ += kCardSize; |
281 | 942k | } |
282 | | |
283 | 962k | inline size_t CardTable::Boundary::index() const { |
284 | 962k | return index_; |
285 | 962k | } |
286 | | |
287 | 1.27M | inline const char *CardTable::Boundary::address() const { |
288 | 1.27M | return address_; |
289 | 1.27M | } |
290 | | |
291 | | inline CardTable::Boundary::Boundary(size_t index, const char *address) |
292 | 258k | : index_(index), address_(address) {} |
293 | | |
294 | 1.86M | inline size_t CardTable::addressToIndex(const void *addr) const { |
295 | 1.86M | auto addrPtr = reinterpret_cast<const char *>(addr); |
296 | 1.86M | assert( |
297 | 1.86M | base() <= addrPtr && |
298 | 1.86M | addrPtr < (static_cast<const char *>(AlignedStorage::end(base())) + |
299 | 1.86M | kCardSize) && |
300 | 1.86M | "address is required to be within range."); |
301 | 1.86M | return (addrPtr - base()) >> kLogCardSize; |
302 | 1.86M | } |
303 | | |
304 | 1.92M | inline const char *CardTable::indexToAddress(size_t index) const { |
305 | 1.92M | assert(index <= kValidIndices && "index must be within the index range"); |
306 | 1.92M | const char *res = base() + (index << kLogCardSize); |
307 | 1.92M | assert( |
308 | 1.92M | base() <= res && res <= AlignedStorage::end(base()) && |
309 | 1.92M | "result must be within the covered range"); |
310 | 1.92M | return res; |
311 | 1.92M | } |
312 | | |
313 | 1.58M | inline void CardTable::dirtyCardForAddress(const void *addr) { |
314 | 1.58M | cards_[addressToIndex(addr)].store( |
315 | 1.58M | CardStatus::Dirty, std::memory_order_relaxed); |
316 | 1.58M | } |
317 | | |
318 | 695 | inline bool CardTable::isCardForAddressDirty(const void *addr) const { |
319 | 695 | return isCardForIndexDirty(addressToIndex(addr)); |
320 | 695 | } |
321 | | |
322 | 865 | inline bool CardTable::isCardForIndexDirty(size_t index) const { |
323 | 865 | assert(index < kValidIndices && "index is required to be in range."); |
324 | 865 | return cards_[index].load(std::memory_order_relaxed) == CardStatus::Dirty; |
325 | 865 | } |
326 | | |
327 | | inline OptValue<size_t> CardTable::findNextDirtyCard( |
328 | | size_t fromIndex, |
329 | 137 | size_t endIndex) const { |
330 | 137 | return findNextCardWithStatus(CardStatus::Dirty, fromIndex, endIndex); |
331 | 137 | } |
332 | | |
333 | | inline OptValue<size_t> CardTable::findNextCleanCard( |
334 | | size_t fromIndex, |
335 | 85 | size_t endIndex) const { |
336 | 85 | return findNextCardWithStatus(CardStatus::Clean, fromIndex, endIndex); |
337 | 85 | } |
338 | | |
339 | 258k | inline CardTable::Boundary CardTable::nextBoundary(const char *level) const { |
340 | 258k | assert(level != nullptr); |
341 | 258k | size_t ix = addressToIndex(level - 1) + 1; |
342 | 258k | const char *addr = indexToAddress(ix); |
343 | | |
344 | 258k | return {ix, addr}; |
345 | 258k | } |
346 | | |
347 | 11.4M | inline const char *CardTable::base() const { |
348 | | // As we know the card table is laid out inline before the allocation region |
349 | | // of its aligned heap segment, we can use its own this pointer as the base |
350 | | // address. |
351 | 11.4M | return reinterpret_cast<const char *>(this); |
352 | 11.4M | } |
353 | | |
354 | 923k | /* static */ inline int8_t CardTable::encodeExp(int8_t exp) { |
355 | 923k | return -(exp + 1); |
356 | 923k | } |
357 | | |
358 | 4.52M | /* static */ inline int8_t CardTable::decodeExp(int8_t encodedVal) { |
359 | 4.52M | return -encodedVal - 1; |
360 | 4.52M | } |
361 | | |
362 | 104 | /* static */ inline bool CardTable::isCardAligned(const void *ptr) { |
363 | 104 | return (reinterpret_cast<intptr_t>(ptr) & (kCardSize - 1)) == 0; |
364 | 104 | } |
365 | | |
366 | | } // namespace vm |
367 | | } // namespace hermes |
368 | | |
369 | | #endif // HERMES_VM_CARDTABLE_H |