1 // Copyright 2016 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef V8_REMEMBERED_SET_H
6 #define V8_REMEMBERED_SET_H
7 
8 #include "src/assembler.h"
9 #include "src/heap/heap.h"
10 #include "src/heap/slot-set.h"
11 #include "src/heap/spaces.h"
12 
13 namespace v8 {
14 namespace internal {
15 
16 enum PointerDirection { OLD_TO_OLD, OLD_TO_NEW };
17 
18 // TODO(ulan): Investigate performance of de-templatizing this class.
19 template <PointerDirection direction>
20 class RememberedSet {
21  public:
22   // Given a page and a slot in that page, this function adds the slot to the
23   // remembered set.
Insert(MemoryChunk * chunk,Address slot_addr)24   static void Insert(MemoryChunk* chunk, Address slot_addr) {
25     DCHECK(chunk->Contains(slot_addr));
26     SlotSet* slot_set = GetSlotSet(chunk);
27     if (slot_set == nullptr) {
28       slot_set = AllocateSlotSet(chunk);
29     }
30     uintptr_t offset = slot_addr - chunk->address();
31     slot_set[offset / Page::kPageSize].Insert(offset % Page::kPageSize);
32   }
33 
34   // Given a page and a slot in that page, this function removes the slot from
35   // the remembered set.
36   // If the slot was never added, then the function does nothing.
Remove(MemoryChunk * chunk,Address slot_addr)37   static void Remove(MemoryChunk* chunk, Address slot_addr) {
38     DCHECK(chunk->Contains(slot_addr));
39     SlotSet* slot_set = GetSlotSet(chunk);
40     if (slot_set != nullptr) {
41       uintptr_t offset = slot_addr - chunk->address();
42       slot_set[offset / Page::kPageSize].Remove(offset % Page::kPageSize);
43     }
44   }
45 
46   // Given a page and a range of slots in that page, this function removes the
47   // slots from the remembered set.
RemoveRange(MemoryChunk * chunk,Address start,Address end,SlotSet::EmptyBucketMode mode)48   static void RemoveRange(MemoryChunk* chunk, Address start, Address end,
49                           SlotSet::EmptyBucketMode mode) {
50     SlotSet* slot_set = GetSlotSet(chunk);
51     if (slot_set != nullptr) {
52       uintptr_t start_offset = start - chunk->address();
53       uintptr_t end_offset = end - chunk->address();
54       DCHECK_LT(start_offset, end_offset);
55       if (end_offset < static_cast<uintptr_t>(Page::kPageSize)) {
56         slot_set->RemoveRange(static_cast<int>(start_offset),
57                               static_cast<int>(end_offset), mode);
58       } else {
59         // The large page has multiple slot sets.
60         // Compute slot set indicies for the range [start_offset, end_offset).
61         int start_chunk = static_cast<int>(start_offset / Page::kPageSize);
62         int end_chunk = static_cast<int>((end_offset - 1) / Page::kPageSize);
63         int offset_in_start_chunk =
64             static_cast<int>(start_offset % Page::kPageSize);
65         // Note that using end_offset % Page::kPageSize would be incorrect
66         // because end_offset is one beyond the last slot to clear.
67         int offset_in_end_chunk = static_cast<int>(
68             end_offset - static_cast<uintptr_t>(end_chunk) * Page::kPageSize);
69         if (start_chunk == end_chunk) {
70           slot_set[start_chunk].RemoveRange(offset_in_start_chunk,
71                                             offset_in_end_chunk, mode);
72         } else {
73           // Clear all slots from start_offset to the end of first chunk.
74           slot_set[start_chunk].RemoveRange(offset_in_start_chunk,
75                                             Page::kPageSize, mode);
76           // Clear all slots in intermediate chunks.
77           for (int i = start_chunk + 1; i < end_chunk; i++) {
78             slot_set[i].RemoveRange(0, Page::kPageSize, mode);
79           }
80           // Clear slots from the beginning of the last page to end_offset.
81           slot_set[end_chunk].RemoveRange(0, offset_in_end_chunk, mode);
82         }
83       }
84     }
85   }
86 
87   // Iterates and filters the remembered set with the given callback.
88   // The callback should take (Address slot) and return SlotCallbackResult.
89   template <typename Callback>
Iterate(Heap * heap,Callback callback)90   static void Iterate(Heap* heap, Callback callback) {
91     IterateMemoryChunks(
92         heap, [callback](MemoryChunk* chunk) { Iterate(chunk, callback); });
93   }
94 
95   // Iterates over all memory chunks that contains non-empty slot sets.
96   // The callback should take (MemoryChunk* chunk) and return void.
97   template <typename Callback>
IterateMemoryChunks(Heap * heap,Callback callback)98   static void IterateMemoryChunks(Heap* heap, Callback callback) {
99     MemoryChunkIterator it(heap);
100     MemoryChunk* chunk;
101     while ((chunk = it.next()) != nullptr) {
102       SlotSet* slots = GetSlotSet(chunk);
103       TypedSlotSet* typed_slots = GetTypedSlotSet(chunk);
104       if (slots != nullptr || typed_slots != nullptr) {
105         callback(chunk);
106       }
107     }
108   }
109 
110   // Iterates and filters the remembered set in the given memory chunk with
111   // the given callback. The callback should take (Address slot) and return
112   // SlotCallbackResult.
113   template <typename Callback>
Iterate(MemoryChunk * chunk,Callback callback)114   static void Iterate(MemoryChunk* chunk, Callback callback) {
115     SlotSet* slots = GetSlotSet(chunk);
116     if (slots != nullptr) {
117       size_t pages = (chunk->size() + Page::kPageSize - 1) / Page::kPageSize;
118       int new_count = 0;
119       for (size_t page = 0; page < pages; page++) {
120         new_count +=
121             slots[page].Iterate(callback, SlotSet::PREFREE_EMPTY_BUCKETS);
122       }
123       // Only old-to-old slot sets are released eagerly. Old-new-slot sets are
124       // released by the sweeper threads.
125       if (direction == OLD_TO_OLD && new_count == 0) {
126         chunk->ReleaseOldToOldSlots();
127       }
128     }
129   }
130 
131   // Given a page and a typed slot in that page, this function adds the slot
132   // to the remembered set.
InsertTyped(Page * page,Address host_addr,SlotType slot_type,Address slot_addr)133   static void InsertTyped(Page* page, Address host_addr, SlotType slot_type,
134                           Address slot_addr) {
135     TypedSlotSet* slot_set = GetTypedSlotSet(page);
136     if (slot_set == nullptr) {
137       AllocateTypedSlotSet(page);
138       slot_set = GetTypedSlotSet(page);
139     }
140     if (host_addr == nullptr) {
141       host_addr = page->address();
142     }
143     uintptr_t offset = slot_addr - page->address();
144     uintptr_t host_offset = host_addr - page->address();
145     DCHECK_LT(offset, static_cast<uintptr_t>(TypedSlotSet::kMaxOffset));
146     DCHECK_LT(host_offset, static_cast<uintptr_t>(TypedSlotSet::kMaxOffset));
147     slot_set->Insert(slot_type, static_cast<uint32_t>(host_offset),
148                      static_cast<uint32_t>(offset));
149   }
150 
151   // Given a page and a range of typed slots in that page, this function removes
152   // the slots from the remembered set.
RemoveRangeTyped(MemoryChunk * page,Address start,Address end)153   static void RemoveRangeTyped(MemoryChunk* page, Address start, Address end) {
154     TypedSlotSet* slots = GetTypedSlotSet(page);
155     if (slots != nullptr) {
156       slots->Iterate(
157           [start, end](SlotType slot_type, Address host_addr,
158                        Address slot_addr) {
159             return start <= slot_addr && slot_addr < end ? REMOVE_SLOT
160                                                          : KEEP_SLOT;
161           },
162           TypedSlotSet::PREFREE_EMPTY_CHUNKS);
163     }
164   }
165 
166   // Iterates and filters the remembered set with the given callback.
167   // The callback should take (SlotType slot_type, SlotAddress slot) and return
168   // SlotCallbackResult.
169   template <typename Callback>
IterateTyped(Heap * heap,Callback callback)170   static void IterateTyped(Heap* heap, Callback callback) {
171     IterateMemoryChunks(heap, [callback](MemoryChunk* chunk) {
172       IterateTyped(chunk, callback);
173     });
174   }
175 
176   // Iterates and filters typed old to old pointers in the given memory chunk
177   // with the given callback. The callback should take (SlotType slot_type,
178   // Address slot_addr) and return SlotCallbackResult.
179   template <typename Callback>
IterateTyped(MemoryChunk * chunk,Callback callback)180   static void IterateTyped(MemoryChunk* chunk, Callback callback) {
181     TypedSlotSet* slots = GetTypedSlotSet(chunk);
182     if (slots != nullptr) {
183       int new_count = slots->Iterate(callback, TypedSlotSet::KEEP_EMPTY_CHUNKS);
184       if (new_count == 0) {
185         ReleaseTypedSlotSet(chunk);
186       }
187     }
188   }
189 
190   // Clear all old to old slots from the remembered set.
ClearAll(Heap * heap)191   static void ClearAll(Heap* heap) {
192     STATIC_ASSERT(direction == OLD_TO_OLD);
193     MemoryChunkIterator it(heap);
194     MemoryChunk* chunk;
195     while ((chunk = it.next()) != nullptr) {
196       chunk->ReleaseOldToOldSlots();
197       chunk->ReleaseTypedOldToOldSlots();
198     }
199   }
200 
201   // Eliminates all stale slots from the remembered set, i.e.
202   // slots that are not part of live objects anymore. This method must be
203   // called after marking, when the whole transitive closure is known and
204   // must be called before sweeping when mark bits are still intact.
205   static void ClearInvalidTypedSlots(Heap* heap, MemoryChunk* chunk);
206 
207  private:
GetSlotSet(MemoryChunk * chunk)208   static SlotSet* GetSlotSet(MemoryChunk* chunk) {
209     if (direction == OLD_TO_OLD) {
210       return chunk->old_to_old_slots();
211     } else {
212       return chunk->old_to_new_slots();
213     }
214   }
215 
GetTypedSlotSet(MemoryChunk * chunk)216   static TypedSlotSet* GetTypedSlotSet(MemoryChunk* chunk) {
217     if (direction == OLD_TO_OLD) {
218       return chunk->typed_old_to_old_slots();
219     } else {
220       return chunk->typed_old_to_new_slots();
221     }
222   }
223 
ReleaseTypedSlotSet(MemoryChunk * chunk)224   static void ReleaseTypedSlotSet(MemoryChunk* chunk) {
225     if (direction == OLD_TO_OLD) {
226       chunk->ReleaseTypedOldToOldSlots();
227     }
228   }
229 
AllocateSlotSet(MemoryChunk * chunk)230   static SlotSet* AllocateSlotSet(MemoryChunk* chunk) {
231     if (direction == OLD_TO_OLD) {
232       chunk->AllocateOldToOldSlots();
233       return chunk->old_to_old_slots();
234     } else {
235       chunk->AllocateOldToNewSlots();
236       return chunk->old_to_new_slots();
237     }
238   }
239 
AllocateTypedSlotSet(MemoryChunk * chunk)240   static TypedSlotSet* AllocateTypedSlotSet(MemoryChunk* chunk) {
241     if (direction == OLD_TO_OLD) {
242       chunk->AllocateTypedOldToOldSlots();
243       return chunk->typed_old_to_old_slots();
244     } else {
245       chunk->AllocateTypedOldToNewSlots();
246       return chunk->typed_old_to_new_slots();
247     }
248   }
249 
250   static bool IsValidSlot(Heap* heap, MemoryChunk* chunk, Object** slot);
251 };
252 
253 class UpdateTypedSlotHelper {
254  public:
255   // Updates a cell slot using an untyped slot callback.
256   // The callback accepts (Heap*, Object**) and returns SlotCallbackResult.
257   template <typename Callback>
UpdateCell(RelocInfo * rinfo,Callback callback)258   static SlotCallbackResult UpdateCell(RelocInfo* rinfo, Callback callback) {
259     DCHECK(rinfo->rmode() == RelocInfo::CELL);
260     Object* cell = rinfo->target_cell();
261     Object* old_cell = cell;
262     SlotCallbackResult result = callback(&cell);
263     if (cell != old_cell) {
264       rinfo->set_target_cell(reinterpret_cast<Cell*>(cell));
265     }
266     return result;
267   }
268 
269   // Updates a code entry slot using an untyped slot callback.
270   // The callback accepts (Heap*, Object**) and returns SlotCallbackResult.
271   template <typename Callback>
UpdateCodeEntry(Address entry_address,Callback callback)272   static SlotCallbackResult UpdateCodeEntry(Address entry_address,
273                                             Callback callback) {
274     Object* code = Code::GetObjectFromEntryAddress(entry_address);
275     Object* old_code = code;
276     SlotCallbackResult result = callback(&code);
277     if (code != old_code) {
278       Memory::Address_at(entry_address) =
279           reinterpret_cast<Code*>(code)->entry();
280     }
281     return result;
282   }
283 
284   // Updates a code target slot using an untyped slot callback.
285   // The callback accepts (Heap*, Object**) and returns SlotCallbackResult.
286   template <typename Callback>
UpdateCodeTarget(RelocInfo * rinfo,Callback callback)287   static SlotCallbackResult UpdateCodeTarget(RelocInfo* rinfo,
288                                              Callback callback) {
289     DCHECK(RelocInfo::IsCodeTarget(rinfo->rmode()));
290     Object* target = Code::GetCodeFromTargetAddress(rinfo->target_address());
291     Object* old_target = target;
292     SlotCallbackResult result = callback(&target);
293     if (target != old_target) {
294       rinfo->set_target_address(Code::cast(target)->instruction_start());
295     }
296     return result;
297   }
298 
299   // Updates an embedded pointer slot using an untyped slot callback.
300   // The callback accepts (Heap*, Object**) and returns SlotCallbackResult.
301   template <typename Callback>
UpdateEmbeddedPointer(RelocInfo * rinfo,Callback callback)302   static SlotCallbackResult UpdateEmbeddedPointer(RelocInfo* rinfo,
303                                                   Callback callback) {
304     DCHECK(rinfo->rmode() == RelocInfo::EMBEDDED_OBJECT);
305     Object* target = rinfo->target_object();
306     Object* old_target = target;
307     SlotCallbackResult result = callback(&target);
308     if (target != old_target) {
309       rinfo->set_target_object(target);
310     }
311     return result;
312   }
313 
314   // Updates a debug target slot using an untyped slot callback.
315   // The callback accepts (Heap*, Object**) and returns SlotCallbackResult.
316   template <typename Callback>
UpdateDebugTarget(RelocInfo * rinfo,Callback callback)317   static SlotCallbackResult UpdateDebugTarget(RelocInfo* rinfo,
318                                               Callback callback) {
319     DCHECK(RelocInfo::IsDebugBreakSlot(rinfo->rmode()) &&
320            rinfo->IsPatchedDebugBreakSlotSequence());
321     Object* target =
322         Code::GetCodeFromTargetAddress(rinfo->debug_call_address());
323     SlotCallbackResult result = callback(&target);
324     rinfo->set_debug_call_address(Code::cast(target)->instruction_start());
325     return result;
326   }
327 
328   // Updates a typed slot using an untyped slot callback.
329   // The callback accepts (Heap*, Object**) and returns SlotCallbackResult.
330   template <typename Callback>
UpdateTypedSlot(Isolate * isolate,SlotType slot_type,Address addr,Callback callback)331   static SlotCallbackResult UpdateTypedSlot(Isolate* isolate,
332                                             SlotType slot_type, Address addr,
333                                             Callback callback) {
334     switch (slot_type) {
335       case CODE_TARGET_SLOT: {
336         RelocInfo rinfo(isolate, addr, RelocInfo::CODE_TARGET, 0, NULL);
337         return UpdateCodeTarget(&rinfo, callback);
338       }
339       case CELL_TARGET_SLOT: {
340         RelocInfo rinfo(isolate, addr, RelocInfo::CELL, 0, NULL);
341         return UpdateCell(&rinfo, callback);
342       }
343       case CODE_ENTRY_SLOT: {
344         return UpdateCodeEntry(addr, callback);
345       }
346       case DEBUG_TARGET_SLOT: {
347         RelocInfo rinfo(isolate, addr, RelocInfo::DEBUG_BREAK_SLOT_AT_POSITION,
348                         0, NULL);
349         if (rinfo.IsPatchedDebugBreakSlotSequence()) {
350           return UpdateDebugTarget(&rinfo, callback);
351         }
352         return REMOVE_SLOT;
353       }
354       case EMBEDDED_OBJECT_SLOT: {
355         RelocInfo rinfo(isolate, addr, RelocInfo::EMBEDDED_OBJECT, 0, NULL);
356         return UpdateEmbeddedPointer(&rinfo, callback);
357       }
358       case OBJECT_SLOT: {
359         return callback(reinterpret_cast<Object**>(addr));
360       }
361       case CLEARED_SLOT:
362         break;
363     }
364     UNREACHABLE();
365     return REMOVE_SLOT;
366   }
367 };
368 
SlotTypeForRelocInfoMode(RelocInfo::Mode rmode)369 inline SlotType SlotTypeForRelocInfoMode(RelocInfo::Mode rmode) {
370   if (RelocInfo::IsCodeTarget(rmode)) {
371     return CODE_TARGET_SLOT;
372   } else if (RelocInfo::IsCell(rmode)) {
373     return CELL_TARGET_SLOT;
374   } else if (RelocInfo::IsEmbeddedObject(rmode)) {
375     return EMBEDDED_OBJECT_SLOT;
376   } else if (RelocInfo::IsDebugBreakSlot(rmode)) {
377     return DEBUG_TARGET_SLOT;
378   }
379   UNREACHABLE();
380   return CLEARED_SLOT;
381 }
382 
383 }  // namespace internal
384 }  // namespace v8
385 
386 #endif  // V8_REMEMBERED_SET_H
387