1 // Copyright 2015 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 "src/heap/scavenger.h"
6
7 #include "src/common/globals.h"
8 #include "src/handles/global-handles.h"
9 #include "src/heap/array-buffer-sweeper.h"
10 #include "src/heap/concurrent-allocator.h"
11 #include "src/heap/gc-tracer-inl.h"
12 #include "src/heap/gc-tracer.h"
13 #include "src/heap/heap-inl.h"
14 #include "src/heap/heap.h"
15 #include "src/heap/invalidated-slots-inl.h"
16 #include "src/heap/mark-compact-inl.h"
17 #include "src/heap/mark-compact.h"
18 #include "src/heap/memory-chunk-inl.h"
19 #include "src/heap/memory-chunk.h"
20 #include "src/heap/objects-visiting-inl.h"
21 #include "src/heap/remembered-set-inl.h"
22 #include "src/heap/scavenger-inl.h"
23 #include "src/heap/sweeper.h"
24 #include "src/objects/data-handler-inl.h"
25 #include "src/objects/embedder-data-array-inl.h"
26 #include "src/objects/js-array-buffer-inl.h"
27 #include "src/objects/objects-body-descriptors-inl.h"
28 #include "src/objects/transitions-inl.h"
29 #include "src/utils/utils-inl.h"
30
31 namespace v8 {
32 namespace internal {
33
34 class IterateAndScavengePromotedObjectsVisitor final : public ObjectVisitor {
35 public:
IterateAndScavengePromotedObjectsVisitor(Scavenger * scavenger,bool record_slots)36 IterateAndScavengePromotedObjectsVisitor(Scavenger* scavenger,
37 bool record_slots)
38 : scavenger_(scavenger), record_slots_(record_slots) {}
39
VisitMapPointer(HeapObject host)40 V8_INLINE void VisitMapPointer(HeapObject host) final {
41 if (!record_slots_) return;
42 MapWord map_word = host.map_word(kRelaxedLoad);
43 if (map_word.IsForwardingAddress()) {
44 // Surviving new large objects have forwarding pointers in the map word.
45 DCHECK(MemoryChunk::FromHeapObject(host)->InNewLargeObjectSpace());
46 return;
47 }
48 HandleSlot(host, HeapObjectSlot(host.map_slot()), map_word.ToMap());
49 }
50
VisitPointers(HeapObject host,ObjectSlot start,ObjectSlot end)51 V8_INLINE void VisitPointers(HeapObject host, ObjectSlot start,
52 ObjectSlot end) final {
53 VisitPointersImpl(host, start, end);
54 }
55
VisitPointers(HeapObject host,MaybeObjectSlot start,MaybeObjectSlot end)56 V8_INLINE void VisitPointers(HeapObject host, MaybeObjectSlot start,
57 MaybeObjectSlot end) final {
58 VisitPointersImpl(host, start, end);
59 }
60
VisitCodePointer(HeapObject host,CodeObjectSlot slot)61 V8_INLINE void VisitCodePointer(HeapObject host, CodeObjectSlot slot) final {
62 CHECK(V8_EXTERNAL_CODE_SPACE_BOOL);
63 // Code slots never appear in new space because CodeDataContainers, the
64 // only object that can contain code pointers, are always allocated in
65 // the old space.
66 UNREACHABLE();
67 }
68
VisitCodeTarget(Code host,RelocInfo * rinfo)69 V8_INLINE void VisitCodeTarget(Code host, RelocInfo* rinfo) final {
70 Code target = Code::GetCodeFromTargetAddress(rinfo->target_address());
71 HandleSlot(host, FullHeapObjectSlot(&target), target);
72 }
VisitEmbeddedPointer(Code host,RelocInfo * rinfo)73 V8_INLINE void VisitEmbeddedPointer(Code host, RelocInfo* rinfo) final {
74 PtrComprCageBase cage_base = host.main_cage_base();
75 HeapObject heap_object = rinfo->target_object(cage_base);
76 HandleSlot(host, FullHeapObjectSlot(&heap_object), heap_object);
77 }
78
VisitEphemeron(HeapObject obj,int entry,ObjectSlot key,ObjectSlot value)79 inline void VisitEphemeron(HeapObject obj, int entry, ObjectSlot key,
80 ObjectSlot value) override {
81 DCHECK(Heap::IsLargeObject(obj) || obj.IsEphemeronHashTable());
82 VisitPointer(obj, value);
83
84 if (ObjectInYoungGeneration(*key)) {
85 // We cannot check the map here, as it might be a large object.
86 scavenger_->RememberPromotedEphemeron(
87 EphemeronHashTable::unchecked_cast(obj), entry);
88 } else {
89 VisitPointer(obj, key);
90 }
91 }
92
93 private:
94 template <typename TSlot>
VisitPointersImpl(HeapObject host,TSlot start,TSlot end)95 V8_INLINE void VisitPointersImpl(HeapObject host, TSlot start, TSlot end) {
96 using THeapObjectSlot = typename TSlot::THeapObjectSlot;
97 // Treat weak references as strong.
98 // TODO(marja): Proper weakness handling in the young generation.
99 for (TSlot slot = start; slot < end; ++slot) {
100 typename TSlot::TObject object = *slot;
101 HeapObject heap_object;
102 if (object.GetHeapObject(&heap_object)) {
103 HandleSlot(host, THeapObjectSlot(slot), heap_object);
104 }
105 }
106 }
107
108 template <typename THeapObjectSlot>
HandleSlot(HeapObject host,THeapObjectSlot slot,HeapObject target)109 V8_INLINE void HandleSlot(HeapObject host, THeapObjectSlot slot,
110 HeapObject target) {
111 static_assert(
112 std::is_same<THeapObjectSlot, FullHeapObjectSlot>::value ||
113 std::is_same<THeapObjectSlot, HeapObjectSlot>::value,
114 "Only FullHeapObjectSlot and HeapObjectSlot are expected here");
115 scavenger_->PageMemoryFence(MaybeObject::FromObject(target));
116
117 if (Heap::InFromPage(target)) {
118 SlotCallbackResult result = scavenger_->ScavengeObject(slot, target);
119 bool success = (*slot)->GetHeapObject(&target);
120 USE(success);
121 DCHECK(success);
122
123 if (result == KEEP_SLOT) {
124 SLOW_DCHECK(target.IsHeapObject());
125 MemoryChunk* chunk = MemoryChunk::FromHeapObject(host);
126
127 // Sweeper is stopped during scavenge, so we can directly
128 // insert into its remembered set here.
129 RememberedSet<OLD_TO_NEW>::Insert<AccessMode::ATOMIC>(chunk,
130 slot.address());
131 }
132 SLOW_DCHECK(!MarkCompactCollector::IsOnEvacuationCandidate(target));
133 } else if (record_slots_ &&
134 MarkCompactCollector::IsOnEvacuationCandidate(target)) {
135 // We should never try to record off-heap slots.
136 DCHECK((std::is_same<THeapObjectSlot, HeapObjectSlot>::value));
137 // Code slots never appear in new space because CodeDataContainers, the
138 // only object that can contain code pointers, are always allocated in
139 // the old space.
140 DCHECK_IMPLIES(V8_EXTERNAL_CODE_SPACE_BOOL,
141 !MemoryChunk::FromHeapObject(target)->IsFlagSet(
142 MemoryChunk::IS_EXECUTABLE));
143
144 // We cannot call MarkCompactCollector::RecordSlot because that checks
145 // that the host page is not in young generation, which does not hold
146 // for pending large pages.
147 RememberedSet<OLD_TO_OLD>::Insert<AccessMode::ATOMIC>(
148 MemoryChunk::FromHeapObject(host), slot.address());
149 }
150 }
151
152 Scavenger* const scavenger_;
153 const bool record_slots_;
154 };
155
156 namespace {
157
IsUnscavengedHeapObject(Heap * heap,Object object)158 V8_INLINE bool IsUnscavengedHeapObject(Heap* heap, Object object) {
159 return Heap::InFromPage(object) &&
160 !HeapObject::cast(object).map_word(kRelaxedLoad).IsForwardingAddress();
161 }
162
163 // Same as IsUnscavengedHeapObject() above but specialized for HeapObjects.
IsUnscavengedHeapObject(Heap * heap,HeapObject heap_object)164 V8_INLINE bool IsUnscavengedHeapObject(Heap* heap, HeapObject heap_object) {
165 return Heap::InFromPage(heap_object) &&
166 !heap_object.map_word(kRelaxedLoad).IsForwardingAddress();
167 }
168
IsUnscavengedHeapObjectSlot(Heap * heap,FullObjectSlot p)169 bool IsUnscavengedHeapObjectSlot(Heap* heap, FullObjectSlot p) {
170 return IsUnscavengedHeapObject(heap, *p);
171 }
172
173 } // namespace
174
175 class ScavengeWeakObjectRetainer : public WeakObjectRetainer {
176 public:
RetainAs(Object object)177 Object RetainAs(Object object) override {
178 if (!Heap::InFromPage(object)) {
179 return object;
180 }
181
182 MapWord map_word = HeapObject::cast(object).map_word(kRelaxedLoad);
183 if (map_word.IsForwardingAddress()) {
184 return map_word.ToForwardingAddress();
185 }
186 return Object();
187 }
188 };
189
JobTask(ScavengerCollector * outer,std::vector<std::unique_ptr<Scavenger>> * scavengers,std::vector<std::pair<ParallelWorkItem,MemoryChunk * >> memory_chunks,Scavenger::CopiedList * copied_list,Scavenger::PromotionList * promotion_list)190 ScavengerCollector::JobTask::JobTask(
191 ScavengerCollector* outer,
192 std::vector<std::unique_ptr<Scavenger>>* scavengers,
193 std::vector<std::pair<ParallelWorkItem, MemoryChunk*>> memory_chunks,
194 Scavenger::CopiedList* copied_list,
195 Scavenger::PromotionList* promotion_list)
196 : outer_(outer),
197 scavengers_(scavengers),
198 memory_chunks_(std::move(memory_chunks)),
199 remaining_memory_chunks_(memory_chunks_.size()),
200 generator_(memory_chunks_.size()),
201 copied_list_(copied_list),
202 promotion_list_(promotion_list) {}
203
Run(JobDelegate * delegate)204 void ScavengerCollector::JobTask::Run(JobDelegate* delegate) {
205 DCHECK_LT(delegate->GetTaskId(), scavengers_->size());
206 Scavenger* scavenger = (*scavengers_)[delegate->GetTaskId()].get();
207 if (delegate->IsJoiningThread()) {
208 // This is already traced in GCTracer::Scope::SCAVENGER_SCAVENGE_PARALLEL
209 // in ScavengerCollector::CollectGarbage.
210 ProcessItems(delegate, scavenger);
211 } else {
212 TRACE_GC_EPOCH(outer_->heap_->tracer(),
213 GCTracer::Scope::SCAVENGER_BACKGROUND_SCAVENGE_PARALLEL,
214 ThreadKind::kBackground);
215 ProcessItems(delegate, scavenger);
216 }
217 }
218
GetMaxConcurrency(size_t worker_count) const219 size_t ScavengerCollector::JobTask::GetMaxConcurrency(
220 size_t worker_count) const {
221 // We need to account for local segments held by worker_count in addition to
222 // GlobalPoolSize() of copied_list_ and promotion_list_.
223 return std::min<size_t>(
224 scavengers_->size(),
225 std::max<size_t>(
226 remaining_memory_chunks_.load(std::memory_order_relaxed),
227 worker_count + copied_list_->Size() + promotion_list_->Size()));
228 }
229
ProcessItems(JobDelegate * delegate,Scavenger * scavenger)230 void ScavengerCollector::JobTask::ProcessItems(JobDelegate* delegate,
231 Scavenger* scavenger) {
232 double scavenging_time = 0.0;
233 {
234 TimedScope scope(&scavenging_time);
235 ConcurrentScavengePages(scavenger);
236 scavenger->Process(delegate);
237 }
238 if (FLAG_trace_parallel_scavenge) {
239 PrintIsolate(outer_->heap_->isolate(),
240 "scavenge[%p]: time=%.2f copied=%zu promoted=%zu\n",
241 static_cast<void*>(this), scavenging_time,
242 scavenger->bytes_copied(), scavenger->bytes_promoted());
243 }
244 }
245
ConcurrentScavengePages(Scavenger * scavenger)246 void ScavengerCollector::JobTask::ConcurrentScavengePages(
247 Scavenger* scavenger) {
248 while (remaining_memory_chunks_.load(std::memory_order_relaxed) > 0) {
249 base::Optional<size_t> index = generator_.GetNext();
250 if (!index) return;
251 for (size_t i = *index; i < memory_chunks_.size(); ++i) {
252 auto& work_item = memory_chunks_[i];
253 if (!work_item.first.TryAcquire()) break;
254 scavenger->ScavengePage(work_item.second);
255 if (remaining_memory_chunks_.fetch_sub(1, std::memory_order_relaxed) <=
256 1) {
257 return;
258 }
259 }
260 }
261 }
262
ScavengerCollector(Heap * heap)263 ScavengerCollector::ScavengerCollector(Heap* heap)
264 : isolate_(heap->isolate()), heap_(heap) {}
265
266 // Remove this crashkey after chromium:1010312 is fixed.
267 class V8_NODISCARD ScopedFullHeapCrashKey {
268 public:
ScopedFullHeapCrashKey(Isolate * isolate)269 explicit ScopedFullHeapCrashKey(Isolate* isolate) : isolate_(isolate) {
270 isolate_->AddCrashKey(v8::CrashKeyId::kDumpType, "heap");
271 }
~ScopedFullHeapCrashKey()272 ~ScopedFullHeapCrashKey() {
273 isolate_->AddCrashKey(v8::CrashKeyId::kDumpType, "");
274 }
275
276 private:
277 Isolate* isolate_ = nullptr;
278 };
279
CollectGarbage()280 void ScavengerCollector::CollectGarbage() {
281 ScopedFullHeapCrashKey collect_full_heap_dump_if_crash(isolate_);
282
283 DCHECK(surviving_new_large_objects_.empty());
284 std::vector<std::unique_ptr<Scavenger>> scavengers;
285 Scavenger::EmptyChunksList empty_chunks;
286 const int num_scavenge_tasks = NumberOfScavengeTasks();
287 Scavenger::CopiedList copied_list;
288 Scavenger::PromotionList promotion_list;
289 EphemeronTableList ephemeron_table_list;
290
291 {
292 Sweeper* sweeper = heap_->mark_compact_collector()->sweeper();
293
294 // Pause the concurrent sweeper.
295 Sweeper::PauseScope pause_scope(sweeper);
296 // Filter out pages from the sweeper that need to be processed for old to
297 // new slots by the Scavenger. After processing, the Scavenger adds back
298 // pages that are still unsweeped. This way the Scavenger has exclusive
299 // access to the slots of a page and can completely avoid any locks on
300 // the page itself.
301 Sweeper::FilterSweepingPagesScope filter_scope(sweeper, pause_scope);
302 filter_scope.FilterOldSpaceSweepingPages(
303 [](Page* page) { return !page->ContainsSlots<OLD_TO_NEW>(); });
304
305 const bool is_logging = isolate_->LogObjectRelocation();
306 for (int i = 0; i < num_scavenge_tasks; ++i) {
307 scavengers.emplace_back(
308 new Scavenger(this, heap_, is_logging, &empty_chunks, &copied_list,
309 &promotion_list, &ephemeron_table_list, i));
310 }
311
312 std::vector<std::pair<ParallelWorkItem, MemoryChunk*>> memory_chunks;
313 RememberedSet<OLD_TO_NEW>::IterateMemoryChunks(
314 heap_, [&memory_chunks](MemoryChunk* chunk) {
315 memory_chunks.emplace_back(ParallelWorkItem{}, chunk);
316 });
317
318 RootScavengeVisitor root_scavenge_visitor(scavengers[kMainThreadId].get());
319
320 {
321 // Identify weak unmodified handles. Requires an unmodified graph.
322 TRACE_GC(
323 heap_->tracer(),
324 GCTracer::Scope::SCAVENGER_SCAVENGE_WEAK_GLOBAL_HANDLES_IDENTIFY);
325 isolate_->global_handles()->IdentifyWeakUnmodifiedObjects(
326 &JSObject::IsUnmodifiedApiObject);
327 }
328 {
329 // Copy roots.
330 TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_SCAVENGE_ROOTS);
331 // Scavenger treats all weak roots except for global handles as strong.
332 // That is why we don't set skip_weak = true here and instead visit
333 // global handles separately.
334 base::EnumSet<SkipRoot> options({SkipRoot::kExternalStringTable,
335 SkipRoot::kGlobalHandles,
336 SkipRoot::kOldGeneration});
337 if (V8_UNLIKELY(FLAG_scavenge_separate_stack_scanning)) {
338 options.Add(SkipRoot::kStack);
339 }
340 heap_->IterateRoots(&root_scavenge_visitor, options);
341 isolate_->global_handles()->IterateYoungStrongAndDependentRoots(
342 &root_scavenge_visitor);
343 scavengers[kMainThreadId]->Publish();
344 }
345 {
346 // Parallel phase scavenging all copied and promoted objects.
347 TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_SCAVENGE_PARALLEL);
348 V8::GetCurrentPlatform()
349 ->PostJob(v8::TaskPriority::kUserBlocking,
350 std::make_unique<JobTask>(this, &scavengers,
351 std::move(memory_chunks),
352 &copied_list, &promotion_list))
353 ->Join();
354 DCHECK(copied_list.IsEmpty());
355 DCHECK(promotion_list.IsEmpty());
356 }
357
358 if (V8_UNLIKELY(FLAG_scavenge_separate_stack_scanning)) {
359 IterateStackAndScavenge(&root_scavenge_visitor, &scavengers,
360 kMainThreadId);
361 DCHECK(copied_list.IsEmpty());
362 DCHECK(promotion_list.IsEmpty());
363 }
364
365 {
366 // Scavenge weak global handles.
367 TRACE_GC(heap_->tracer(),
368 GCTracer::Scope::SCAVENGER_SCAVENGE_WEAK_GLOBAL_HANDLES_PROCESS);
369 isolate_->global_handles()->MarkYoungWeakDeadObjectsPending(
370 &IsUnscavengedHeapObjectSlot);
371 isolate_->global_handles()->IterateYoungWeakDeadObjectsForFinalizers(
372 &root_scavenge_visitor);
373 scavengers[kMainThreadId]->Process();
374
375 DCHECK(copied_list.IsEmpty());
376 DCHECK(promotion_list.IsEmpty());
377 isolate_->global_handles()->IterateYoungWeakObjectsForPhantomHandles(
378 &root_scavenge_visitor, &IsUnscavengedHeapObjectSlot);
379 }
380
381 {
382 // Finalize parallel scavenging.
383 TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_SCAVENGE_FINALIZE);
384
385 DCHECK(surviving_new_large_objects_.empty());
386
387 for (auto& scavenger : scavengers) {
388 scavenger->Finalize();
389 }
390 scavengers.clear();
391
392 HandleSurvivingNewLargeObjects();
393 }
394 }
395
396 {
397 // Update references into new space
398 TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_SCAVENGE_UPDATE_REFS);
399 heap_->UpdateYoungReferencesInExternalStringTable(
400 &Heap::UpdateYoungReferenceInExternalStringTableEntry);
401
402 heap_->incremental_marking()->UpdateMarkingWorklistAfterYoungGenGC();
403
404 if (V8_UNLIKELY(FLAG_track_retaining_path)) {
405 heap_->UpdateRetainersAfterScavenge();
406 }
407 }
408
409 if (FLAG_concurrent_marking) {
410 // Ensure that concurrent marker does not track pages that are
411 // going to be unmapped.
412 for (Page* p :
413 PageRange(heap_->new_space()->from_space().first_page(), nullptr)) {
414 heap_->concurrent_marking()->ClearMemoryChunkData(p);
415 }
416 }
417
418 ProcessWeakReferences(&ephemeron_table_list);
419
420 // Set age mark.
421 heap_->new_space_->set_age_mark(heap_->new_space()->top());
422
423 // Since we promote all surviving large objects immediatelly, all remaining
424 // large objects must be dead.
425 // TODO(hpayer): Don't free all as soon as we have an intermediate generation.
426 heap_->new_lo_space()->FreeDeadObjects([](HeapObject) { return true; });
427
428 {
429 TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_FREE_REMEMBERED_SET);
430 Scavenger::EmptyChunksList::Local empty_chunks_local(&empty_chunks);
431 MemoryChunk* chunk;
432 while (empty_chunks_local.Pop(&chunk)) {
433 // Since sweeping was already restarted only check chunks that already got
434 // swept.
435 if (chunk->SweepingDone()) {
436 RememberedSet<OLD_TO_NEW>::CheckPossiblyEmptyBuckets(chunk);
437 } else {
438 chunk->possibly_empty_buckets()->Release();
439 }
440 }
441
442 #ifdef DEBUG
443 RememberedSet<OLD_TO_NEW>::IterateMemoryChunks(
444 heap_, [](MemoryChunk* chunk) {
445 DCHECK(chunk->possibly_empty_buckets()->IsEmpty());
446 });
447 #endif
448 }
449
450 {
451 TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_SWEEP_ARRAY_BUFFERS);
452 SweepArrayBufferExtensions();
453 }
454
455 // Update how much has survived scavenge.
456 heap_->IncrementYoungSurvivorsCounter(heap_->SurvivedYoungObjectSize());
457 }
458
IterateStackAndScavenge(RootScavengeVisitor * root_scavenge_visitor,std::vector<std::unique_ptr<Scavenger>> * scavengers,int main_thread_id)459 void ScavengerCollector::IterateStackAndScavenge(
460
461 RootScavengeVisitor* root_scavenge_visitor,
462 std::vector<std::unique_ptr<Scavenger>>* scavengers, int main_thread_id) {
463 // Scan the stack, scavenge the newly discovered objects, and report
464 // the survival statistics before and afer the stack scanning.
465 // This code is not intended for production.
466 TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_SCAVENGE_STACK_ROOTS);
467 size_t survived_bytes_before = 0;
468 for (auto& scavenger : *scavengers) {
469 survived_bytes_before +=
470 scavenger->bytes_copied() + scavenger->bytes_promoted();
471 }
472 heap_->IterateStackRoots(root_scavenge_visitor);
473 (*scavengers)[main_thread_id]->Process();
474 size_t survived_bytes_after = 0;
475 for (auto& scavenger : *scavengers) {
476 survived_bytes_after +=
477 scavenger->bytes_copied() + scavenger->bytes_promoted();
478 }
479 TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("v8.gc"),
480 "V8.GCScavengerStackScanning", "survived_bytes_before",
481 survived_bytes_before, "survived_bytes_after",
482 survived_bytes_after);
483 if (FLAG_trace_gc_verbose && !FLAG_trace_gc_ignore_scavenger) {
484 isolate_->PrintWithTimestamp(
485 "Scavenge stack scanning: survived_before=%4zuKB, "
486 "survived_after=%4zuKB delta=%.1f%%\n",
487 survived_bytes_before / KB, survived_bytes_after / KB,
488 (survived_bytes_after - survived_bytes_before) * 100.0 /
489 survived_bytes_after);
490 }
491 }
492
SweepArrayBufferExtensions()493 void ScavengerCollector::SweepArrayBufferExtensions() {
494 heap_->array_buffer_sweeper()->RequestSweep(
495 ArrayBufferSweeper::SweepingType::kYoung);
496 }
497
HandleSurvivingNewLargeObjects()498 void ScavengerCollector::HandleSurvivingNewLargeObjects() {
499 const bool is_compacting = heap_->incremental_marking()->IsCompacting();
500 MajorAtomicMarkingState* marking_state =
501 heap_->incremental_marking()->atomic_marking_state();
502
503 for (SurvivingNewLargeObjectMapEntry update_info :
504 surviving_new_large_objects_) {
505 HeapObject object = update_info.first;
506 Map map = update_info.second;
507 // Order is important here. We have to re-install the map to have access
508 // to meta-data like size during page promotion.
509 object.set_map_word(MapWord::FromMap(map), kRelaxedStore);
510
511 if (is_compacting && marking_state->IsBlack(object) &&
512 MarkCompactCollector::IsOnEvacuationCandidate(map)) {
513 RememberedSet<OLD_TO_OLD>::Insert<AccessMode::ATOMIC>(
514 MemoryChunk::FromHeapObject(object), object.map_slot().address());
515 }
516 LargePage* page = LargePage::FromHeapObject(object);
517 heap_->lo_space()->PromoteNewLargeObject(page);
518 }
519 surviving_new_large_objects_.clear();
520 }
521
MergeSurvivingNewLargeObjects(const SurvivingNewLargeObjectsMap & objects)522 void ScavengerCollector::MergeSurvivingNewLargeObjects(
523 const SurvivingNewLargeObjectsMap& objects) {
524 for (SurvivingNewLargeObjectMapEntry object : objects) {
525 bool success = surviving_new_large_objects_.insert(object).second;
526 USE(success);
527 DCHECK(success);
528 }
529 }
530
NumberOfScavengeTasks()531 int ScavengerCollector::NumberOfScavengeTasks() {
532 if (!FLAG_parallel_scavenge) return 1;
533 const int num_scavenge_tasks =
534 static_cast<int>(heap_->new_space()->TotalCapacity()) / MB + 1;
535 static int num_cores = V8::GetCurrentPlatform()->NumberOfWorkerThreads() + 1;
536 int tasks = std::max(
537 1, std::min({num_scavenge_tasks, kMaxScavengerTasks, num_cores}));
538 if (!heap_->CanPromoteYoungAndExpandOldGeneration(
539 static_cast<size_t>(tasks * Page::kPageSize))) {
540 // Optimize for memory usage near the heap limit.
541 tasks = 1;
542 }
543 return tasks;
544 }
545
Local(Scavenger::PromotionList * promotion_list)546 Scavenger::PromotionList::Local::Local(Scavenger::PromotionList* promotion_list)
547 : regular_object_promotion_list_local_(
548 &promotion_list->regular_object_promotion_list_),
549 large_object_promotion_list_local_(
550 &promotion_list->large_object_promotion_list_) {}
551
552 namespace {
CreateSharedOldAllocator(Heap * heap)553 ConcurrentAllocator* CreateSharedOldAllocator(Heap* heap) {
554 if (FLAG_shared_string_table && heap->isolate()->shared_isolate()) {
555 return new ConcurrentAllocator(nullptr, heap->shared_old_space());
556 }
557 return nullptr;
558 }
559 } // namespace
560
Scavenger(ScavengerCollector * collector,Heap * heap,bool is_logging,EmptyChunksList * empty_chunks,CopiedList * copied_list,PromotionList * promotion_list,EphemeronTableList * ephemeron_table_list,int task_id)561 Scavenger::Scavenger(ScavengerCollector* collector, Heap* heap, bool is_logging,
562 EmptyChunksList* empty_chunks, CopiedList* copied_list,
563 PromotionList* promotion_list,
564 EphemeronTableList* ephemeron_table_list, int task_id)
565 : collector_(collector),
566 heap_(heap),
567 empty_chunks_local_(empty_chunks),
568 promotion_list_local_(promotion_list),
569 copied_list_local_(copied_list),
570 ephemeron_table_list_local_(ephemeron_table_list),
571 local_pretenuring_feedback_(kInitialLocalPretenuringFeedbackCapacity),
572 copied_size_(0),
573 promoted_size_(0),
574 allocator_(heap, CompactionSpaceKind::kCompactionSpaceForScavenge),
575 shared_old_allocator_(CreateSharedOldAllocator(heap_)),
576 is_logging_(is_logging),
577 is_incremental_marking_(heap->incremental_marking()->IsMarking()),
578 is_compacting_(heap->incremental_marking()->IsCompacting()),
579 is_compacting_including_map_space_(is_compacting_ && FLAG_compact_maps),
580 shared_string_table_(shared_old_allocator_.get() != nullptr) {}
581
IterateAndScavengePromotedObject(HeapObject target,Map map,int size)582 void Scavenger::IterateAndScavengePromotedObject(HeapObject target, Map map,
583 int size) {
584 // We are not collecting slots on new space objects during mutation thus we
585 // have to scan for pointers to evacuation candidates when we promote
586 // objects. But we should not record any slots in non-black objects. Grey
587 // object's slots would be rescanned. White object might not survive until
588 // the end of collection it would be a violation of the invariant to record
589 // its slots.
590 const bool record_slots =
591 is_compacting_ &&
592 heap()->incremental_marking()->atomic_marking_state()->IsBlack(target);
593
594 IterateAndScavengePromotedObjectsVisitor visitor(this, record_slots);
595
596 if (is_compacting_including_map_space_) {
597 // When we compact map space, we also want to visit the map word.
598 target.IterateFast(map, size, &visitor);
599 } else {
600 target.IterateBodyFast(map, size, &visitor);
601 }
602
603 if (map.IsJSArrayBufferMap()) {
604 DCHECK(!BasicMemoryChunk::FromHeapObject(target)->IsLargePage());
605 JSArrayBuffer::cast(target).YoungMarkExtensionPromoted();
606 }
607 }
608
RememberPromotedEphemeron(EphemeronHashTable table,int entry)609 void Scavenger::RememberPromotedEphemeron(EphemeronHashTable table, int entry) {
610 auto indices =
611 ephemeron_remembered_set_.insert({table, std::unordered_set<int>()});
612 indices.first->second.insert(entry);
613 }
614
AddPageToSweeperIfNecessary(MemoryChunk * page)615 void Scavenger::AddPageToSweeperIfNecessary(MemoryChunk* page) {
616 AllocationSpace space = page->owner_identity();
617 if ((space == OLD_SPACE) && !page->SweepingDone()) {
618 heap()->mark_compact_collector()->sweeper()->AddPage(
619 space, reinterpret_cast<Page*>(page),
620 Sweeper::READD_TEMPORARY_REMOVED_PAGE);
621 }
622 }
623
ScavengePage(MemoryChunk * page)624 void Scavenger::ScavengePage(MemoryChunk* page) {
625 CodePageMemoryModificationScope memory_modification_scope(page);
626
627 if (page->slot_set<OLD_TO_NEW, AccessMode::ATOMIC>() != nullptr) {
628 InvalidatedSlotsFilter filter = InvalidatedSlotsFilter::OldToNew(page);
629 RememberedSet<OLD_TO_NEW>::IterateAndTrackEmptyBuckets(
630 page,
631 [this, &filter](MaybeObjectSlot slot) {
632 if (!filter.IsValid(slot.address())) return REMOVE_SLOT;
633 return CheckAndScavengeObject(heap_, slot);
634 },
635 &empty_chunks_local_);
636 }
637
638 if (page->invalidated_slots<OLD_TO_NEW>() != nullptr) {
639 // The invalidated slots are not needed after old-to-new slots were
640 // processed.
641 page->ReleaseInvalidatedSlots<OLD_TO_NEW>();
642 }
643
644 RememberedSet<OLD_TO_NEW>::IterateTyped(
645 page, [=](SlotType type, Address addr) {
646 return UpdateTypedSlotHelper::UpdateTypedSlot(
647 heap_, type, addr, [this](FullMaybeObjectSlot slot) {
648 return CheckAndScavengeObject(heap(), slot);
649 });
650 });
651
652 AddPageToSweeperIfNecessary(page);
653 }
654
Process(JobDelegate * delegate)655 void Scavenger::Process(JobDelegate* delegate) {
656 ScavengeVisitor scavenge_visitor(this);
657
658 bool done;
659 size_t objects = 0;
660 do {
661 done = true;
662 ObjectAndSize object_and_size;
663 while (promotion_list_local_.ShouldEagerlyProcessPromotionList() &&
664 copied_list_local_.Pop(&object_and_size)) {
665 scavenge_visitor.Visit(object_and_size.first);
666 done = false;
667 if (delegate && ((++objects % kInterruptThreshold) == 0)) {
668 if (!copied_list_local_.IsEmpty()) {
669 delegate->NotifyConcurrencyIncrease();
670 }
671 }
672 }
673
674 struct PromotionListEntry entry;
675 while (promotion_list_local_.Pop(&entry)) {
676 HeapObject target = entry.heap_object;
677 IterateAndScavengePromotedObject(target, entry.map, entry.size);
678 done = false;
679 if (delegate && ((++objects % kInterruptThreshold) == 0)) {
680 if (!promotion_list_local_.IsGlobalPoolEmpty()) {
681 delegate->NotifyConcurrencyIncrease();
682 }
683 }
684 }
685 } while (!done);
686 }
687
ProcessWeakReferences(EphemeronTableList * ephemeron_table_list)688 void ScavengerCollector::ProcessWeakReferences(
689 EphemeronTableList* ephemeron_table_list) {
690 ScavengeWeakObjectRetainer weak_object_retainer;
691 heap_->ProcessYoungWeakReferences(&weak_object_retainer);
692 ClearYoungEphemerons(ephemeron_table_list);
693 ClearOldEphemerons();
694 }
695
696 // Clear ephemeron entries from EphemeronHashTables in new-space whenever the
697 // entry has a dead new-space key.
ClearYoungEphemerons(EphemeronTableList * ephemeron_table_list)698 void ScavengerCollector::ClearYoungEphemerons(
699 EphemeronTableList* ephemeron_table_list) {
700 ephemeron_table_list->Iterate([this](EphemeronHashTable table) {
701 for (InternalIndex i : table.IterateEntries()) {
702 // Keys in EphemeronHashTables must be heap objects.
703 HeapObjectSlot key_slot(
704 table.RawFieldOfElementAt(EphemeronHashTable::EntryToIndex(i)));
705 HeapObject key = key_slot.ToHeapObject();
706 if (IsUnscavengedHeapObject(heap_, key)) {
707 table.RemoveEntry(i);
708 } else {
709 HeapObject forwarded = ForwardingAddress(key);
710 key_slot.StoreHeapObject(forwarded);
711 }
712 }
713 });
714 ephemeron_table_list->Clear();
715 }
716
717 // Clear ephemeron entries from EphemeronHashTables in old-space whenever the
718 // entry has a dead new-space key.
ClearOldEphemerons()719 void ScavengerCollector::ClearOldEphemerons() {
720 for (auto it = heap_->ephemeron_remembered_set_.begin();
721 it != heap_->ephemeron_remembered_set_.end();) {
722 EphemeronHashTable table = it->first;
723 auto& indices = it->second;
724 for (auto iti = indices.begin(); iti != indices.end();) {
725 // Keys in EphemeronHashTables must be heap objects.
726 HeapObjectSlot key_slot(table.RawFieldOfElementAt(
727 EphemeronHashTable::EntryToIndex(InternalIndex(*iti))));
728 HeapObject key = key_slot.ToHeapObject();
729 if (IsUnscavengedHeapObject(heap_, key)) {
730 table.RemoveEntry(InternalIndex(*iti));
731 iti = indices.erase(iti);
732 } else {
733 HeapObject forwarded = ForwardingAddress(key);
734 key_slot.StoreHeapObject(forwarded);
735 if (!Heap::InYoungGeneration(forwarded)) {
736 iti = indices.erase(iti);
737 } else {
738 ++iti;
739 }
740 }
741 }
742
743 if (indices.size() == 0) {
744 it = heap_->ephemeron_remembered_set_.erase(it);
745 } else {
746 ++it;
747 }
748 }
749 }
750
Finalize()751 void Scavenger::Finalize() {
752 heap()->MergeAllocationSitePretenuringFeedback(local_pretenuring_feedback_);
753 heap()->IncrementSemiSpaceCopiedObjectSize(copied_size_);
754 heap()->IncrementPromotedObjectsSize(promoted_size_);
755 collector_->MergeSurvivingNewLargeObjects(surviving_new_large_objects_);
756 allocator_.Finalize();
757 if (shared_old_allocator_) shared_old_allocator_->FreeLinearAllocationArea();
758 empty_chunks_local_.Publish();
759 ephemeron_table_list_local_.Publish();
760 for (auto it = ephemeron_remembered_set_.begin();
761 it != ephemeron_remembered_set_.end(); ++it) {
762 auto insert_result = heap()->ephemeron_remembered_set_.insert(
763 {it->first, std::unordered_set<int>()});
764 for (int entry : it->second) {
765 insert_result.first->second.insert(entry);
766 }
767 }
768 }
769
Publish()770 void Scavenger::Publish() {
771 copied_list_local_.Publish();
772 promotion_list_local_.Publish();
773 }
774
AddEphemeronHashTable(EphemeronHashTable table)775 void Scavenger::AddEphemeronHashTable(EphemeronHashTable table) {
776 ephemeron_table_list_local_.Push(table);
777 }
778
VisitRootPointer(Root root,const char * description,FullObjectSlot p)779 void RootScavengeVisitor::VisitRootPointer(Root root, const char* description,
780 FullObjectSlot p) {
781 DCHECK(!HasWeakHeapObjectTag(*p));
782 DCHECK(!MapWord::IsPacked((*p).ptr()));
783 ScavengePointer(p);
784 }
785
VisitRootPointers(Root root,const char * description,FullObjectSlot start,FullObjectSlot end)786 void RootScavengeVisitor::VisitRootPointers(Root root, const char* description,
787 FullObjectSlot start,
788 FullObjectSlot end) {
789 // Copy all HeapObject pointers in [start, end)
790 for (FullObjectSlot p = start; p < end; ++p) {
791 ScavengePointer(p);
792 }
793 }
794
ScavengePointer(FullObjectSlot p)795 void RootScavengeVisitor::ScavengePointer(FullObjectSlot p) {
796 Object object = *p;
797 DCHECK(!HasWeakHeapObjectTag(object));
798 DCHECK(!MapWord::IsPacked(object.ptr()));
799 if (Heap::InYoungGeneration(object)) {
800 scavenger_->ScavengeObject(FullHeapObjectSlot(p), HeapObject::cast(object));
801 }
802 }
803
RootScavengeVisitor(Scavenger * scavenger)804 RootScavengeVisitor::RootScavengeVisitor(Scavenger* scavenger)
805 : scavenger_(scavenger) {}
806
ScavengeVisitor(Scavenger * scavenger)807 ScavengeVisitor::ScavengeVisitor(Scavenger* scavenger)
808 : NewSpaceVisitor<ScavengeVisitor>(scavenger->heap()->isolate()),
809 scavenger_(scavenger) {}
810
811 } // namespace internal
812 } // namespace v8
813