/* Called by a thread when it thinks it is done with GC. It may get some more * work yet, though. */ static void finish_gc(MVMThreadContext *tc, MVMuint8 gen) { MVMuint32 put_vote = 1, i; /* Loop until other threads have terminated, processing any extra work * that we are given. */ while (tc->instance->gc_finish) { MVMuint32 failed = 0; MVMuint32 i = 0; for ( ; i < tc->gc_work_count; i++) { process_in_tray(tc->gc_work[i].tc, gen, &put_vote); failed = process_sent_items(tc->gc_work[i].tc, &put_vote) | failed; } if (!failed && put_vote) { MVM_atomic_decr(&tc->instance->gc_finish); put_vote = 0; } } /* GCORCH_LOG(tc, "Thread %d run %d : Discovered GC termination\n");*/ /* Reset GC status flags and cleanup sent items for any work threads. */ /* This is also where thread destruction happens, and it needs to happen * before we acknowledge this GC run is finished. */ for (i = 0; i < tc->gc_work_count; i++) { MVMThreadContext *other = tc->gc_work[i].tc; MVMThread *thread_obj = other->thread_obj; cleanup_sent_items(other); if (thread_obj->body.stage == MVM_thread_stage_clearing_nursery) { GCORCH_LOG(tc, "Thread %d run %d : freeing gen2 of thread %d\n", other->thread_id); /* always free gen2 */ MVM_gc_collect_free_gen2_unmarked(other); GCORCH_LOG(tc, "Thread %d run %d : transferring gen2 of thread %d\n", other->thread_id); MVM_gc_gen2_transfer(other, tc); GCORCH_LOG(tc, "Thread %d run %d : destroying thread %d\n", other->thread_id); MVM_tc_destroy(other); tc->gc_work[i].tc = thread_obj->body.tc = NULL; thread_obj->body.stage = MVM_thread_stage_destroyed; } else { if (thread_obj->body.stage == MVM_thread_stage_exited) { /* don't bother freeing gen2; we'll do it next time */ thread_obj->body.stage = MVM_thread_stage_clearing_nursery; // GCORCH_LOG(tc, "Thread %d run %d : set thread %d clearing nursery stage to %d\n", other->thread_id, thread_obj->body.stage); } apr_atomic_cas32(&other->gc_status, MVMGCStatus_UNABLE, MVMGCStatus_STOLEN); apr_atomic_cas32(&other->gc_status, MVMGCStatus_NONE, MVMGCStatus_INTERRUPT); } } MVM_atomic_decr(&tc->instance->gc_ack); }
/* Run the global destruction phase. */ void MVM_gc_global_destruction(MVMThreadContext *tc) { char *nursery_tmp; /* Fake a nursery collection run by swapping the semi- * space nurseries. */ nursery_tmp = tc->nursery_fromspace; tc->nursery_fromspace = tc->nursery_tospace; tc->nursery_tospace = nursery_tmp; /* Run the objects' finalizers */ MVM_gc_collect_free_nursery_uncopied(tc, tc->nursery_alloc); MVM_gc_root_gen2_cleanup(tc); MVM_gc_collect_free_gen2_unmarked(tc, 1); MVM_gc_collect_free_stables(tc); }
static void run_gc(MVMThreadContext *tc, MVMuint8 what_to_do) { MVMuint8 gen; MVMuint32 i, n; /* Decide nursery or full collection. */ gen = is_full_collection(tc) ? MVMGCGenerations_Both : MVMGCGenerations_Nursery; /* Do GC work for ourselves and any work threads. */ for (i = 0, n = tc->gc_work_count ; i < n; i++) { MVMThreadContext *other = tc->gc_work[i].tc; tc->gc_work[i].limit = other->nursery_alloc; GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : starting collection for thread %d\n", other->thread_id); other->gc_promoted_bytes = 0; MVM_gc_collect(other, (other == tc ? what_to_do : MVMGCWhatToDo_NoInstance), gen); } /* Wait for everybody to agree we're done. */ finish_gc(tc, gen, what_to_do == MVMGCWhatToDo_All); /* Now we're all done, it's safe to finalize any objects that need it. */ /* XXX TODO explore the feasability of doing this in a background * finalizer/destructor thread and letting the main thread(s) continue * on their merry way(s). */ for (i = 0, n = tc->gc_work_count ; i < n; i++) { MVMThreadContext *other = tc->gc_work[i].tc; /* The thread might've been destroyed */ if (!other) continue; /* Contribute this thread's promoted bytes. */ MVM_add(&tc->instance->gc_promoted_bytes_since_last_full, other->gc_promoted_bytes); /* Collect nursery and gen2 as needed. */ GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : collecting nursery uncopied of thread %d\n", other->thread_id); MVM_gc_collect_free_nursery_uncopied(other, tc->gc_work[i].limit); if (gen == MVMGCGenerations_Both) { GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : freeing gen2 of thread %d\n", other->thread_id); MVM_gc_collect_free_gen2_unmarked(other); } } }
static void run_gc(MVMThreadContext *tc, MVMuint8 what_to_do) { MVMuint8 gen; MVMThread *child; MVMuint32 i, n; /* Do GC work for this thread, or at least all we know about. */ gen = tc->instance->gc_seq_number % MVM_GC_GEN2_RATIO == 0 ? MVMGCGenerations_Both : MVMGCGenerations_Nursery; /* Do GC work for any work threads. */ for (i = 0, n = tc->gc_work_count ; i < n; i++) { MVMThreadContext *other = tc->gc_work[i].tc; tc->gc_work[i].limit = other->nursery_alloc; GCORCH_LOG(tc, "Thread %d run %d : starting collection for thread %d\n", other->thread_id); MVM_gc_collect(other, (other == tc ? what_to_do : MVMGCWhatToDo_NoInstance), gen); } /* Wait for everybody to agree we're done. */ finish_gc(tc, gen); /* Now we're all done, it's safe to finalize any objects that need it. */ for (i = 0, n = tc->gc_work_count ; i < n; i++) { MVMThreadContext *other = tc->gc_work[i].tc; MVMThread *thread_obj; /* the thread might've been destroyed */ if (!other) continue; thread_obj = other->thread_obj; MVM_gc_collect_free_nursery_uncopied(other, tc->gc_work[i].limit); if (gen == MVMGCGenerations_Both) { GCORCH_LOG(tc, "Thread %d run %d : freeing gen2 of thread %d\n", other->thread_id); MVM_gc_collect_cleanup_gen2roots(other); MVM_gc_collect_free_gen2_unmarked(other); } } }
/* Run the global destruction phase. */ void MVM_gc_global_destruction(MVMThreadContext *tc) { char *nursery_tmp; /* Must wait until we're the only thread... */ while (tc->instance->num_user_threads) { GC_SYNC_POINT(tc); MVM_platform_thread_yield(); } /* Fake a nursery collection run by swapping the semi- * space nurseries. */ nursery_tmp = tc->nursery_fromspace; tc->nursery_fromspace = tc->nursery_tospace; tc->nursery_tospace = nursery_tmp; /* Run the objects' finalizers */ MVM_gc_collect_free_nursery_uncopied(tc, tc->nursery_alloc); MVM_gc_root_gen2_cleanup(tc); MVM_gc_collect_free_gen2_unmarked(tc); MVM_gc_collect_free_stables(tc); }
static void finish_gc(MVMThreadContext *tc, MVMuint8 gen, MVMuint8 is_coordinator) { MVMuint32 i, did_work; /* Do any extra work that we have been passed. */ GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : doing any work in thread in-trays\n"); did_work = 1; while (did_work) { did_work = 0; for (i = 0; i < tc->gc_work_count; i++) did_work += process_in_tray(tc->gc_work[i].tc, gen); } /* Decrement gc_finish to say we're done, and wait for termination. */ GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Voting to finish\n"); MVM_decr(&tc->instance->gc_finish); while (MVM_load(&tc->instance->gc_finish)) { for (i = 0; i < 1000; i++) ; /* XXX Something HT-efficienter. */ /* XXX Here we can look to see if we got passed any work, and if so * try to un-vote. */ } GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Termination agreed\n"); /* Co-ordinator should do final check over all the in-trays, and trigger * collection until all is settled. Rest should wait. Additionally, after * in-trays are settled, coordinator walks threads looking for anything * that needs adding to the finalize queue. It then will make another * iteration over in-trays to handle cross-thread references to objects * needing finalization. For full collections, collected objects are then * cleaned from all inter-generational sets, and finally any objects to * be freed at the fixed size allocator's next safepoint are freed. */ if (is_coordinator) { GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Co-ordinator handling in-tray clearing completion\n"); clear_intrays(tc, gen); GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Co-ordinator handling finalizers\n"); MVM_finalize_walk_queues(tc, gen); clear_intrays(tc, gen); if (gen == MVMGCGenerations_Both) { MVMThread *cur_thread = (MVMThread *)MVM_load(&tc->instance->threads); GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Co-ordinator handling inter-gen root cleanup\n"); while (cur_thread) { if (cur_thread->body.tc) MVM_gc_root_gen2_cleanup(cur_thread->body.tc); cur_thread = cur_thread->body.next; } } GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Co-ordinator handling fixed-size allocator safepoint frees\n"); MVM_fixed_size_safepoint(tc, tc->instance->fsa); MVM_profile_heap_take_snapshot(tc); GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Co-ordinator signalling in-trays clear\n"); MVM_store(&tc->instance->gc_intrays_clearing, 0); } else { GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Waiting for in-tray clearing completion\n"); while (MVM_load(&tc->instance->gc_intrays_clearing)) for (i = 0; i < 1000; i++) ; /* XXX Something HT-efficienter. */ GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Got in-tray clearing complete notice\n"); } /* Reset GC status flags. This is also where thread destruction happens, * and it needs to happen before we acknowledge this GC run is finished. */ for (i = 0; i < tc->gc_work_count; i++) { MVMThreadContext *other = tc->gc_work[i].tc; MVMThread *thread_obj = other->thread_obj; if (MVM_load(&thread_obj->body.stage) == MVM_thread_stage_clearing_nursery) { GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : transferring gen2 of thread %d\n", other->thread_id); MVM_gc_gen2_transfer(other, tc); GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : destroying thread %d\n", other->thread_id); MVM_tc_destroy(other); tc->gc_work[i].tc = thread_obj->body.tc = NULL; MVM_store(&thread_obj->body.stage, MVM_thread_stage_destroyed); } else { /* Free gen2 unmarked if full collection. */ if (gen == MVMGCGenerations_Both) { GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : freeing gen2 of thread %d\n", other->thread_id); MVM_gc_collect_free_gen2_unmarked(other, 0); } /* Contribute this thread's promoted bytes. */ MVM_add(&tc->instance->gc_promoted_bytes_since_last_full, other->gc_promoted_bytes); /* Collect nursery. */ GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : collecting nursery uncopied of thread %d\n", other->thread_id); MVM_gc_collect_free_nursery_uncopied(other, tc->gc_work[i].limit); /* Handle exited threads. */ if (MVM_load(&thread_obj->body.stage) == MVM_thread_stage_exited) { /* Don't bother freeing gen2; we'll do it next time */ MVM_store(&thread_obj->body.stage, MVM_thread_stage_clearing_nursery); GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : set thread %d clearing nursery stage to %d\n", other->thread_id, (int)MVM_load(&thread_obj->body.stage)); } /* Mark thread free to continue. */ MVM_cas(&other->gc_status, MVMGCStatus_STOLEN, MVMGCStatus_UNABLE); MVM_cas(&other->gc_status, MVMGCStatus_INTERRUPT, MVMGCStatus_NONE); } } /* Signal acknowledgement of completing the cleanup, * except for STables, and if we're the final to do * so, free the STables, which have been linked. */ if (MVM_decr(&tc->instance->gc_ack) == 2) { /* Set it to zero (we're guaranteed the only ones trying to write to * it here). Actual STable free in MVM_gc_enter_from_allocator. */ MVM_store(&tc->instance->gc_ack, 0); } }