1 // Copyright 2011 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 #include <algorithm>
6
7 #include "src/v8.h"
8
9 #include "src/base/atomicops.h"
10 #include "src/counters.h"
11 #include "src/heap/store-buffer-inl.h"
12
13 namespace v8 {
14 namespace internal {
15
StoreBuffer(Heap * heap)16 StoreBuffer::StoreBuffer(Heap* heap)
17 : heap_(heap),
18 start_(NULL),
19 limit_(NULL),
20 old_start_(NULL),
21 old_limit_(NULL),
22 old_top_(NULL),
23 old_reserved_limit_(NULL),
24 old_buffer_is_sorted_(false),
25 old_buffer_is_filtered_(false),
26 during_gc_(false),
27 store_buffer_rebuilding_enabled_(false),
28 callback_(NULL),
29 may_move_store_buffer_entries_(true),
30 virtual_memory_(NULL),
31 hash_set_1_(NULL),
32 hash_set_2_(NULL),
33 hash_sets_are_empty_(true) {}
34
35
SetUp()36 void StoreBuffer::SetUp() {
37 virtual_memory_ = new base::VirtualMemory(kStoreBufferSize * 3);
38 uintptr_t start_as_int =
39 reinterpret_cast<uintptr_t>(virtual_memory_->address());
40 start_ =
41 reinterpret_cast<Address*>(RoundUp(start_as_int, kStoreBufferSize * 2));
42 limit_ = start_ + (kStoreBufferSize / kPointerSize);
43
44 old_virtual_memory_ =
45 new base::VirtualMemory(kOldStoreBufferLength * kPointerSize);
46 old_top_ = old_start_ =
47 reinterpret_cast<Address*>(old_virtual_memory_->address());
48 // Don't know the alignment requirements of the OS, but it is certainly not
49 // less than 0xfff.
50 DCHECK((reinterpret_cast<uintptr_t>(old_start_) & 0xfff) == 0);
51 int initial_length =
52 static_cast<int>(base::OS::CommitPageSize() / kPointerSize);
53 DCHECK(initial_length > 0);
54 DCHECK(initial_length <= kOldStoreBufferLength);
55 old_limit_ = old_start_ + initial_length;
56 old_reserved_limit_ = old_start_ + kOldStoreBufferLength;
57
58 CHECK(old_virtual_memory_->Commit(reinterpret_cast<void*>(old_start_),
59 (old_limit_ - old_start_) * kPointerSize,
60 false));
61
62 DCHECK(reinterpret_cast<Address>(start_) >= virtual_memory_->address());
63 DCHECK(reinterpret_cast<Address>(limit_) >= virtual_memory_->address());
64 Address* vm_limit = reinterpret_cast<Address*>(
65 reinterpret_cast<char*>(virtual_memory_->address()) +
66 virtual_memory_->size());
67 DCHECK(start_ <= vm_limit);
68 DCHECK(limit_ <= vm_limit);
69 USE(vm_limit);
70 DCHECK((reinterpret_cast<uintptr_t>(limit_) & kStoreBufferOverflowBit) != 0);
71 DCHECK((reinterpret_cast<uintptr_t>(limit_ - 1) & kStoreBufferOverflowBit) ==
72 0);
73
74 CHECK(virtual_memory_->Commit(reinterpret_cast<Address>(start_),
75 kStoreBufferSize,
76 false)); // Not executable.
77 heap_->public_set_store_buffer_top(start_);
78
79 hash_set_1_ = new uintptr_t[kHashSetLength];
80 hash_set_2_ = new uintptr_t[kHashSetLength];
81 hash_sets_are_empty_ = false;
82
83 ClearFilteringHashSets();
84 }
85
86
TearDown()87 void StoreBuffer::TearDown() {
88 delete virtual_memory_;
89 delete old_virtual_memory_;
90 delete[] hash_set_1_;
91 delete[] hash_set_2_;
92 old_start_ = old_top_ = old_limit_ = old_reserved_limit_ = NULL;
93 start_ = limit_ = NULL;
94 heap_->public_set_store_buffer_top(start_);
95 }
96
97
StoreBufferOverflow(Isolate * isolate)98 void StoreBuffer::StoreBufferOverflow(Isolate* isolate) {
99 isolate->heap()->store_buffer()->Compact();
100 isolate->counters()->store_buffer_overflows()->Increment();
101 }
102
103
Uniq()104 void StoreBuffer::Uniq() {
105 // Remove adjacent duplicates and cells that do not point at new space.
106 Address previous = NULL;
107 Address* write = old_start_;
108 DCHECK(may_move_store_buffer_entries_);
109 for (Address* read = old_start_; read < old_top_; read++) {
110 Address current = *read;
111 if (current != previous) {
112 if (heap_->InNewSpace(*reinterpret_cast<Object**>(current))) {
113 *write++ = current;
114 }
115 }
116 previous = current;
117 }
118 old_top_ = write;
119 }
120
121
SpaceAvailable(intptr_t space_needed)122 bool StoreBuffer::SpaceAvailable(intptr_t space_needed) {
123 return old_limit_ - old_top_ >= space_needed;
124 }
125
126
EnsureSpace(intptr_t space_needed)127 void StoreBuffer::EnsureSpace(intptr_t space_needed) {
128 while (old_limit_ - old_top_ < space_needed &&
129 old_limit_ < old_reserved_limit_) {
130 size_t grow = old_limit_ - old_start_; // Double size.
131 CHECK(old_virtual_memory_->Commit(reinterpret_cast<void*>(old_limit_),
132 grow * kPointerSize, false));
133 old_limit_ += grow;
134 }
135
136 if (SpaceAvailable(space_needed)) return;
137
138 if (old_buffer_is_filtered_) return;
139 DCHECK(may_move_store_buffer_entries_);
140 Compact();
141
142 old_buffer_is_filtered_ = true;
143 bool page_has_scan_on_scavenge_flag = false;
144
145 PointerChunkIterator it(heap_);
146 MemoryChunk* chunk;
147 while ((chunk = it.next()) != NULL) {
148 if (chunk->scan_on_scavenge()) {
149 page_has_scan_on_scavenge_flag = true;
150 break;
151 }
152 }
153
154 if (page_has_scan_on_scavenge_flag) {
155 Filter(MemoryChunk::SCAN_ON_SCAVENGE);
156 }
157
158 if (SpaceAvailable(space_needed)) return;
159
160 // Sample 1 entry in 97 and filter out the pages where we estimate that more
161 // than 1 in 8 pointers are to new space.
162 static const int kSampleFinenesses = 5;
163 static const struct Samples {
164 int prime_sample_step;
165 int threshold;
166 } samples[kSampleFinenesses] = {
167 {97, ((Page::kPageSize / kPointerSize) / 97) / 8},
168 {23, ((Page::kPageSize / kPointerSize) / 23) / 16},
169 {7, ((Page::kPageSize / kPointerSize) / 7) / 32},
170 {3, ((Page::kPageSize / kPointerSize) / 3) / 256},
171 {1, 0}};
172 for (int i = 0; i < kSampleFinenesses; i++) {
173 ExemptPopularPages(samples[i].prime_sample_step, samples[i].threshold);
174 // As a last resort we mark all pages as being exempt from the store buffer.
175 DCHECK(i != (kSampleFinenesses - 1) || old_top_ == old_start_);
176 if (SpaceAvailable(space_needed)) return;
177 }
178 UNREACHABLE();
179 }
180
181
182 // Sample the store buffer to see if some pages are taking up a lot of space
183 // in the store buffer.
ExemptPopularPages(int prime_sample_step,int threshold)184 void StoreBuffer::ExemptPopularPages(int prime_sample_step, int threshold) {
185 PointerChunkIterator it(heap_);
186 MemoryChunk* chunk;
187 while ((chunk = it.next()) != NULL) {
188 chunk->set_store_buffer_counter(0);
189 }
190 bool created_new_scan_on_scavenge_pages = false;
191 MemoryChunk* previous_chunk = NULL;
192 for (Address* p = old_start_; p < old_top_; p += prime_sample_step) {
193 Address addr = *p;
194 MemoryChunk* containing_chunk = NULL;
195 if (previous_chunk != NULL && previous_chunk->Contains(addr)) {
196 containing_chunk = previous_chunk;
197 } else {
198 containing_chunk = MemoryChunk::FromAnyPointerAddress(heap_, addr);
199 }
200 int old_counter = containing_chunk->store_buffer_counter();
201 if (old_counter >= threshold) {
202 containing_chunk->set_scan_on_scavenge(true);
203 created_new_scan_on_scavenge_pages = true;
204 }
205 containing_chunk->set_store_buffer_counter(old_counter + 1);
206 previous_chunk = containing_chunk;
207 }
208 if (created_new_scan_on_scavenge_pages) {
209 Filter(MemoryChunk::SCAN_ON_SCAVENGE);
210 }
211 old_buffer_is_filtered_ = true;
212 }
213
214
Filter(int flag)215 void StoreBuffer::Filter(int flag) {
216 Address* new_top = old_start_;
217 MemoryChunk* previous_chunk = NULL;
218 for (Address* p = old_start_; p < old_top_; p++) {
219 Address addr = *p;
220 MemoryChunk* containing_chunk = NULL;
221 if (previous_chunk != NULL && previous_chunk->Contains(addr)) {
222 containing_chunk = previous_chunk;
223 } else {
224 containing_chunk = MemoryChunk::FromAnyPointerAddress(heap_, addr);
225 previous_chunk = containing_chunk;
226 }
227 if (!containing_chunk->IsFlagSet(flag)) {
228 *new_top++ = addr;
229 }
230 }
231 old_top_ = new_top;
232
233 // Filtering hash sets are inconsistent with the store buffer after this
234 // operation.
235 ClearFilteringHashSets();
236 }
237
238
SortUniq()239 void StoreBuffer::SortUniq() {
240 Compact();
241 if (old_buffer_is_sorted_) return;
242 std::sort(old_start_, old_top_);
243 Uniq();
244
245 old_buffer_is_sorted_ = true;
246
247 // Filtering hash sets are inconsistent with the store buffer after this
248 // operation.
249 ClearFilteringHashSets();
250 }
251
252
PrepareForIteration()253 bool StoreBuffer::PrepareForIteration() {
254 Compact();
255 PointerChunkIterator it(heap_);
256 MemoryChunk* chunk;
257 bool page_has_scan_on_scavenge_flag = false;
258 while ((chunk = it.next()) != NULL) {
259 if (chunk->scan_on_scavenge()) {
260 page_has_scan_on_scavenge_flag = true;
261 break;
262 }
263 }
264
265 if (page_has_scan_on_scavenge_flag) {
266 Filter(MemoryChunk::SCAN_ON_SCAVENGE);
267 }
268
269 // Filtering hash sets are inconsistent with the store buffer after
270 // iteration.
271 ClearFilteringHashSets();
272
273 return page_has_scan_on_scavenge_flag;
274 }
275
276
277 #ifdef DEBUG
Clean()278 void StoreBuffer::Clean() {
279 ClearFilteringHashSets();
280 Uniq(); // Also removes things that no longer point to new space.
281 EnsureSpace(kStoreBufferSize / 2);
282 }
283
284
285 static Address* in_store_buffer_1_element_cache = NULL;
286
287
CellIsInStoreBuffer(Address cell_address)288 bool StoreBuffer::CellIsInStoreBuffer(Address cell_address) {
289 if (!FLAG_enable_slow_asserts) return true;
290 if (in_store_buffer_1_element_cache != NULL &&
291 *in_store_buffer_1_element_cache == cell_address) {
292 return true;
293 }
294 Address* top = reinterpret_cast<Address*>(heap_->store_buffer_top());
295 for (Address* current = top - 1; current >= start_; current--) {
296 if (*current == cell_address) {
297 in_store_buffer_1_element_cache = current;
298 return true;
299 }
300 }
301 for (Address* current = old_top_ - 1; current >= old_start_; current--) {
302 if (*current == cell_address) {
303 in_store_buffer_1_element_cache = current;
304 return true;
305 }
306 }
307 return false;
308 }
309 #endif
310
311
ClearFilteringHashSets()312 void StoreBuffer::ClearFilteringHashSets() {
313 if (!hash_sets_are_empty_) {
314 memset(reinterpret_cast<void*>(hash_set_1_), 0,
315 sizeof(uintptr_t) * kHashSetLength);
316 memset(reinterpret_cast<void*>(hash_set_2_), 0,
317 sizeof(uintptr_t) * kHashSetLength);
318 hash_sets_are_empty_ = true;
319 }
320 }
321
322
GCPrologue()323 void StoreBuffer::GCPrologue() {
324 ClearFilteringHashSets();
325 during_gc_ = true;
326 }
327
328
329 #ifdef VERIFY_HEAP
VerifyPointers(LargeObjectSpace * space)330 void StoreBuffer::VerifyPointers(LargeObjectSpace* space) {
331 LargeObjectIterator it(space);
332 for (HeapObject* object = it.Next(); object != NULL; object = it.Next()) {
333 if (object->IsFixedArray()) {
334 Address slot_address = object->address();
335 Address end = object->address() + object->Size();
336
337 while (slot_address < end) {
338 HeapObject** slot = reinterpret_cast<HeapObject**>(slot_address);
339 // When we are not in GC the Heap::InNewSpace() predicate
340 // checks that pointers which satisfy predicate point into
341 // the active semispace.
342 Object* object = reinterpret_cast<Object*>(
343 base::NoBarrier_Load(reinterpret_cast<base::AtomicWord*>(slot)));
344 heap_->InNewSpace(object);
345 slot_address += kPointerSize;
346 }
347 }
348 }
349 }
350 #endif
351
352
Verify()353 void StoreBuffer::Verify() {
354 #ifdef VERIFY_HEAP
355 VerifyPointers(heap_->lo_space());
356 #endif
357 }
358
359
GCEpilogue()360 void StoreBuffer::GCEpilogue() {
361 during_gc_ = false;
362 #ifdef VERIFY_HEAP
363 if (FLAG_verify_heap) {
364 Verify();
365 }
366 #endif
367 }
368
369
FindPointersToNewSpaceInRegion(Address start,Address end,ObjectSlotCallback slot_callback,bool clear_maps)370 void StoreBuffer::FindPointersToNewSpaceInRegion(
371 Address start, Address end, ObjectSlotCallback slot_callback,
372 bool clear_maps) {
373 for (Address slot_address = start; slot_address < end;
374 slot_address += kPointerSize) {
375 Object** slot = reinterpret_cast<Object**>(slot_address);
376 Object* object = reinterpret_cast<Object*>(
377 base::NoBarrier_Load(reinterpret_cast<base::AtomicWord*>(slot)));
378 if (heap_->InNewSpace(object)) {
379 HeapObject* heap_object = reinterpret_cast<HeapObject*>(object);
380 DCHECK(heap_object->IsHeapObject());
381 // The new space object was not promoted if it still contains a map
382 // pointer. Clear the map field now lazily.
383 if (clear_maps) ClearDeadObject(heap_object);
384 slot_callback(reinterpret_cast<HeapObject**>(slot), heap_object);
385 object = reinterpret_cast<Object*>(
386 base::NoBarrier_Load(reinterpret_cast<base::AtomicWord*>(slot)));
387 if (heap_->InNewSpace(object)) {
388 EnterDirectlyIntoStoreBuffer(slot_address);
389 }
390 }
391 }
392 }
393
394
IteratePointersInStoreBuffer(ObjectSlotCallback slot_callback,bool clear_maps)395 void StoreBuffer::IteratePointersInStoreBuffer(ObjectSlotCallback slot_callback,
396 bool clear_maps) {
397 Address* limit = old_top_;
398 old_top_ = old_start_;
399 {
400 DontMoveStoreBufferEntriesScope scope(this);
401 for (Address* current = old_start_; current < limit; current++) {
402 #ifdef DEBUG
403 Address* saved_top = old_top_;
404 #endif
405 Object** slot = reinterpret_cast<Object**>(*current);
406 Object* object = reinterpret_cast<Object*>(
407 base::NoBarrier_Load(reinterpret_cast<base::AtomicWord*>(slot)));
408 if (heap_->InFromSpace(object)) {
409 HeapObject* heap_object = reinterpret_cast<HeapObject*>(object);
410 // The new space object was not promoted if it still contains a map
411 // pointer. Clear the map field now lazily.
412 if (clear_maps) ClearDeadObject(heap_object);
413 slot_callback(reinterpret_cast<HeapObject**>(slot), heap_object);
414 object = reinterpret_cast<Object*>(
415 base::NoBarrier_Load(reinterpret_cast<base::AtomicWord*>(slot)));
416 if (heap_->InNewSpace(object)) {
417 EnterDirectlyIntoStoreBuffer(reinterpret_cast<Address>(slot));
418 }
419 }
420 DCHECK(old_top_ == saved_top + 1 || old_top_ == saved_top);
421 }
422 }
423 }
424
425
IteratePointersToNewSpace(ObjectSlotCallback slot_callback)426 void StoreBuffer::IteratePointersToNewSpace(ObjectSlotCallback slot_callback) {
427 IteratePointersToNewSpace(slot_callback, false);
428 }
429
430
IteratePointersToNewSpaceAndClearMaps(ObjectSlotCallback slot_callback)431 void StoreBuffer::IteratePointersToNewSpaceAndClearMaps(
432 ObjectSlotCallback slot_callback) {
433 IteratePointersToNewSpace(slot_callback, true);
434 }
435
436
IteratePointersToNewSpace(ObjectSlotCallback slot_callback,bool clear_maps)437 void StoreBuffer::IteratePointersToNewSpace(ObjectSlotCallback slot_callback,
438 bool clear_maps) {
439 // We do not sort or remove duplicated entries from the store buffer because
440 // we expect that callback will rebuild the store buffer thus removing
441 // all duplicates and pointers to old space.
442 bool some_pages_to_scan = PrepareForIteration();
443
444 // TODO(gc): we want to skip slots on evacuation candidates
445 // but we can't simply figure that out from slot address
446 // because slot can belong to a large object.
447 IteratePointersInStoreBuffer(slot_callback, clear_maps);
448
449 // We are done scanning all the pointers that were in the store buffer, but
450 // there may be some pages marked scan_on_scavenge that have pointers to new
451 // space that are not in the store buffer. We must scan them now. As we
452 // scan, the surviving pointers to new space will be added to the store
453 // buffer. If there are still a lot of pointers to new space then we will
454 // keep the scan_on_scavenge flag on the page and discard the pointers that
455 // were added to the store buffer. If there are not many pointers to new
456 // space left on the page we will keep the pointers in the store buffer and
457 // remove the flag from the page.
458 if (some_pages_to_scan) {
459 if (callback_ != NULL) {
460 (*callback_)(heap_, NULL, kStoreBufferStartScanningPagesEvent);
461 }
462 PointerChunkIterator it(heap_);
463 MemoryChunk* chunk;
464 while ((chunk = it.next()) != NULL) {
465 if (chunk->scan_on_scavenge()) {
466 chunk->set_scan_on_scavenge(false);
467 if (callback_ != NULL) {
468 (*callback_)(heap_, chunk, kStoreBufferScanningPageEvent);
469 }
470 if (chunk->owner() == heap_->lo_space()) {
471 LargePage* large_page = reinterpret_cast<LargePage*>(chunk);
472 HeapObject* array = large_page->GetObject();
473 DCHECK(array->IsFixedArray());
474 Address start = array->address();
475 Address end = start + array->Size();
476 FindPointersToNewSpaceInRegion(start, end, slot_callback, clear_maps);
477 } else {
478 Page* page = reinterpret_cast<Page*>(chunk);
479 PagedSpace* owner = reinterpret_cast<PagedSpace*>(page->owner());
480 if (owner == heap_->map_space()) {
481 DCHECK(page->WasSwept());
482 HeapObjectIterator iterator(page, NULL);
483 for (HeapObject* heap_object = iterator.Next(); heap_object != NULL;
484 heap_object = iterator.Next()) {
485 // We skip free space objects.
486 if (!heap_object->IsFiller()) {
487 DCHECK(heap_object->IsMap());
488 FindPointersToNewSpaceInRegion(
489 heap_object->address() + Map::kPointerFieldsBeginOffset,
490 heap_object->address() + Map::kPointerFieldsEndOffset,
491 slot_callback, clear_maps);
492 }
493 }
494 } else {
495 if (!page->SweepingCompleted()) {
496 heap_->mark_compact_collector()->SweepInParallel(page, owner);
497 if (!page->SweepingCompleted()) {
498 // We were not able to sweep that page, i.e., a concurrent
499 // sweeper thread currently owns this page.
500 // TODO(hpayer): This may introduce a huge pause here. We
501 // just care about finish sweeping of the scan on scavenge page.
502 heap_->mark_compact_collector()->EnsureSweepingCompleted();
503 }
504 }
505 CHECK(page->owner() == heap_->old_pointer_space());
506 HeapObjectIterator iterator(page, NULL);
507 for (HeapObject* heap_object = iterator.Next(); heap_object != NULL;
508 heap_object = iterator.Next()) {
509 // We iterate over objects that contain new space pointers only.
510 if (!heap_object->MayContainRawValues()) {
511 FindPointersToNewSpaceInRegion(
512 heap_object->address() + HeapObject::kHeaderSize,
513 heap_object->address() + heap_object->Size(), slot_callback,
514 clear_maps);
515 }
516 }
517 }
518 }
519 }
520 }
521 if (callback_ != NULL) {
522 (*callback_)(heap_, NULL, kStoreBufferScanningPageEvent);
523 }
524 }
525 }
526
527
Compact()528 void StoreBuffer::Compact() {
529 Address* top = reinterpret_cast<Address*>(heap_->store_buffer_top());
530
531 if (top == start_) return;
532
533 // There's no check of the limit in the loop below so we check here for
534 // the worst case (compaction doesn't eliminate any pointers).
535 DCHECK(top <= limit_);
536 heap_->public_set_store_buffer_top(start_);
537 EnsureSpace(top - start_);
538 DCHECK(may_move_store_buffer_entries_);
539 // Goes through the addresses in the store buffer attempting to remove
540 // duplicates. In the interest of speed this is a lossy operation. Some
541 // duplicates will remain. We have two hash sets with different hash
542 // functions to reduce the number of unnecessary clashes.
543 hash_sets_are_empty_ = false; // Hash sets are in use.
544 for (Address* current = start_; current < top; current++) {
545 DCHECK(!heap_->cell_space()->Contains(*current));
546 DCHECK(!heap_->code_space()->Contains(*current));
547 DCHECK(!heap_->old_data_space()->Contains(*current));
548 uintptr_t int_addr = reinterpret_cast<uintptr_t>(*current);
549 // Shift out the last bits including any tags.
550 int_addr >>= kPointerSizeLog2;
551 // The upper part of an address is basically random because of ASLR and OS
552 // non-determinism, so we use only the bits within a page for hashing to
553 // make v8's behavior (more) deterministic.
554 uintptr_t hash_addr =
555 int_addr & (Page::kPageAlignmentMask >> kPointerSizeLog2);
556 int hash1 = ((hash_addr ^ (hash_addr >> kHashSetLengthLog2)) &
557 (kHashSetLength - 1));
558 if (hash_set_1_[hash1] == int_addr) continue;
559 uintptr_t hash2 = (hash_addr - (hash_addr >> kHashSetLengthLog2));
560 hash2 ^= hash2 >> (kHashSetLengthLog2 * 2);
561 hash2 &= (kHashSetLength - 1);
562 if (hash_set_2_[hash2] == int_addr) continue;
563 if (hash_set_1_[hash1] == 0) {
564 hash_set_1_[hash1] = int_addr;
565 } else if (hash_set_2_[hash2] == 0) {
566 hash_set_2_[hash2] = int_addr;
567 } else {
568 // Rather than slowing down we just throw away some entries. This will
569 // cause some duplicates to remain undetected.
570 hash_set_1_[hash1] = int_addr;
571 hash_set_2_[hash2] = 0;
572 }
573 old_buffer_is_sorted_ = false;
574 old_buffer_is_filtered_ = false;
575 *old_top_++ = reinterpret_cast<Address>(int_addr << kPointerSizeLog2);
576 DCHECK(old_top_ <= old_limit_);
577 }
578 heap_->isolate()->counters()->store_buffer_compactions()->Increment();
579 }
580 }
581 } // namespace v8::internal
582