1 /**
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "runtime/mem/gc/g1/g1-gc.h"
17
18 #include "runtime/include/panda_vm.h"
19 #include "runtime/mem/gc/card_table-inl.h"
20 #include "runtime/mem/gc/dynamic/gc_marker_dynamic-inl.h"
21 #include "runtime/mem/gc/g1/ref_cache_builder.h"
22 #include "runtime/mem/gc/g1/update_remset_thread.h"
23 #include "runtime/mem/gc/gc_workers_thread_pool.h"
24 #include "runtime/mem/gc/generational-gc-base-inl.h"
25 #include "runtime/mem/gc/static/gc_marker_static-inl.h"
26 #include "runtime/mem/gc/reference-processor/reference_processor.h"
27 #include "runtime/mem/object_helpers-inl.h"
28 #include "runtime/mem/refstorage/global_object_storage.h"
29 #include "runtime/mem/rem_set-inl.h"
30 #include "runtime/include/thread.h"
31 #include "runtime/include/managed_thread.h"
32
33 namespace panda::mem {
34
ToObjPtr(const void * ptr)35 static inline object_pointer_type ToObjPtr(const void *ptr)
36 {
37 return static_cast<object_pointer_type>(ToUintPtr(ptr));
38 }
39
GetG1BarrierSet()40 static inline GCG1BarrierSet *GetG1BarrierSet()
41 {
42 Thread *thread = Thread::GetCurrent();
43 ASSERT(thread != nullptr);
44 GCBarrierSet *barrier_set = thread->GetBarrierSet();
45 ASSERT(barrier_set != nullptr);
46 return static_cast<GCG1BarrierSet *>(barrier_set);
47 }
48
PreWrbFuncEntrypoint(void * old_value)49 extern "C" void PreWrbFuncEntrypoint(void *old_value)
50 {
51 // The cast below is needed to truncate high 32bits from 64bit pointer
52 // in case object pointers have 32bit.
53 old_value = ToVoidPtr(ToObjPtr(old_value));
54 LOG(DEBUG, GC) << "G1GC pre barrier val = " << std::hex << old_value;
55 auto *thread = ManagedThread::GetCurrent();
56 // thread can't be null here because pre-barrier is called only in concurrent-mark, but we don't process
57 // weak-references in concurrent mark
58 ASSERT(thread != nullptr);
59 auto buffer_vec = thread->GetPreBuff();
60 buffer_vec->push_back(static_cast<ObjectHeader *>(old_value));
61 }
62
63 /*
64 * TSAN doesn't understand that we hold a lock already when we call it from OnThreadTerminate with need_lock=false and
65 * requires to defend by lock
66 */
67 template <bool need_lock = true>
PushCardToUpdateThread(mem::GCG1BarrierSet::G1PostBarrierRingBufferType * cards,mem::GCG1BarrierSet * barrier_set)68 static NO_THREAD_SAFETY_ANALYSIS void PushCardToUpdateThread(mem::GCG1BarrierSet::G1PostBarrierRingBufferType *cards,
69 mem::GCG1BarrierSet *barrier_set)
70 {
71 ASSERT(cards != nullptr);
72 ASSERT(barrier_set != nullptr);
73 LOG(DEBUG, GC) << "Call PushCardToUpdateThread with vector: " << cards;
74 while (true) {
75 mem::CardTable::CardPtr card;
76 bool has_element = cards->TryPop(&card);
77 if (!has_element) {
78 break;
79 }
80 ASSERT(barrier_set != nullptr);
81 // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
82 if constexpr (need_lock) {
83 barrier_set->GetQueueLock()->Lock();
84 }
85 barrier_set->GetUpdatedRefsQueue()->push_back(card);
86 // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
87 if constexpr (need_lock) {
88 barrier_set->GetQueueLock()->Unlock();
89 }
90 }
91 }
92
93 // PostWrbUpdateCardFuncEntrypoint
PostWrbUpdateCardFuncEntrypoint(const void * from,const void * to)94 extern "C" void PostWrbUpdateCardFuncEntrypoint(const void *from, const void *to)
95 {
96 ASSERT(from != nullptr);
97 ASSERT(to != nullptr);
98 // The cast below is needed to truncate high 32bits from 64bit pointer
99 // in case object pointers have 32bit.
100 from = ToVoidPtr(ToObjPtr(from));
101 GCG1BarrierSet *barriers = GetG1BarrierSet();
102 ASSERT(barriers != nullptr);
103 auto card_table = barriers->GetCardTable();
104 ASSERT(card_table != nullptr);
105 // No need to keep remsets for young->young
106 // TODO(dtrubenkov): add assert that we do not have young -> young reference here
107 auto card = card_table->GetCardPtr(ToUintPtr(from));
108 LOG(DEBUG, GC) << "G1GC post queue add ref: " << std::hex << from << " -> " << ToVoidPtr(ToObjPtr(to))
109 << " from_card: " << card;
110 // TODO(dtrubenkov): remove !card->IsYoung() after it will be encoded in compiler barrier
111 if ((card->IsClear()) && (!card->IsYoung())) {
112 // TODO(dtrubenkov): either encode this in compiler barrier or remove from Interpreter barrier (if move to
113 // INT/JIT parts then don't check IsClear here cause it will be marked already)
114 card->Mark();
115
116 auto thread = ManagedThread::GetCurrent();
117 if (thread == nullptr) { // slow path via shared-queue for VM threads: gc/compiler/etc
118 os::memory::LockHolder lock(*barriers->GetQueueLock());
119 barriers->GetUpdatedRefsQueue()->push_back(card);
120 } else {
121 // general fast-path for mutators
122 ASSERT(thread->GetPreBuff() != nullptr); // write barrier cant be called after Terminate
123 auto buffer = thread->GetG1PostBarrierBuffer();
124 ASSERT(buffer != nullptr);
125 buffer->Push(card);
126 }
127 }
128 }
129
130 template <class LanguageConfig>
G1GC(ObjectAllocatorBase * object_allocator,const GCSettings & settings)131 G1GC<LanguageConfig>::G1GC(ObjectAllocatorBase *object_allocator, const GCSettings &settings)
132 : GenerationalGC<LanguageConfig>(object_allocator, settings),
133 marker_(this),
134 concurrent_marking_stack_(this),
135 region_garbage_rate_threshold_(settings.G1RegionGarbageRateThreshold()),
136 g1_promotion_region_alive_rate_(settings.G1PromotionRegionAliveRate()),
137 g1_track_freed_objects_(settings.G1TrackFreedObjects())
138 {
139 InternalAllocatorPtr allocator = this->GetInternalAllocator();
140 this->SetType(GCType::G1_GC);
141 this->SetTLABsSupported();
142 updated_refs_queue_ = allocator->New<GCG1BarrierSet::ThreadLocalCardQueues>();
143 calc_live_bytes_ = [this](const ObjectHeader *obj) {
144 Region *region = ObjectToRegion(obj);
145 if (!this->marker_.IsMarked(obj) && region->HasFlag(IS_OLD)) {
146 size_t object_size = GetAlignedObjectSize(GetObjectSize(obj));
147 region->AddLiveBytesConcurrently(object_size);
148 }
149 return true;
150 };
151
152 os::memory::LockHolder lock(unassigned_buffers_lock_);
153 size_t workers_count = this->GetSettings()->GCWorkersCount() + 1;
154 unique_refs_from_remsets_.resize(workers_count);
155 unassigned_buffers_.reserve(workers_count);
156 for (auto &buf : unique_refs_from_remsets_) {
157 buf.reserve(MAX_REFS / workers_count);
158 unassigned_buffers_.push_back(&buf);
159 }
160 }
161
162 template <class LanguageConfig>
~G1GC()163 G1GC<LanguageConfig>::~G1GC()
164 {
165 InternalAllocatorPtr allocator = this->GetInternalAllocator();
166 {
167 for (auto obj_vector : satb_buff_list_) {
168 allocator->Delete(obj_vector);
169 }
170 }
171 allocator->Delete(updated_refs_queue_);
172 this->GetInternalAllocator()->Delete(update_remset_thread_);
173 }
174
175 template <class LanguageConfig>
WaitForGC(GCTask task)176 void G1GC<LanguageConfig>::WaitForGC(GCTask task)
177 {
178 GenerationalGC<LanguageConfig>::WaitForGC(task);
179 }
180
181 template <class LanguageConfig>
InitGCBits(panda::ObjectHeader * obj_header)182 void G1GC<LanguageConfig>::InitGCBits(panda::ObjectHeader *obj_header)
183 {
184 // The mutator may create a new object during concurrent marking phase.
185 // In this case GC may don't mark it (for example only vregs may contain reference to the new object)
186 // and collect. To avoid such situations add objects to a special buffer which
187 // will be processed at remark stage.
188 if (this->GetCardTable()->GetCardPtr(ToUintPtr(obj_header))->IsYoung() || !concurrent_marking_flag_) {
189 return;
190 }
191 os::memory::LockHolder lock(satb_and_newobj_buf_lock_);
192 newobj_buffer_.push_back(obj_header);
193 }
194
195 template <class LanguageConfig>
PreStartupImp()196 void G1GC<LanguageConfig>::PreStartupImp()
197 {
198 GenerationalGC<LanguageConfig>::DisableTenuredGC();
199 }
200
201 template <class LanguageConfig>
Trigger()202 void G1GC<LanguageConfig>::Trigger()
203 {
204 auto task = MakePandaUnique<GCTask>(GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE, time::GetCurrentTimeInNanos());
205 this->AddGCTask(true, std::move(task), true);
206 }
207
208 template <class LanguageConfig>
209 template <RegionFlag region_type>
DoRegionCompacting(Region * region,bool use_gc_workers,PandaVector<PandaVector<ObjectHeader * > * > * moved_objects_vector)210 void G1GC<LanguageConfig>::DoRegionCompacting(Region *region, bool use_gc_workers,
211 PandaVector<PandaVector<ObjectHeader *> *> *moved_objects_vector)
212 {
213 auto internal_allocator = this->GetInternalAllocator();
214 if (use_gc_workers) {
215 auto vector = internal_allocator->template New<PandaVector<ObjectHeader *>>();
216 moved_objects_vector->push_back(vector);
217 auto storage = internal_allocator->template New<GCWorkersTask::RegionDataType>(vector, region);
218 if (!this->GetWorkersPool()->AddTask(GCWorkersTaskTypes::TASK_REGION_COMPACTING, storage)) {
219 // We couldn't send a task to workers. Therefore, do it here.
220 internal_allocator->Delete(storage);
221 RegionCompactingImpl<true, region_type>(vector, region);
222 }
223 } else {
224 ASSERT(moved_objects_vector->size() == 1);
225 RegionCompactingImpl<false, region_type>(moved_objects_vector->back(), region);
226 }
227 }
228
229 template <class LanguageConfig>
230 template <bool atomic>
RegionPromotionImpl(PandaVector<ObjectHeader * > * moved_objects,Region * region)231 void G1GC<LanguageConfig>::RegionPromotionImpl(PandaVector<ObjectHeader *> *moved_objects, Region *region)
232 {
233 size_t move_size = 0;
234 size_t move_count = 0;
235 auto object_allocator = this->GetG1ObjectAllocator();
236 auto promotion_move_checker = [&moved_objects](ObjectHeader *src) {
237 LOG_DEBUG_OBJECT_EVENTS << "PROMOTE YOUNG object " << src;
238 ASSERT(ObjectToRegion(src)->HasFlag(RegionFlag::IS_EDEN));
239 moved_objects->push_back(src);
240 };
241 auto promotion_death_checker = [this, &move_count, &move_size](ObjectHeader *object_header) {
242 ++move_count;
243 move_size += GetAlignedObjectSize(object_header->ObjectSize());
244 if (IsMarked(object_header)) {
245 return ObjectStatus::ALIVE_OBJECT;
246 }
247 LOG_DEBUG_OBJECT_EVENTS << "PROMOTE DEAD YOUNG object " << object_header;
248 return ObjectStatus::DEAD_OBJECT;
249 };
250 object_allocator->PromoteYoungRegion(region, promotion_death_checker, promotion_move_checker);
251 region->RmvFlag(RegionFlag::IS_COLLECTION_SET);
252 this->mem_stats_.template RecordSizeMovedYoung<atomic>(move_size);
253 this->mem_stats_.template RecordCountMovedYoung<atomic>(move_count);
254 }
255
256 template <class LanguageConfig>
CollectRefsFromCard(CardTable::CardPtr card,Region * region,RefVector * refs_from_remsets)257 void G1GC<LanguageConfig>::CollectRefsFromCard(CardTable::CardPtr card, Region *region, RefVector *refs_from_remsets)
258 {
259 auto mem_range = this->GetCardTable()->GetMemoryRange(card);
260 auto visitor = [this, refs_from_remsets](void *mem) {
261 auto obj = static_cast<ObjectHeader *>(mem);
262 RefCacheBuilder<LanguageConfig> builder(this, refs_from_remsets, &unique_objects_from_remsets_,
263 &objects_from_remsets_lock_);
264 ObjectHelpers<LanguageConfig::LANG_TYPE>::TraverseAllObjectsWithInfo(obj, builder);
265 };
266 region->GetLiveBitmap()->IterateOverMarkedChunkInRange(ToVoidPtr(mem_range.GetStartAddress()),
267 ToVoidPtr(mem_range.GetEndAddress()), visitor);
268 }
269
270 template <class LanguageConfig>
271 template <bool atomic, bool concurrently>
CollectNonRegularObjects(GCTask & task)272 void G1GC<LanguageConfig>::CollectNonRegularObjects(GCTask &task)
273 {
274 size_t delete_size = 0;
275 size_t delete_count = 0;
276 auto death_checker = [&delete_size, &delete_count](ObjectHeader *object_header) {
277 // NOLINTNEXTLINE(readability-braces-around-statements, bugprone-suspicious-semicolon)
278 if constexpr (concurrently) {
279 // We may face a newly created object without live bitmap initialization.
280 if (object_header->template AtomicClassAddr<BaseClass>() == nullptr) {
281 return ObjectStatus::ALIVE_OBJECT;
282 }
283 }
284 Region *region = ObjectToRegion(object_header);
285 auto live_bitmap = region->GetLiveBitmap();
286 if (live_bitmap->AtomicTest(object_header)) {
287 return ObjectStatus::ALIVE_OBJECT;
288 }
289
290 if (region->HasFlag(RegionFlag::IS_LARGE_OBJECT)) {
291 LOG_DEBUG_OBJECT_EVENTS << "DELETE HUMONGOUS object " << object_header;
292 // humongous allocator increases size by region size
293 delete_size += region->Size();
294 ++delete_count;
295 } else {
296 ASSERT(region->HasFlag(RegionFlag::IS_NONMOVABLE));
297 LOG_DEBUG_OBJECT_EVENTS << "DELETE NON MOVABLE object " << object_header;
298 }
299 return ObjectStatus::DEAD_OBJECT;
300 };
301 auto region_visitor = [this](PandaVector<Region *> ®ions) {
302 update_remset_thread_->InvalidateRegions(®ions);
303 };
304 this->GetG1ObjectAllocator()->CollectNonRegularRegions(region_visitor, death_checker);
305 this->mem_stats_.template RecordCountFreedTenured<atomic>(delete_count);
306 this->mem_stats_.template RecordSizeFreedTenured<atomic>(delete_size);
307 task.UpdateGCCollectionType(GCCollectionType::TENURED);
308 }
309
310 template <class LanguageConfig>
NeedToPromote(const Region * region) const311 bool G1GC<LanguageConfig>::NeedToPromote(const Region *region) const
312 {
313 ASSERT(region->HasFlag(RegionFlag::IS_EDEN));
314 // Issue 8183: Remove unnessesary IsYoungFullGC check after refactoring Full GC
315 if ((g1_promotion_region_alive_rate_ < PERCENT_100_D) && !this->IsFullGC() && !IsYoungFullGC()) {
316 size_t alive_bytes = region->CalcMarkBytes();
317 double alive_percentage = static_cast<double>(alive_bytes) / region->Size() * PERCENT_100_D;
318 if (alive_percentage >= g1_promotion_region_alive_rate_) {
319 return true;
320 }
321 }
322 return false;
323 }
324
325 template <class LanguageConfig>
326 template <bool atomic, RegionFlag region_type>
RegionCompactingImpl(PandaVector<ObjectHeader * > * moved_objects,Region * region)327 void G1GC<LanguageConfig>::RegionCompactingImpl(PandaVector<ObjectHeader *> *moved_objects, Region *region)
328 {
329 auto object_allocator = this->GetG1ObjectAllocator();
330 size_t move_size = 0;
331 size_t move_count = 0;
332 size_t delete_size = 0;
333 size_t delete_count = 0;
334
335 auto move_checker = [this, &moved_objects](ObjectHeader *src, ObjectHeader *dst) {
336 LOG_DEBUG_OBJECT_EVENTS << "MOVE object " << src << " -> " << dst;
337 ASSERT(ObjectToRegion(dst)->HasFlag(RegionFlag::IS_OLD));
338 this->SetForwardAddress(src, dst);
339 moved_objects->push_back(dst);
340 };
341 auto death_checker = [this, &move_count, &move_size, &delete_size, &delete_count](ObjectHeader *object_header) {
342 if (IsMarked(object_header)) {
343 ++move_count;
344 move_size += GetAlignedObjectSize(object_header->ObjectSize());
345
346 return ObjectStatus::ALIVE_OBJECT;
347 }
348 ++delete_count;
349 delete_size += GetAlignedObjectSize(object_header->ObjectSize());
350
351 // NOLINTNEXTLINE(readability-braces-around-statements)
352 if constexpr (region_type == RegionFlag::IS_EDEN) {
353 LOG_DEBUG_OBJECT_EVENTS << "DELETE YOUNG object " << object_header;
354 // NOLINTNEXTLINE(readability-misleading-indentation)
355 } else {
356 ASSERT(region_type == RegionFlag::IS_OLD);
357 LOG_DEBUG_OBJECT_EVENTS << "DELETE TENURED object " << object_header;
358 }
359 return ObjectStatus::DEAD_OBJECT;
360 };
361 // NOLINTNEXTLINE(readability-braces-around-statements)
362 if constexpr (region_type == RegionFlag::IS_EDEN) {
363 if (!this->NeedToPromote(region)) {
364 if (g1_track_freed_objects_) {
365 // We want to track all freed objects, therefore, iterate over all objects in region.
366 object_allocator->template CompactRegion<RegionFlag::IS_EDEN, false>(region, death_checker,
367 move_checker);
368 } else {
369 object_allocator->template CompactRegion<RegionFlag::IS_EDEN, true>(region, death_checker,
370 move_checker);
371 size_t allocated_size = region->GetAllocatedBytes();
372 ASSERT(move_size <= allocated_size);
373 // delete_count is equal to 0 because we don't track allocation in TLABs by a default.
374 // We will do it only with PANDA_TRACK_TLAB_ALLOCATIONS key
375 ASSERT(delete_count == 0);
376 ASSERT(delete_size == 0);
377 delete_size = allocated_size - move_size;
378 }
379 this->mem_stats_.template RecordSizeMovedYoung<atomic>(move_size);
380 this->mem_stats_.template RecordCountMovedYoung<atomic>(move_count);
381 this->mem_stats_.template RecordSizeFreedYoung<atomic>(delete_size);
382 this->mem_stats_.template RecordCountFreedYoung<atomic>(delete_count);
383 } else {
384 RegionPromotionImpl<atomic>(moved_objects, region);
385 }
386 // NOLINTNEXTLINE(readability-misleading-indentation)
387 } else {
388 ASSERT(region->HasFlag(RegionFlag::IS_OLD));
389 ASSERT(!region->HasFlag(RegionFlag::IS_NONMOVABLE) && !region->HasFlag(RegionFlag::IS_LARGE_OBJECT));
390 if (g1_track_freed_objects_) {
391 // We want to track all freed objects, therefore, iterate over all objects in region.
392 object_allocator->template CompactRegion<RegionFlag::IS_OLD, false>(region, death_checker, move_checker);
393 } else {
394 object_allocator->template CompactRegion<RegionFlag::IS_OLD, true>(region, death_checker, move_checker);
395 size_t allocated_objects = region->GetAllocatedObjects();
396 size_t allocated_size = region->GetAllocatedBytes();
397 ASSERT(move_count <= allocated_objects);
398 ASSERT(move_size <= allocated_size);
399 ASSERT(delete_count == 0);
400 ASSERT(delete_size == 0);
401 delete_count = allocated_objects - move_count;
402 delete_size = allocated_size - move_size;
403 }
404 this->mem_stats_.template RecordSizeMovedTenured<atomic>(move_size);
405 this->mem_stats_.template RecordCountMovedTenured<atomic>(move_count);
406 this->mem_stats_.template RecordSizeFreedTenured<atomic>(delete_size);
407 this->mem_stats_.template RecordCountFreedTenured<atomic>(delete_count);
408 }
409 }
410
411 template <class LanguageConfig>
InitWorker(void ** worker_data)412 bool G1GC<LanguageConfig>::InitWorker(void **worker_data)
413 {
414 os::memory::LockHolder lock(unassigned_buffers_lock_);
415 ASSERT(!unassigned_buffers_.empty());
416 *worker_data = unassigned_buffers_.back();
417 unassigned_buffers_.pop_back();
418 return true;
419 }
420
421 template <class LanguageConfig>
DestroyWorker(void * worker_data)422 void G1GC<LanguageConfig>::DestroyWorker(void *worker_data)
423 {
424 os::memory::LockHolder lock(unassigned_buffers_lock_);
425 unassigned_buffers_.push_back(reinterpret_cast<RefVector *>(worker_data));
426 }
427
428 template <class LanguageConfig>
WorkerTaskProcessing(GCWorkersTask * task,void * worker_data)429 void G1GC<LanguageConfig>::WorkerTaskProcessing(GCWorkersTask *task, void *worker_data)
430 {
431 switch (task->GetType()) {
432 case GCWorkersTaskTypes::TASK_MARKING: {
433 auto objects_stack = task->GetMarkingStack();
434 MarkStackMixed(objects_stack);
435 ASSERT(objects_stack->Empty());
436 this->GetInternalAllocator()->Delete(objects_stack);
437 break;
438 }
439 case GCWorkersTaskTypes::TASK_REMARK: {
440 const ReferenceCheckPredicateT &ref_disable_pred = [this]([[maybe_unused]] const ObjectHeader *obj) {
441 // dont process refs on conc-mark in G1, it can cause a high pause
442 LOG(DEBUG, REF_PROC) << "Skip reference: " << obj
443 << " because it's G1 with phase: " << static_cast<int>(this->GetGCPhase());
444 return false;
445 };
446 auto objects_stack = task->GetMarkingStack();
447 this->MarkStack(&marker_, objects_stack, ref_disable_pred, calc_live_bytes_);
448 ASSERT(objects_stack->Empty());
449 this->GetInternalAllocator()->Delete(objects_stack);
450 break;
451 }
452 case GCWorkersTaskTypes::TASK_REGION_COMPACTING: {
453 auto data = task->GetRegionData();
454 PandaVector<ObjectHeader *> *moved_objects = data->first;
455 Region *region = data->second;
456 if (region->HasFlag(RegionFlag::IS_EDEN)) {
457 RegionCompactingImpl<true, RegionFlag::IS_EDEN>(moved_objects, region);
458 } else if (region->HasFlag(RegionFlag::IS_OLD)) {
459 RegionCompactingImpl<true, RegionFlag::IS_OLD>(moved_objects, region);
460 } else {
461 LOG(FATAL, GC) << "Unsupported region type";
462 }
463 this->GetInternalAllocator()->Delete(data);
464 break;
465 }
466 case GCWorkersTaskTypes::TASK_INIT_REFS_FROM_REMSETS: {
467 CardTable::CardPtr card = task->GetCard();
468 auto mem_range = this->GetCardTable()->GetMemoryRange(card);
469 Region *region = AddrToRegion(ToVoidPtr(mem_range.GetStartAddress()));
470 ASSERT(!region->HasFlag(IS_EDEN));
471 auto *refs_from_remsets = reinterpret_cast<RefVector *>(worker_data);
472 CollectRefsFromCard(card, region, refs_from_remsets);
473 break;
474 }
475 default:
476 LOG(FATAL, GC) << "Unimplemented for " << GCWorkersTaskTypesToString(task->GetType());
477 UNREACHABLE();
478 }
479 }
480
481 template <class LanguageConfig>
UpdateCollectionSet(const CollectionSet & collectible_regions)482 void G1GC<LanguageConfig>::UpdateCollectionSet(const CollectionSet &collectible_regions)
483 {
484 collection_set_ = collectible_regions;
485 for (auto r : collection_set_) {
486 // we don't need to reset flag, because we don't reuse collection_set region
487 r->AddFlag(RegionFlag::IS_COLLECTION_SET);
488 LOG_DEBUG_GC << "dump region: " << *r;
489 }
490 }
491
492 template <class LanguageConfig>
RunPhasesForRegions(panda::GCTask & task,const CollectionSet & collectible_regions)493 void G1GC<LanguageConfig>::RunPhasesForRegions(panda::GCTask &task, const CollectionSet &collectible_regions)
494 {
495 if (collectible_regions.empty()) {
496 LOG_DEBUG_GC << "No regions specified for collection " << task.reason_;
497 }
498 ASSERT(concurrent_marking_stack_.Empty());
499 this->GetObjectGenAllocator()->InvalidateSpaceData();
500 this->GetObjectGenAllocator()->UpdateSpaceData();
501 RunGC(task, collectible_regions);
502 collection_set_ = CollectionSet(); // We can use a pointer to vector here
503 }
504
505 template <class LanguageConfig>
RunFullForTenured(panda::GCTask & task)506 void G1GC<LanguageConfig>::RunFullForTenured(panda::GCTask &task)
507 {
508 this->SetFullGC(true);
509 // At this point young is empty, we can mark+collect+move tenured as a whole
510 // A single free tenured region is enough to compact the whole tenured space
511 ASSERT(this->GetG1ObjectAllocator()->GetYoungRegions().empty());
512 ASSERT(HaveEnoughRegionsToMove(1));
513 LOG_DEBUG_GC << "Running Full gc for tenured";
514 auto tenured_set = GetCollectibleRegions(task, false);
515 RunFullMarkAndProcessRefs(task, tenured_set);
516
517 // Collect regions 1 by 1
518 RunPhasesForRegions(task, tenured_set);
519 }
520
521 template <class LanguageConfig>
RunFullMarkAndProcessRefs(panda::GCTask & task,const CollectionSet & collectible_regions)522 void G1GC<LanguageConfig>::RunFullMarkAndProcessRefs(panda::GCTask &task, const CollectionSet &collectible_regions)
523 {
524 this->SetFullGC(true);
525 LOG_DEBUG_GC << "Mark regions set size:" << collectible_regions.size();
526 UpdateCollectionSet(collectible_regions);
527 StartMarking<false>(task);
528 for (auto region : collectible_regions) {
529 region->RmvFlag(RegionFlag::IS_COLLECTION_SET);
530 }
531 collection_set_ = CollectionSet(); // We can use pointer to vector here
532 }
533
534 template <class LanguageConfig>
RunFullProcessRefsNoCollect(panda::GCTask & task)535 void G1GC<LanguageConfig>::RunFullProcessRefsNoCollect(panda::GCTask &task)
536 {
537 this->SetFullGC(true);
538 LOG_DEBUG_GC << "Scannig full heap and process references";
539 auto scan_set = GetCollectibleRegions(task, false);
540 RunFullMarkAndProcessRefs(task, scan_set);
541 }
542
543 template <class LanguageConfig>
RunPhasesImpl(panda::GCTask & task)544 void G1GC<LanguageConfig>::RunPhasesImpl(panda::GCTask &task)
545 {
546 interrupt_concurrent_flag_ = false;
547 LOG_DEBUG_GC << "G1GC start, reason: " << task.reason_;
548 LOG_DEBUG_GC << "Footprint before GC: " << this->GetPandaVm()->GetMemStats()->GetFootprintHeap();
549 task.UpdateGCCollectionType(GCCollectionType::YOUNG);
550
551 uint64_t young_total_time {0};
552 this->GetTiming()->Reset();
553 size_t bytes_in_heap_before_move = this->GetPandaVm()->GetMemStats()->GetFootprintHeap();
554 {
555 ScopedTiming t("G1 GC", *this->GetTiming());
556 {
557 GCScopedPauseStats scoped_pause_stats(this->GetPandaVm()->GetGCStats());
558 this->mem_stats_.Reset();
559 if ((task.reason_ == GCTaskCause::YOUNG_GC_CAUSE) || (task.reason_ == GCTaskCause::OOM_CAUSE) ||
560 (task.reason_ == GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE) ||
561 (task.reason_ == GCTaskCause::STARTUP_COMPLETE_CAUSE) ||
562 (task.reason_ == GCTaskCause::EXPLICIT_CAUSE) || (task.reason_ == GCTaskCause::NATIVE_ALLOC_CAUSE)) {
563 this->GetPandaVm()->GetMemStats()->RecordGCPauseStart();
564 // Check there is no concurrent mark running by another thread.
565 // Atomic with relaxed order reason: concurrent access with another thread which can running GC now
566 ASSERT(!concurrent_marking_flag_.load(std::memory_order_relaxed));
567
568 if ((task.reason_ == GCTaskCause::EXPLICIT_CAUSE) || (task.reason_ == GCTaskCause::OOM_CAUSE)) {
569 this->SetFullGC(true);
570 }
571 WaitForUpdateRemsetThread();
572 // Atomic with acquire order reason: to see changes made by GC thread (which do concurrent marking and
573 // than set is_mixed_gc_required_) in mutator thread which waits for the end of concurrent marking.
574 auto collectible_regions =
575 GetCollectibleRegions(task, is_mixed_gc_required_.load(std::memory_order_acquire));
576 if (this->IsFullGC()) {
577 LOG_DEBUG_GC << "Explicit Full GC invocation due to a reason: " << task.reason_;
578 // Clear young first. We tried to maintain enough regions for that
579 // If the cause is OOM - most likely it is not true and GC will give up
580 // GCing Young can be done using usual young collection routines
581 // so no need to launch it with "full" flag.
582 this->SetFullGC(false);
583 // Issue 8183: Remove unnessesary SetYoungFullGC check after refactoring Full GC
584 SetYoungFullGC(true);
585 auto g1_allocator = this->GetG1ObjectAllocator();
586 CollectionSet young_set(g1_allocator->GetYoungRegions());
587 if (!young_set.empty() && HaveEnoughSpaceToMove(young_set)) {
588 LOG_DEBUG_GC << "Collect young-space, size:" << young_set.size();
589 UpdateCollectionSet(young_set);
590 RunPhasesForRegions(task, young_set);
591 }
592 // Issue 8183: Remove unnessesary SetYoungFullGC check after refactoring Full GC
593 SetYoungFullGC(false);
594
595 // To efficiently get rid of garbage, we only work with tenured space, doing full mark
596 if (this->GetG1ObjectAllocator()->GetYoungRegions().empty() && HaveEnoughRegionsToMove(1)) {
597 RunFullForTenured(task);
598 } else {
599 // We cannot move or delete any garbage at this point
600 // However, some languages require some types of references being processed
601 // at OOM. That is possible since it doesn't require any free space
602 RunFullProcessRefsNoCollect(task);
603 LOG_INFO_GC << "Failed to run gc, not enough free regions";
604 LOG_INFO_GC << "Accounted total object used bytes = "
605 << PoolManager::GetMmapMemPool()->GetObjectUsedBytes();
606 }
607 } else if (!collectible_regions.empty() && HaveEnoughSpaceToMove(collectible_regions)) {
608 // Ordinary collection flow
609 time::Timer timer(&young_total_time, true);
610 LOG_DEBUG_GC << "Collect regions size:" << collectible_regions.size();
611 UpdateCollectionSet(collectible_regions);
612 RunPhasesForRegions(task, collectible_regions);
613 if (!HaveEnoughSpaceToMove(collectible_regions)) {
614 LOG_DEBUG_GC << "Not leaving enough regions for next young/mixed collection. Proceed to "
615 "iterative implicit Full GC";
616 // Slow path, full GC. We're short on free regions. In order to prevent OOM at the next GC,
617 // try to free up enough regions so we can do mixed/young once again next time.
618 // Here, we have a chance to compact heap so at the next GC mixed is going to have enough
619 // regions to move to tenured. Without this step, we won't be able to do full at any time, since
620 // we permanently won't have enough regions to move young to, thus not collecting anything
621 RunFullForTenured(task);
622 }
623 if (!HaveEnoughSpaceToMove(collectible_regions)) {
624 LOG_DEBUG_GC << "Implicit Full GC failed to free up enough space. Expect OOM GC soon";
625 LOG_DEBUG_GC << "Accounted total object used bytes = "
626 << PoolManager::GetMmapMemPool()->GetObjectUsedBytes();
627 }
628 if (young_total_time > 0) {
629 this->GetStats()->AddTimeValue(young_total_time, TimeTypeStats::YOUNG_TOTAL_TIME);
630 }
631 } else {
632 LOG_DEBUG_GC << "Failed to run gc: "
633 << (collectible_regions.empty() ? "nothing to collect in movable space"
634 : "not enough free regions to move");
635 }
636 this->GetPandaVm()->GetMemStats()->RecordGCPauseEnd();
637 }
638 }
639 // Atomic with acquire order reason: to see changes made by GC thread (which do concurrent marking and than set
640 // is_mixed_gc_required_) in mutator thread which waits for the end of concurrent marking.
641 if (is_mixed_gc_required_.load(std::memory_order_acquire)) {
642 if (!HaveGarbageRegions()) {
643 // Atomic with release order reason: to see changes made by GC thread (which do concurrent marking and
644 // than set is_mixed_gc_required_) in mutator thread which waits for the end of concurrent marking.
645 is_mixed_gc_required_.store(false, std::memory_order_release);
646 }
647 } else if (!interrupt_concurrent_flag_ && this->ShouldRunTenuredGC(task)) {
648 ASSERT(collection_set_.empty());
649 // Init concurrent marking
650 concurrent_marking_flag_ = true;
651 }
652 if (concurrent_marking_flag_ && !interrupt_concurrent_flag_) {
653 StartMarking<true>(task);
654 concurrent_marking_flag_ = false;
655 // interrupt_concurrent_flag_ may be set during concurrent marking.
656 if (!interrupt_concurrent_flag_) {
657 Remark(task);
658 // Enable mixed GC
659 if (HaveGarbageRegions()) {
660 // Atomic with release order reason: to see changes made by GC thread (which do concurrent marking
661 // and than set is_mixed_gc_required_) in mutator thread which waits for the end of concurrent
662 // marking.
663 is_mixed_gc_required_.store(true, std::memory_order_release);
664 }
665 {
666 ConcurrentScope concurrent_scope(this);
667 CollectNonRegularObjects<true, true>(task);
668 }
669 } else {
670 ClearSatb();
671 }
672 }
673 }
674 // Update global and GC memstats based on generational memstats information
675 // We will update tenured stats and record allocations, so set 'true' values
676 this->UpdateMemStats(bytes_in_heap_before_move, true, true);
677
678 LOG_DEBUG_GC << "Footprint after GC: " << this->GetPandaVm()->GetMemStats()->GetFootprintHeap();
679 this->SetFullGC(false);
680 }
681
682 template <class LanguageConfig>
HaveGarbageRegions()683 bool G1GC<LanguageConfig>::HaveGarbageRegions()
684 {
685 // Use GetTopGarbageRegions because it doesn't return current regions
686 PandaVector<Region *> regions = GetG1ObjectAllocator()->template GetTopGarbageRegions<false>(1U);
687 if (regions.empty()) {
688 return false;
689 }
690 double garbage_rate = static_cast<double>(regions[0]->GetGarbageBytes()) / regions[0]->Size();
691 return garbage_rate >= region_garbage_rate_threshold_;
692 }
693
694 template <class LanguageConfig>
InitializeImpl()695 void G1GC<LanguageConfig>::InitializeImpl()
696 {
697 // GC saved the PandaVM instance, so we get allocator from the PandaVM.
698 InternalAllocatorPtr allocator = this->GetInternalAllocator();
699 this->CreateCardTable(allocator, PoolManager::GetMmapMemPool()->GetMinObjectAddress(),
700 PoolManager::GetMmapMemPool()->GetTotalObjectSize());
701
702 // TODO(dtrubenkov): initialize barriers
703 auto barrier_set = allocator->New<GCG1BarrierSet>(
704 allocator, &concurrent_marking_flag_, &PreWrbFuncEntrypoint, &PostWrbUpdateCardFuncEntrypoint,
705 panda::helpers::math::GetIntLog2(this->GetG1ObjectAllocator()->GetRegionSize()), this->GetCardTable(),
706 updated_refs_queue_, &queue_lock_);
707 ASSERT(barrier_set != nullptr);
708 this->SetGCBarrierSet(barrier_set);
709
710 if (this->IsWorkerThreadsExist()) {
711 auto thread_pool = allocator->New<GCWorkersThreadPool>(allocator, this, this->GetSettings()->GCWorkersCount());
712 ASSERT(thread_pool != nullptr);
713 this->SetWorkersPool(thread_pool);
714 }
715 {
716 // to make TSAN happy because we access updated_refs_queue_ inside constructor of UpdateRemsetThread
717 os::memory::LockHolder lock(queue_lock_);
718 update_remset_thread_ = allocator->template New<UpdateRemsetThread<LanguageConfig>>(
719 this, this->GetPandaVm(), updated_refs_queue_, &queue_lock_, this->GetG1ObjectAllocator()->GetRegionSize(),
720 this->GetSettings()->G1EnableConcurrentUpdateRemset(), this->GetSettings()->G1MinConcurrentCardsToProcess(),
721 this->GetCardTable());
722 }
723 ASSERT(update_remset_thread_ != nullptr);
724 LOG_DEBUG_GC << "G1GC initialized";
725 }
726
727 template <class LanguageConfig>
MarkObject(ObjectHeader * object)728 void G1GC<LanguageConfig>::MarkObject(ObjectHeader *object)
729 {
730 marker_.Mark(object);
731 }
732
733 template <class LanguageConfig>
MarkObjectIfNotMarked(ObjectHeader * object)734 bool G1GC<LanguageConfig>::MarkObjectIfNotMarked(ObjectHeader *object)
735 {
736 ASSERT(object != nullptr);
737 return marker_.MarkIfNotMarked(object);
738 }
739
740 template <class LanguageConfig>
InitGCBitsForAllocationInTLAB(panda::ObjectHeader * object)741 void G1GC<LanguageConfig>::InitGCBitsForAllocationInTLAB([[maybe_unused]] panda::ObjectHeader *object)
742 {
743 LOG(FATAL, GC) << "Not implemented";
744 }
745
746 template <class LanguageConfig>
IsMarked(panda::ObjectHeader const * object) const747 bool G1GC<LanguageConfig>::IsMarked(panda::ObjectHeader const *object) const
748 {
749 return marker_.IsMarked(object);
750 }
751
752 template <class LanguageConfig>
MarkStackMixed(GCMarkingStackType * stack)753 void G1GC<LanguageConfig>::MarkStackMixed(GCMarkingStackType *stack)
754 {
755 auto ref_pred = [this](const ObjectHeader *obj) { return InGCSweepRange(obj); };
756 auto mark_pred = [this](const ObjectHeader *obj) { return InGCSweepRange(obj); };
757 this->MarkStack(&marker_, stack, ref_pred, mark_pred);
758 }
759
760 template <class LanguageConfig>
MarkStackFull(GCMarkingStackType * stack)761 void G1GC<LanguageConfig>::MarkStackFull(GCMarkingStackType *stack)
762 {
763 auto ref_pred = []([[maybe_unused]] const ObjectHeader *obj) { return true; };
764 auto mark_pred = []([[maybe_unused]] const ObjectHeader *obj) { return true; };
765 this->MarkStack(&marker_, stack, ref_pred, mark_pred);
766 }
767
768 template <class LanguageConfig>
MarkReferences(GCMarkingStackType * references,GCPhase gc_phase)769 void G1GC<LanguageConfig>::MarkReferences(GCMarkingStackType *references, GCPhase gc_phase)
770 {
771 trace::ScopedTrace scoped_trace(__FUNCTION__);
772 LOG_DEBUG_GC << "Start marking " << references->Size() << " references";
773 // mark refs only on mixed-gc and on full_gc. On concurrent mark we don't handle any references
774 if (gc_phase == GCPhase::GC_PHASE_MARK_YOUNG || this->IsFullGC()) {
775 MarkStackMixed(references);
776 } else if (gc_phase == GCPhase::GC_PHASE_INITIAL_MARK || gc_phase == GCPhase::GC_PHASE_MARK ||
777 gc_phase == GCPhase::GC_PHASE_REMARK) {
778 // nothing
779 } else {
780 LOG_DEBUG_GC << "phase: " << GCScopedPhase::GetPhaseName(gc_phase);
781 UNREACHABLE();
782 }
783 }
784
785 template <class LanguageConfig>
InGCSweepRange(const ObjectHeader * object) const786 bool G1GC<LanguageConfig>::InGCSweepRange(const ObjectHeader *object) const
787 {
788 [[maybe_unused]] auto phase = this->GetGCPhase();
789 ASSERT_DO(!this->collection_set_.empty() || this->IsFullGC() || phase == GCPhase::GC_PHASE_REMARK,
790 std::cerr << "Incorrect phase in InGCSweepRange: " << static_cast<size_t>(phase) << "\n");
791
792 ASSERT(PoolManager::GetMmapMemPool()->GetSpaceTypeForAddr(object) == SpaceType::SPACE_TYPE_OBJECT ||
793 PoolManager::GetMmapMemPool()->GetSpaceTypeForAddr(object) == SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT ||
794 PoolManager::GetMmapMemPool()->GetSpaceTypeForAddr(object) == SpaceType::SPACE_TYPE_HUMONGOUS_OBJECT);
795 Region *obj_region = ObjectToRegion(object);
796 return obj_region->IsInCollectionSet();
797 }
798
799 template <class LanguageConfig>
RunGC(GCTask & task,const CollectionSet & collectible_regions)800 void G1GC<LanguageConfig>::RunGC(GCTask &task, const CollectionSet &collectible_regions)
801 {
802 GCScope<TRACE_TIMING> scoped_trace(__FUNCTION__, this);
803 LOG_DEBUG_GC << "GC start";
804 uint64_t young_pause_time;
805 {
806 // TODO: Measure only those that are on pause
807 time::Timer timer(&young_pause_time, true);
808 if (is_mixed_gc_required_) {
809 LOG_DEBUG_GC << "Mixed GC";
810 task.collection_type_ = GCCollectionType::MIXED;
811 } else {
812 task.collection_type_ = GCCollectionType::YOUNG;
813 }
814
815 if (!this->IsFullGC()) {
816 CacheRefsFromRemsets();
817 MixedMark(task, collectible_regions);
818 CollectAndMove(collectible_regions);
819 this->GetCardTable()->ClearAll();
820 ClearRefsFromRemsetsCache();
821 } else {
822 task.collection_type_ = GCCollectionType::FULL;
823 for (auto r : collectible_regions) {
824 LOG_DEBUG_GC << "Iterative full GC collecting region " << *r;
825 CollectionSet cs {};
826 cs.AddRegion(r);
827 UpdateCollectionSet(cs);
828 CacheRefsFromRemsets();
829 CollectAndMove(cs);
830 ClearRefsFromRemsetsCache();
831 this->GetCardTable()->ClearAll();
832 }
833 }
834 this->GetObjectGenAllocator()->InvalidateSpaceData();
835 }
836 if (young_pause_time > 0) {
837 this->GetStats()->AddTimeValue(young_pause_time, TimeTypeStats::YOUNG_PAUSED_TIME);
838 }
839 LOG_DEBUG_GC << "G1GC RunGC end";
840 }
841
842 template <class LanguageConfig>
MixedMark(const GCTask & task,const CollectionSet & collectible_regions)843 void G1GC<LanguageConfig>::MixedMark(const GCTask &task, const CollectionSet &collectible_regions)
844 {
845 GCScope<TRACE_TIMING_PHASE> scoped_trace(__FUNCTION__, this, GCPhase::GC_PHASE_MARK_YOUNG);
846
847 bool use_gc_workers = this->GetSettings()->ParallelMarkingEnabled();
848
849 GCMarkingStackType objects_stack(this, use_gc_workers ? this->GetSettings()->GCRootMarkingStackMaxSize() : 0,
850 use_gc_workers ? this->GetSettings()->GCWorkersMarkingStackMaxSize() : 0,
851 GCWorkersTaskTypes::TASK_MARKING);
852 // Iterate over roots and add other roots
853 // 0. Pre-process refs queue and fill RemSets (should be done later in background)
854 // Note: We need to process only tenured -> young refs,
855 // since we reach this by graph from tenured roots,
856 // because we will process all young regions at young GC we will find all required references
857
858 ASSERT(this->GetReferenceProcessor()->GetReferenceQueueSize() ==
859 0); // all references should be processed on previous-gc
860
861 auto ref_pred = [this](const ObjectHeader *obj) { return this->InGCSweepRange(obj); };
862 GCRootVisitor gc_mark_collection_set = [&objects_stack, this, &ref_pred](const GCRoot &gc_root) {
863 ObjectHeader *root_object = gc_root.GetObjectHeader();
864 ObjectHeader *from_object = gc_root.GetFromObjectHeader();
865 LOG_DEBUG_GC << "Handle root " << GetDebugInfoAboutObject(root_object) << " from: " << gc_root.GetType();
866 if (UNLIKELY(from_object != nullptr) &&
867 this->IsReference(from_object->ClassAddr<BaseClass>(), from_object, ref_pred)) {
868 LOG_DEBUG_GC << "Add reference: " << GetDebugInfoAboutObject(from_object) << " to stack";
869 marker_.Mark(from_object);
870 this->ProcessReference(&objects_stack, from_object->ClassAddr<BaseClass>(), from_object,
871 GC::EmptyReferenceProcessPredicate);
872 } else {
873 // Skip non-collection-set roots
874 auto root_object_ptr = gc_root.GetObjectHeader();
875 ASSERT(root_object_ptr != nullptr);
876 if (this->InGCSweepRange(root_object_ptr)) {
877 LOG_DEBUG_GC << "root " << GetDebugInfoAboutObject(root_object_ptr);
878 if (marker_.MarkIfNotMarked(root_object_ptr)) {
879 objects_stack.PushToStack(gc_root.GetType(), root_object_ptr);
880 }
881 } else {
882 LOG_DEBUG_GC << "Skip root for young mark: " << std::hex << root_object_ptr;
883 }
884 }
885 };
886 {
887 GCScope<TRACE_TIMING> marking_collection_set_roots_trace("Marking roots collection-set", this);
888
889 for (Region *region : collectible_regions) {
890 region->GetMarkBitmap()->ClearAllBits();
891 }
892
893 this->VisitRoots(gc_mark_collection_set, VisitGCRootFlags::ACCESS_ROOT_NONE);
894 // Visit roots from RemSets
895 auto field_visitor = [this, &objects_stack]([[maybe_unused]] ObjectHeader *from_obj, ObjectHeader *to_obj,
896 [[maybe_unused]] uint32_t offset,
897 [[maybe_unused]] bool is_volatile) {
898 if (!InGCSweepRange(to_obj)) {
899 LOG_DEBUG_GC << "Skip root for collection_set mark: " << std::hex << to_obj;
900 return true;
901 }
902 LOG_DEBUG_GC << "root " << GetDebugInfoAboutObject(to_obj);
903 if (marker_.MarkIfNotMarked(to_obj)) {
904 objects_stack.PushToStack(from_obj, to_obj);
905 }
906 return true;
907 };
908 VisitRemSets(field_visitor);
909 }
910 {
911 ScopedTiming mark_stack_timing("MarkStack", *this->GetTiming());
912 this->MarkStackMixed(&objects_stack);
913 ASSERT(objects_stack.Empty());
914 if (use_gc_workers) {
915 this->GetWorkersPool()->WaitUntilTasksEnd();
916 }
917 }
918
919 auto ref_clear_pred = [this]([[maybe_unused]] const ObjectHeader *obj) { return this->InGCSweepRange(obj); };
920 this->GetPandaVm()->HandleReferences(task, ref_clear_pred);
921 // HandleReferences could write a new barriers - so we need to handle them before moving
922 WaitForUpdateRemsetThread();
923 }
924
925 template <class LanguageConfig>
CollectVerificationInfo(const CollectionSet & collection_set)926 HeapVerifierIntoGC<LanguageConfig> G1GC<LanguageConfig>::CollectVerificationInfo(const CollectionSet &collection_set)
927 {
928 HeapVerifierIntoGC<LanguageConfig> collect_verifier(this->GetPandaVm()->GetHeapManager());
929 if (this->GetSettings()->IntoGCHeapVerification()) {
930 ScopedTiming collect_verification_timing(__FUNCTION__, *this->GetTiming());
931 PandaVector<MemRange> mem_ranges;
932 mem_ranges.reserve(collection_set.size());
933 std::for_each(collection_set.begin(), collection_set.end(),
934 [&mem_ranges](const Region *region) { mem_ranges.emplace_back(region->Begin(), region->End()); });
935 collect_verifier.CollectVerificationInfo(std::move(mem_ranges));
936 }
937 return collect_verifier;
938 }
939
940 template <class LanguageConfig>
VerifyCollectAndMove(HeapVerifierIntoGC<LanguageConfig> && collect_verifier,const CollectionSet & collection_set)941 void G1GC<LanguageConfig>::VerifyCollectAndMove(HeapVerifierIntoGC<LanguageConfig> &&collect_verifier,
942 const CollectionSet &collection_set)
943 {
944 if (this->GetSettings()->IntoGCHeapVerification()) {
945 ScopedTiming verification_timing(__FUNCTION__, *this->GetTiming());
946 PandaVector<MemRange> alive_mem_range;
947 std::for_each(collection_set.begin(), collection_set.end(), [&alive_mem_range](const Region *region) {
948 if (region->HasFlag(RegionFlag::IS_PROMOTED)) {
949 alive_mem_range.emplace_back(region->Begin(), region->End());
950 }
951 });
952 size_t fails_count = collect_verifier.VerifyAll(std::move(alive_mem_range));
953 if (this->GetSettings()->FailOnHeapVerification() && fails_count > 0U) {
954 LOG(FATAL, GC) << "Heap was corrupted during CollectAndMove GC phase, HeapVerifier found " << fails_count
955 << " corruptions";
956 }
957 }
958 }
959
960 template <class LanguageConfig>
961 // NOLINTNEXTLINE(readability-function-size)
CollectAndMove(const CollectionSet & collection_set)962 bool G1GC<LanguageConfig>::CollectAndMove(const CollectionSet &collection_set)
963 {
964 GCScope<TRACE_PHASE> scope(__FUNCTION__, this, GCPhase::GC_PHASE_COLLECT_YOUNG_AND_MOVE);
965 LOG_DEBUG_GC << "== G1GC CollectAndMove start ==";
966 auto internal_allocator = this->GetInternalAllocator();
967 bool use_gc_workers = this->GetSettings()->ParallelCompactingEnabled() && !this->IsFullGC();
968
969 PandaVector<PandaVector<ObjectHeader *> *> moved_objects_vector;
970 HeapVerifierIntoGC<LanguageConfig> collect_verifier = this->CollectVerificationInfo(collection_set);
971 {
972 ScopedTiming compact_regions("CompactRegions", *this->GetTiming());
973 if (!use_gc_workers) {
974 auto vector = internal_allocator->template New<PandaVector<ObjectHeader *>>();
975 moved_objects_vector.push_back(vector);
976 }
977 for (auto r : collection_set.Young()) {
978 this->DoRegionCompacting<RegionFlag::IS_EDEN>(r, use_gc_workers, &moved_objects_vector);
979 }
980 for (auto r : collection_set.Tenured()) {
981 this->DoRegionCompacting<RegionFlag::IS_OLD>(r, use_gc_workers, &moved_objects_vector);
982 }
983
984 if (use_gc_workers) {
985 this->GetWorkersPool()->WaitUntilTasksEnd();
986 }
987 }
988
989 PandaVector<Region *> tenured_regions(collection_set.Tenured().begin(), collection_set.Tenured().end());
990 UpdateRefsToMovedObjects(&moved_objects_vector);
991 this->VerifyCollectAndMove(std::move(collect_verifier), collection_set);
992 this->SweepStringTableYoung([this](ObjectHeader *obj) { return this->InGCSweepRange(obj); });
993
994 ActualizeRemSets();
995
996 auto object_allocator = this->GetG1ObjectAllocator();
997 object_allocator->ResetYoungAllocator();
998 object_allocator->template ResetRegions<RegionFlag::IS_OLD>(tenured_regions);
999
1000 // Don't forget to delete all temporary elements
1001 if (use_gc_workers) {
1002 for (auto r : moved_objects_vector) {
1003 internal_allocator->Delete(r);
1004 }
1005 } else {
1006 ASSERT(moved_objects_vector.size() == 1);
1007 internal_allocator->Delete(moved_objects_vector.back());
1008 }
1009
1010 LOG_DEBUG_GC << "== G1GC CollectAndMove end ==";
1011 return true;
1012 }
1013
1014 template <class LanguageConfig>
UpdateRefsToMovedObjects(PandaVector<PandaVector<ObjectHeader * > * > * moved_objects_vector)1015 void G1GC<LanguageConfig>::UpdateRefsToMovedObjects(PandaVector<PandaVector<ObjectHeader *> *> *moved_objects_vector)
1016 {
1017 GCScope<TRACE_TIMING> scope("UpdateRefsToMovedObjects", this);
1018 auto object_allocator = this->GetG1ObjectAllocator();
1019 size_t region_size_bits = panda::helpers::math::GetIntLog2(object_allocator->GetRegionSize());
1020 auto update_refs = [region_size_bits](ObjectHeader *object, ObjectHeader *ref, uint32_t offset, bool is_volatile) {
1021 ObjectHeader *forwarded =
1022 ObjectHelpers<LanguageConfig::LANG_TYPE>::UpdateRefToMovedObject(object, ref, offset, is_volatile);
1023 if (((ToUintPtr(object) ^ ToUintPtr(forwarded)) >> region_size_bits) != 0) {
1024 RemSet<>::AddRefWithAddr<false>(object, forwarded);
1025 }
1026 return true;
1027 };
1028
1029 // Update references exyoung -> young
1030 LOG_DEBUG_GC << "=== Update exyoung -> young references. START. ===";
1031 for (auto moved_objects : *moved_objects_vector) {
1032 for (auto obj : *moved_objects) {
1033 ObjectHelpers<LanguageConfig::LANG_TYPE>::TraverseAllObjectsWithInfo(obj, update_refs);
1034 }
1035 }
1036
1037 LOG_DEBUG_GC << "=== Update exyoung -> young references. END. ===";
1038 // update references tenured -> young
1039 LOG_DEBUG_GC << "=== Update tenured -> young references. START. ===";
1040
1041 VisitRemSets(update_refs);
1042 LOG_DEBUG_GC << "=== Update tenured -> young references. END. ===";
1043 this->CommonUpdateRefsToMovedObjects();
1044 }
1045
1046 template <class LanguageConfig>
1047 template <bool is_concurrent>
StartMarking(panda::GCTask & task)1048 void G1GC<LanguageConfig>::StartMarking(panda::GCTask &task)
1049 {
1050 auto object_allocator = GetG1ObjectAllocator();
1051 PandaString mark_type;
1052 // NOLINTNEXTLINE(readability-braces-around-statements)
1053 if constexpr (is_concurrent) {
1054 mark_type = "Concurrent";
1055 } else { // NOLINT(readability-misleading-indentation)
1056 mark_type = "OnPause";
1057 }
1058
1059 {
1060 // First we need to unmark all heap
1061 ScopedTiming un_mark_timing("UnMark", *this->GetTiming());
1062 LOG_DEBUG_GC << "Start unmark all heap before " << mark_type << " mark";
1063 auto all_region = object_allocator->GetAllRegions();
1064 for (Region *r : all_region) {
1065 auto *bitmap = r->GetMarkBitmap();
1066 // unmark full-heap except Humongous-space
1067 bitmap->ClearAllBits();
1068 }
1069 #ifndef NDEBUG
1070 this->GetObjectAllocator()->IterateOverObjects(
1071 [this](ObjectHeader *obj) { ASSERT(!this->marker_.IsMarked(obj)); });
1072 #endif
1073 }
1074 ASSERT(this->GetReferenceProcessor()->GetReferenceQueueSize() ==
1075 0); // all references should be processed on mixed-gc
1076 LOG_DEBUG_GC << mark_type << " marking started";
1077 {
1078 GCScopedPhase scoped_phase(this->GetPandaVm()->GetMemStats(), this, GCPhase::GC_PHASE_INITIAL_MARK);
1079 // Collect non-heap roots.
1080 // Mark the whole heap by using only these roots.
1081 // The interregion roots will be processed at pause
1082
1083 // InitialMark. STW
1084 ASSERT(concurrent_marking_stack_.Empty());
1085 auto &conc_stack = concurrent_marking_stack_;
1086 GCRootVisitor gc_mark_roots = [this, &conc_stack](const GCRoot &gc_root) {
1087 ValidateObject(gc_root.GetType(), gc_root.GetObjectHeader());
1088 if (marker_.MarkIfNotMarked(gc_root.GetObjectHeader())) {
1089 conc_stack.PushToStack(gc_root.GetType(), gc_root.GetObjectHeader());
1090 }
1091 };
1092 this->VisitRoots(gc_mark_roots, VisitGCRootFlags::ACCESS_ROOT_ALL);
1093 }
1094 // Concurrent/on-pause marking
1095 {
1096 this->GetPandaVm()->GetMemStats()->RecordGCPauseEnd();
1097 // NOLINTNEXTLINE(readability-braces-around-statements)
1098 if constexpr (is_concurrent) {
1099 const ReferenceCheckPredicateT &disable_ref_pred = []([[maybe_unused]] const ObjectHeader *obj) {
1100 return false;
1101 };
1102 auto interrupt_checker = [this]() {
1103 return !concurrent_marking_stack_.Empty() && !interrupt_concurrent_flag_;
1104 };
1105 ConcurrentMark(
1106 &marker_, &concurrent_marking_stack_, CardTableVisitFlag::VISIT_DISABLED, interrupt_checker,
1107 disable_ref_pred, [&object_allocator](MemRange &mem_range) {
1108 return !object_allocator->IsIntersectedWithYoung( // CODECHECK-NOLINT(C_RULE_ID_INDENT_CHECK)
1109 mem_range); // CODECHECK-NOLINT(C_RULE_ID_INDENT_CHECK)
1110 }); // CODECHECK-NOLINT(C_RULE_ID_INDENT_CHECK)
1111 // weak refs shouldn't be added to the queue on concurrent-mark
1112 ASSERT(this->GetReferenceProcessor()->GetReferenceQueueSize() == 0);
1113 } else { // NOLINT(readability-misleading-indentation)
1114 const ReferenceCheckPredicateT &ref_pred = []([[maybe_unused]] const ObjectHeader *obj) { return true; };
1115 auto no_concurrent_interrupt_checker = [this]() { return !concurrent_marking_stack_.Empty(); };
1116 this->OnPauseMark(
1117 &marker_, &concurrent_marking_stack_, CardTableVisitFlag::VISIT_DISABLED,
1118 no_concurrent_interrupt_checker, ref_pred, [&object_allocator](MemRange &mem_range) {
1119 return !object_allocator->IsIntersectedWithYoung( // CODECHECK-NOLINT(C_RULE_ID_INDENT_CHECK)
1120 mem_range); // CODECHECK-NOLINT(C_RULE_ID_INDENT_CHECK)
1121 }); // CODECHECK-NOLINT(C_RULE_ID_INDENT_CHECK)
1122
1123 {
1124 // we process all refs on FULL_GC
1125 GCScopedPhase scoped_phase(this->GetPandaVm()->GetMemStats(), this, GCPhase::GC_PHASE_MARK);
1126 ASSERT(this->IsFullGC());
1127 auto g1_allocator = this->GetG1ObjectAllocator();
1128 auto all_regions = g1_allocator->GetAllRegions();
1129 /**
1130 * We don't collect non-movable regions right now, if there was a reference from non-movable to
1131 * young/tenured region then we reset markbitmap for non-nonmovable, but don't update livebitmap and we
1132 * can traverse over non-reachable object (in CacheRefsFromRemsets) and visit DEAD object in
1133 * tenured space (was delete on young-collection or in Iterative-full-gc phase.
1134 */
1135 // TODO(alovkov): add ASSERT(g1_allocator->GetRegions().size == collection_set_.size())
1136 auto ref_clear_pred = []([[maybe_unused]] const ObjectHeader *obj) { return true; };
1137 this->GetPandaVm()->HandleReferences(task, ref_clear_pred);
1138 // HandleReferences could write a new barriers - so we need to handle them before cloning maps
1139 WaitForUpdateRemsetThread();
1140 // TODO(alovkov): make iterations over collection_set when non-movable regions will be supported
1141 for (const auto &r : all_regions) {
1142 if (r->GetLiveBitmap() != nullptr) {
1143 r->CloneMarkBitmapToLiveBitmap();
1144 }
1145 }
1146 CollectNonRegularObjects<false, false>(task);
1147 }
1148 }
1149 if (interrupt_concurrent_flag_) {
1150 concurrent_marking_stack_.Clear();
1151 }
1152 ASSERT(concurrent_marking_stack_.Empty());
1153 this->GetPandaVm()->GetMemStats()->RecordGCPauseStart();
1154 }
1155 ASSERT(concurrent_marking_stack_.Empty());
1156 }
1157
1158 template <class LanguageConfig>
ConcurrentMark(Marker * marker,GCMarkingStackType * objects_stack,CardTableVisitFlag visit_card_table_roots,const ConcurrentMarkPredicateT & pred,const ReferenceCheckPredicateT & ref_pred,const MemRangeChecker & mem_range_checker)1159 void G1GC<LanguageConfig>::ConcurrentMark(Marker *marker, GCMarkingStackType *objects_stack,
1160 CardTableVisitFlag visit_card_table_roots,
1161 const ConcurrentMarkPredicateT &pred,
1162 const ReferenceCheckPredicateT &ref_pred,
1163 const MemRangeChecker &mem_range_checker)
1164 {
1165 GCScope<TRACE_TIMING_PHASE> scope(__FUNCTION__, this, GCPhase::GC_PHASE_MARK);
1166 ConcurrentScope concurrent_scope(this);
1167 this->MarkImpl(marker, objects_stack, visit_card_table_roots, pred, ref_pred, mem_range_checker);
1168 if (interrupt_concurrent_flag_) {
1169 return;
1170 }
1171 CalcLiveBytesForMovableTenuredRegions();
1172 }
1173
1174 template <class LanguageConfig>
Remark(panda::GCTask const & task)1175 void G1GC<LanguageConfig>::Remark(panda::GCTask const &task)
1176 {
1177 /**
1178 * Make remark on pause to have all marked objects in tenured space, it gives possibility to check objects in
1179 * remsets. If they are not marked - we don't process this object, because it's dead already
1180 */
1181 GCScope<TIMING_PHASE> gc_scope(__FUNCTION__, this, GCPhase::GC_PHASE_REMARK);
1182 // TODO(alovkov): extract method for creation stack
1183 bool use_gc_workers = this->GetSettings()->ParallelMarkingEnabled();
1184 GCMarkingStackType stack(this, use_gc_workers ? this->GetSettings()->GCRootMarkingStackMaxSize() : 0,
1185 use_gc_workers ? this->GetSettings()->GCWorkersMarkingStackMaxSize() : 0,
1186 GCWorkersTaskTypes::TASK_REMARK);
1187
1188 // The mutator may create new regions.
1189 // If so we should bind bitmaps of new regions.
1190 DrainSatb(&stack);
1191 const ReferenceCheckPredicateT &ref_disable_pred = [this]([[maybe_unused]] const ObjectHeader *obj) {
1192 LOG(DEBUG, REF_PROC) << "Skip reference: " << obj
1193 << " because it's G1 with phase: " << static_cast<int>(this->GetGCPhase());
1194 return false;
1195 };
1196 this->MarkStack(&marker_, &stack, ref_disable_pred, calc_live_bytes_);
1197
1198 if (use_gc_workers) {
1199 this->GetWorkersPool()->WaitUntilTasksEnd();
1200 }
1201 {
1202 ScopedTiming remsetThreadTiming("RemsetThread WaitUntilTasksEnd", *this->GetTiming());
1203 WaitForUpdateRemsetThread();
1204 }
1205
1206 // ConcurrentMark doesn't visit young objects - so we can't clear references which are in young-space because we
1207 // don't know which objects are marked. We will process them on young/mixed GC separately later, here we process
1208 // only refs in tenured-space
1209 auto ref_clear_pred = []([[maybe_unused]] const ObjectHeader *obj) {
1210 return !ObjectToRegion(obj)->HasFlag(RegionFlag::IS_EDEN);
1211 };
1212 this->GetPandaVm()->HandleReferences(task, ref_clear_pred);
1213
1214 auto g1_allocator = this->GetG1ObjectAllocator();
1215 auto all_regions = g1_allocator->GetAllRegions();
1216 for (const auto ®ion : all_regions) {
1217 // TODO(alovkov): set IS_OLD for NON_MOVABLE region when we create it
1218 if (region->HasFlag(IS_OLD) || region->HasFlag(IS_NONMOVABLE)) {
1219 region->SwapMarkBitmap();
1220 }
1221 }
1222 }
1223
1224 template <class LanguageConfig>
GetCollectibleRegions(panda::GCTask const & task,bool is_mixed)1225 CollectionSet G1GC<LanguageConfig>::GetCollectibleRegions(panda::GCTask const &task, bool is_mixed)
1226 {
1227 ScopedTiming scoped_timing(__FUNCTION__, *this->GetTiming());
1228 // FillRemSet should be always finished before GetCollectibleRegions
1229 ASSERT(update_remset_thread_->GetQueueSize() == 0);
1230 auto g1_allocator = this->GetG1ObjectAllocator();
1231 LOG_DEBUG_GC << "Start GetCollectibleRegions is_mixed: " << is_mixed << " reason: " << task.reason_;
1232 CollectionSet collection_set(g1_allocator->GetYoungRegions());
1233 bool is_full_gc = this->IsFullGC();
1234 if (is_mixed || is_full_gc) {
1235 if (is_full_gc) {
1236 auto all_movable_regions = g1_allocator->GetMovableRegions();
1237 LOG_DEBUG_GC << "all movable region size: " << all_movable_regions.size();
1238 for (const auto ®ion : all_movable_regions) {
1239 LOG_DEBUG_GC << "region: " << *region;
1240 if (region->HasFlag(IS_EDEN)) {
1241 continue;
1242 }
1243 ASSERT(!region->HasFlag(IS_NONMOVABLE) && !region->HasFlag(IS_LARGE_OBJECT));
1244 ASSERT(region->HasFlag(IS_OLD));
1245 collection_set.AddRegion(region);
1246 }
1247 // make new region to move objects there
1248 g1_allocator->ClearCurrentRegion();
1249 } else {
1250 auto garbage_regions = g1_allocator->template GetTopGarbageRegions<false>(number_of_mixed_tenured_regions_);
1251 for (auto garbage_region : garbage_regions) {
1252 ASSERT(!garbage_region->HasFlag(IS_EDEN));
1253 ASSERT(is_mixed_gc_required_); // to be sure that GetLiveBytes is calculated in concurrent
1254 double garbage_rate = static_cast<double>(garbage_region->GetGarbageBytes()) / garbage_region->Size();
1255 if (garbage_rate >= region_garbage_rate_threshold_) {
1256 LOG_DEBUG_GC << "Garbage percentage in " << std::hex << garbage_region << " region = " << std::dec
1257 << garbage_rate << " %, add to collection set";
1258 collection_set.AddRegion(garbage_region);
1259 } else {
1260 LOG_DEBUG_GC << "Garbage percentage in " << std::hex << garbage_region << " region = " << std::dec
1261 << garbage_rate << " %, don't add to collection set";
1262 break;
1263 }
1264 }
1265 }
1266 }
1267 LOG_DEBUG_GC << "collectible_regions size: " << collection_set.size() << " reason: " << task.reason_
1268 << " is_mixed: " << is_mixed;
1269 return collection_set;
1270 }
1271
1272 template <class LanguageConfig>
CalcLiveBytesForMovableTenuredRegions()1273 void G1GC<LanguageConfig>::CalcLiveBytesForMovableTenuredRegions()
1274 {
1275 ScopedTiming scoped_timing(__FUNCTION__, *this->GetTiming());
1276 auto object_allocator = this->GetG1ObjectAllocator();
1277 auto movable_region = object_allocator->GetMovableRegions();
1278 for (const auto ®ion : movable_region) {
1279 if (region->HasFlag(IS_OLD)) {
1280 region->SetLiveBytes(region->CalcMarkBytes());
1281 }
1282 }
1283 }
1284
1285 template <class LanguageConfig>
HaveEnoughSpaceToMove(const CollectionSet & collectible_regions)1286 bool G1GC<LanguageConfig>::HaveEnoughSpaceToMove(const CollectionSet &collectible_regions)
1287 {
1288 // Take parallel compacting into account
1289 size_t parallel_compacting_fragmentation =
1290 this->GetSettings()->ParallelCompactingEnabled() ? this->GetSettings()->GCWorkersCount() : 0;
1291 size_t required_regions = collectible_regions.Movable().size() + parallel_compacting_fragmentation;
1292 return HaveEnoughRegionsToMove(required_regions);
1293 }
1294
1295 template <class LanguageConfig>
HaveEnoughRegionsToMove(size_t num)1296 bool G1GC<LanguageConfig>::HaveEnoughRegionsToMove(size_t num)
1297 {
1298 return GetG1ObjectAllocator()->HaveTenuredSize(num) && GetG1ObjectAllocator()->HaveFreeRegions(num);
1299 }
1300
1301 template <class LanguageConfig>
OnThreadTerminate(ManagedThread * thread)1302 void G1GC<LanguageConfig>::OnThreadTerminate(ManagedThread *thread)
1303 {
1304 auto tid = thread->GetId();
1305 LOG_DEBUG_GC << "Call OnThreadTerminate for thread: " << tid;
1306 {
1307 os::memory::LockHolder lock(satb_and_newobj_buf_lock_);
1308 auto pre_buff = thread->MovePreBuff();
1309 ASSERT(pre_buff != nullptr);
1310 satb_buff_list_.push_back(pre_buff);
1311 }
1312 {
1313 os::memory::LockHolder lock(queue_lock_);
1314 auto *local_buffer = thread->GetG1PostBarrierBuffer();
1315
1316 ASSERT(local_buffer != nullptr);
1317 LOG_DEBUG_GC << "OnThreadTerminate push queue: " << local_buffer;
1318 // we need to make it under lock, because we can have situation when we made GetG1PostBarrierBuffer
1319 // -> FillRemset -> call PushCardToUpdateThread, but it's too late - thread processed all cards already
1320 auto barrier_set = static_cast<GCG1BarrierSet *>(thread->GetBarrierSet());
1321 PushCardToUpdateThread<false>(local_buffer, barrier_set);
1322 ASSERT(local_buffer->IsEmpty());
1323 thread->ResetG1PostBarrierRingBuffer();
1324 this->GetInternalAllocator()->template Delete(local_buffer);
1325 }
1326 }
1327
1328 template <class LanguageConfig>
PreZygoteFork()1329 void G1GC<LanguageConfig>::PreZygoteFork()
1330 {
1331 GC::PreZygoteFork();
1332 if (this->GetWorkersPool() != nullptr) {
1333 auto allocator = this->GetInternalAllocator();
1334 allocator->Delete(this->GetWorkersPool());
1335 this->ClearWorkersPool();
1336 }
1337 this->DisableWorkerThreads();
1338 update_remset_thread_->DestroyThread();
1339 // don't use thread while we are in zygote
1340 update_remset_thread_->SetUpdateConcurrent(false);
1341 }
1342
1343 template <class LanguageConfig>
PostZygoteFork()1344 void G1GC<LanguageConfig>::PostZygoteFork()
1345 {
1346 InternalAllocatorPtr allocator = this->GetInternalAllocator();
1347 this->EnableWorkerThreads();
1348 if (this->IsWorkerThreadsExist()) {
1349 auto thread_pool =
1350 allocator->template New<GCWorkersThreadPool>(allocator, this, this->GetSettings()->GCWorkersCount());
1351 ASSERT(thread_pool != nullptr);
1352 this->SetWorkersPool(thread_pool);
1353 }
1354 GC::PostZygoteFork();
1355 // use concurrent-option after zygote
1356 update_remset_thread_->SetUpdateConcurrent(this->GetSettings()->G1EnableConcurrentUpdateRemset());
1357 update_remset_thread_->CreateThread(allocator);
1358 }
1359
1360 template <class LanguageConfig>
DrainSatb(GCAdaptiveStack * object_stack)1361 void G1GC<LanguageConfig>::DrainSatb(GCAdaptiveStack *object_stack)
1362 {
1363 ScopedTiming scoped_timing(__FUNCTION__, *this->GetTiming());
1364 // Process satb buffers of the active threads
1365 auto callback = [this, object_stack](ManagedThread *thread) {
1366 // Acquire lock here to avoid data races with the threads
1367 // which are terminating now.
1368 // Data race is happens in thread.pre_buf_. The terminating thread may
1369 // release own pre_buf_ while GC thread iterates over threads and gets theirs
1370 // pre_buf_.
1371 os::memory::LockHolder lock(satb_and_newobj_buf_lock_);
1372 auto pre_buff = thread->GetPreBuff();
1373 if (pre_buff == nullptr) {
1374 // This can happens when the thread gives us own satb_buffer but
1375 // doesn't unregister from ThreadManaged.
1376 // At this perion GC can happen and we get pre_buff null here.
1377 return true;
1378 }
1379 for (auto obj : *pre_buff) {
1380 marker_.Mark(obj);
1381 object_stack->PushToStack(RootType::SATB_BUFFER, obj);
1382 }
1383 pre_buff->clear();
1384 return true;
1385 };
1386 // NOLINTNEXTLINE(readability-braces-around-statements)
1387 if constexpr (LanguageConfig::MT_MODE == MT_MODE_MULTI) {
1388 Thread::GetCurrent()->GetVM()->GetThreadManager()->EnumerateThreads(
1389 callback, static_cast<unsigned int>(EnumerationFlag::ALL));
1390 } else if (LanguageConfig::MT_MODE == MT_MODE_SINGLE) { // NOLINT(readability-misleading-indentation)
1391 callback(Thread::GetCurrent()->GetVM()->GetAssociatedThread());
1392 } else {
1393 UNREACHABLE();
1394 }
1395
1396 // Process satb buffers of the terminated threads
1397 os::memory::LockHolder lock(satb_and_newobj_buf_lock_);
1398 for (auto obj_vector : satb_buff_list_) {
1399 ASSERT(obj_vector != nullptr);
1400 for (auto obj : *obj_vector) {
1401 marker_.Mark(obj);
1402 object_stack->PushToStack(RootType::SATB_BUFFER, obj);
1403 }
1404 this->GetInternalAllocator()->Delete(obj_vector);
1405 }
1406 satb_buff_list_.clear();
1407 for (auto obj : newobj_buffer_) {
1408 marker_.Mark(obj);
1409 object_stack->PushToStack(RootType::SATB_BUFFER, obj);
1410 }
1411 newobj_buffer_.clear();
1412 }
1413
1414 template <class LanguageConfig>
WaitForUpdateRemsetThread()1415 void G1GC<LanguageConfig>::WaitForUpdateRemsetThread()
1416 {
1417 ScopedTiming scoped_timing(__FUNCTION__, *this->GetTiming());
1418 LOG_DEBUG_GC << "Execute WaitForUpdateRemsetThread";
1419
1420 // we forced to call EnumerateThreads many times because it takes WriteLock and we block update_remset_thread
1421 // can be done only once if EnumerateThreads will be implemented via ReadLock
1422 update_remset_thread_->WaitUntilTasksEnd();
1423
1424 // NOLINTNEXTLINE(readability-braces-around-statements)
1425 if constexpr (LanguageConfig::MT_MODE == MT_MODE_SINGLE) {
1426 auto thread = this->GetPandaVm()->GetAssociatedThread();
1427 auto local_buffer = thread->GetG1PostBarrierBuffer();
1428 if (local_buffer == nullptr) {
1429 return;
1430 }
1431 while (!local_buffer->IsEmpty()) {
1432 }
1433 } else if (LanguageConfig::MT_MODE == MT_MODE_MULTI) { // NOLINT(readability-misleading-indentation)
1434 while (true) {
1435 bool have_not_empty_queue = false;
1436 Thread::GetCurrent()->GetVM()->GetThreadManager()->EnumerateThreads(
1437 [&have_not_empty_queue](ManagedThread *thread) {
1438 auto local_buffer = thread->GetG1PostBarrierBuffer();
1439 if (local_buffer != nullptr && !local_buffer->IsEmpty()) {
1440 have_not_empty_queue = true;
1441 return false;
1442 }
1443 return true;
1444 },
1445 static_cast<unsigned int>(EnumerationFlag::ALL));
1446 if (!have_not_empty_queue) {
1447 break;
1448 }
1449 }
1450 } else {
1451 UNREACHABLE();
1452 }
1453 }
1454
1455 template <class LanguageConfig>
ClearSatb()1456 void G1GC<LanguageConfig>::ClearSatb()
1457 {
1458 ScopedTiming scoped_timing(__FUNCTION__, *this->GetTiming());
1459 // Acquire lock here to avoid data races with the threads
1460 // which are terminating now.
1461 // Data race is happens in thread.pre_buf_. The terminating thread may
1462 // release own pre_buf_ while GC thread iterates over threads and gets theirs
1463 // pre_buf_.
1464 os::memory::LockHolder lock(satb_and_newobj_buf_lock_);
1465 // Process satb buffers of the active threads
1466 auto thread_callback = [](ManagedThread *thread) {
1467 auto pre_buff = thread->GetPreBuff();
1468 if (pre_buff != nullptr) {
1469 pre_buff->clear();
1470 }
1471 return true;
1472 };
1473 if constexpr (LanguageConfig::MT_MODE == MT_MODE_MULTI) { // NOLINT
1474 Thread::GetCurrent()->GetVM()->GetThreadManager()->EnumerateThreads(thread_callback);
1475 } else if (LanguageConfig::MT_MODE == MT_MODE_SINGLE) { // NOLINT(readability-misleading-indentation)
1476 thread_callback(Thread::GetCurrent()->GetVM()->GetAssociatedThread());
1477 } else {
1478 UNREACHABLE();
1479 }
1480
1481 // Process satb buffers of the terminated threads
1482 for (auto obj_vector : satb_buff_list_) {
1483 this->GetInternalAllocator()->Delete(obj_vector);
1484 }
1485 satb_buff_list_.clear();
1486 newobj_buffer_.clear();
1487 }
1488
1489 template <class LanguageConfig>
1490 template <class Visitor>
VisitRemSets(const Visitor & visitor)1491 void G1GC<LanguageConfig>::VisitRemSets(const Visitor &visitor)
1492 {
1493 ScopedTiming t(__FUNCTION__, *this->GetTiming());
1494
1495 ASSERT(unique_cards_initialized_);
1496 // Iterate over stored references to the collection set
1497 for (auto &collection : unique_refs_from_remsets_) {
1498 for (auto &entry : collection) {
1499 ObjectHeader *object = entry.GetObject();
1500 uint32_t offset = entry.GetReferenceOffset();
1501 bool is_volatile = entry.IsVolatile();
1502 visitor(object, ObjectAccessor::GetObject(object, offset), offset, is_volatile);
1503 }
1504 }
1505 // Iterate over objects which have a reference to the collection set
1506 for (auto object : unique_objects_from_remsets_) {
1507 ObjectHelpers<LanguageConfig::LANG_TYPE>::TraverseAllObjectsWithInfo(object, visitor);
1508 }
1509 }
1510
1511 template <class LanguageConfig>
CacheRefsFromRemsets()1512 void G1GC<LanguageConfig>::CacheRefsFromRemsets()
1513 {
1514 ScopedTiming t(__FUNCTION__, *this->GetTiming());
1515 // Collect only unique objects to not proceed them more than once.
1516 ASSERT(!unique_cards_initialized_);
1517 bool use_gc_workers = this->GetSettings()->ParallelCompactingEnabled() && !this->IsFullGC();
1518 auto card_visitor = [this, use_gc_workers](CardPtr card, Region *r) {
1519 ASSERT(!r->HasFlag(IS_EDEN));
1520 // In case of mixed GC don't process remsets of the tenured regions which are in the collection set
1521 bool to_process = !r->HasFlag(IS_COLLECTION_SET) || r->HasFlag(IS_NONMOVABLE) || r->HasFlag(IS_LARGE_OBJECT);
1522 if (!to_process) {
1523 return;
1524 }
1525 if (!card->IsProcessed()) {
1526 card->SetProcessed();
1527 if (!use_gc_workers ||
1528 !this->GetWorkersPool()->AddTask(GCWorkersTaskTypes::TASK_INIT_REFS_FROM_REMSETS, card)) {
1529 CollectRefsFromCard(card, r, &unique_refs_from_remsets_.front());
1530 }
1531 }
1532 };
1533 for (auto region : this->collection_set_) {
1534 region->GetRemSet()->ProceedMarkedCards(card_visitor);
1535 }
1536 if (use_gc_workers) {
1537 this->GetWorkersPool()->WaitUntilTasksEnd();
1538 }
1539 #ifndef NDEBUG
1540 unique_cards_initialized_ = true;
1541 #endif // NDEBUG
1542 }
1543
1544 template <class LanguageConfig>
ClearRefsFromRemsetsCache()1545 void G1GC<LanguageConfig>::ClearRefsFromRemsetsCache()
1546 {
1547 for (auto &collection : unique_refs_from_remsets_) {
1548 collection.clear();
1549 }
1550 unique_objects_from_remsets_.clear();
1551 #ifndef NDEBUG
1552 unique_cards_initialized_ = false;
1553 #endif // NDEBUG
1554 }
1555
1556 template <class LanguageConfig>
ActualizeRemSets()1557 void G1GC<LanguageConfig>::ActualizeRemSets()
1558 {
1559 ScopedTiming t(__FUNCTION__, *this->GetTiming());
1560
1561 // Invalidate regions from collection set in all remsets
1562 for (Region *region : collection_set_.Young()) {
1563 if (!region->HasFlag(RegionFlag::IS_PROMOTED)) {
1564 RemSet<>::template InvalidateRegion<false>(region);
1565 }
1566 }
1567 for (Region *region : collection_set_.Tenured()) {
1568 RemSet<>::template InvalidateRegion<false>(region);
1569 }
1570 }
1571
1572 template <class LanguageConfig>
ShouldRunTenuredGC(const GCTask & task)1573 bool G1GC<LanguageConfig>::ShouldRunTenuredGC(const GCTask &task)
1574 {
1575 return this->IsOnPygoteFork() || task.reason_ == GCTaskCause::OOM_CAUSE ||
1576 task.reason_ == GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE ||
1577 task.reason_ == GCTaskCause::STARTUP_COMPLETE_CAUSE;
1578 }
1579
1580 template <class LanguageConfig>
OnWaitForIdleFail()1581 void G1GC<LanguageConfig>::OnWaitForIdleFail()
1582 {
1583 if (this->GetGCPhase() == GCPhase::GC_PHASE_MARK) {
1584 interrupt_concurrent_flag_ = true;
1585 }
1586 }
1587
1588 TEMPLATE_CLASS_LANGUAGE_CONFIG(G1GC);
1589 template class G1GCMarker<LANG_TYPE_STATIC, false>;
1590 template class G1GCMarker<LANG_TYPE_DYNAMIC, false>;
1591
1592 } // namespace panda::mem
1593