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