• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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