/* Marks objects held in the profiling graph. */ static void mark_call_graph_node(MVMThreadContext *tc, MVMProfileCallNode *node, MVMGCWorklist *worklist) { MVMuint32 i; MVM_gc_worklist_add(tc, worklist, &(node->sf)); for (i = 0; i < node->num_alloc; i++) MVM_gc_worklist_add(tc, worklist, &(node->alloc[i].type)); for (i = 0; i < node->num_succ; i++) mark_call_graph_node(tc, node->succ[i], worklist); }
/* Takes work in a thread's in-tray, if any, and adds it to the worklist. */ static void add_in_tray_to_worklist(MVMThreadContext *tc, MVMGCWorklist *worklist) { MVMGCPassedWork * volatile *in_tray = &tc->gc_in_tray; MVMGCPassedWork *head; /* Get work to process. */ while (1) { /* See if there's anything in the in-tray; if not, we're done. */ head = *in_tray; if (head == NULL) return; /* Otherwise, try to take it. */ if (MVM_casptr(in_tray, head, NULL) == head) break; } /* Go through list, adding to worklist. */ while (head) { MVMGCPassedWork *next = head->next; MVMuint32 i; for (i = 0; i < head->num_items; i++) MVM_gc_worklist_add(tc, worklist, head->items[i]); MVM_free(head); head = next; } }
/* Adds the set of permanently registered roots to a GC worklist. */ void MVM_gc_root_add_permanents_to_worklist(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMHeapSnapshotState *snapshot) { MVMuint32 i, num_roots; MVMCollectable ***permroots; num_roots = tc->instance->num_permroots; permroots = tc->instance->permroots; if (worklist) { for (i = 0; i < num_roots; i++) MVM_gc_worklist_add(tc, worklist, permroots[i]); } else { char **permroot_descriptions = tc->instance->permroot_descriptions; for (i = 0; i < num_roots; i++) MVM_profile_heap_add_collectable_rel_const_cstr(tc, snapshot, *(permroots[i]), permroot_descriptions[i]); } }
/* Marks any objects held by an argument guard. */ void MVM_spesh_arg_guard_gc_mark(MVMThreadContext *tc, MVMSpeshArgGuard *ag, MVMGCWorklist *worklist) { if (ag) { MVMuint32 i; for (i = 0; i < ag->used_nodes; i++) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wswitch" switch (ag->nodes[i].op) { case MVM_SPESH_GUARD_OP_STABLE_CONC: case MVM_SPESH_GUARD_OP_STABLE_TYPE: MVM_gc_worklist_add(tc, worklist, &(ag->nodes[i].st)); break; } #pragma clang diagnostic pop } } }
static void mark_find_method_sr_data(MVMThreadContext *tc, MVMFrame *frame, MVMGCWorklist *worklist) { FindMethodSRData *fm = (FindMethodSRData *)frame->special_return_data; MVM_gc_worklist_add(tc, worklist, &fm->obj); MVM_gc_worklist_add(tc, worklist, &fm->name); }
static void mark_sr_data(MVMThreadContext *tc, MVMFrame *frame, MVMGCWorklist *worklist) { AcceptsTypeSRData *atd = (AcceptsTypeSRData *)frame->special_return_data; MVM_gc_worklist_add(tc, worklist, &atd->obj); MVM_gc_worklist_add(tc, worklist, &atd->type); }
static void code_pair_gc_mark_data(MVMThreadContext *tc, MVMSTable *st, MVMGCWorklist *worklist) { CodePairContData *data = (CodePairContData *)st->container_data; MVM_gc_worklist_add(tc, worklist, &data->fetch_code); MVM_gc_worklist_add(tc, worklist, &data->store_code); }
static void mark_sr_data(MVMThreadContext *tc, MVMFrame *frame, MVMGCWorklist *worklist) { MVM_gc_worklist_add(tc, worklist, &frame->special_return_data); }
/* Does a garbage collection run. Exactly what it does is configured by the * couple of arguments that it takes. * * The what_to_do argument specifies where it should look for things to add * to the worklist: everywhere, just at thread local stuff, or just in the * thread's in-tray. * * The gen argument specifies whether to collect the nursery or both of the * generations. Nursery collection is done by semi-space copying. Once an * object is seen/copied once in the nursery (may be tuned in the future to * twice or so - we'll see) then it is not copied to tospace, but instead * promoted to the second generation. If we are collecting generation 2 also, * then objects that are alive in the second generation are simply marked. * Since the second generation is managed as a set of sized pools, there is * much less motivation for any kind of copying/compaction; the internal * fragmentation that makes finding a right-sized gap problematic will not * happen. * * Note that it adds the roots and processes them in phases, to try to avoid * building up a huge worklist. */ void MVM_gc_collect(MVMThreadContext *tc, MVMuint8 what_to_do, MVMuint8 gen) { /* Create a GC worklist. */ MVMGCWorklist *worklist = MVM_gc_worklist_create(tc, gen != MVMGCGenerations_Nursery, 0); /* Initialize work passing data structure. */ WorkToPass wtp; wtp.num_target_threads = 0; wtp.target_work = NULL; /* See what we need to work on this time. */ if (what_to_do == MVMGCWhatToDo_InTray) { /* We just need to process anything in the in-tray. */ add_in_tray_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from in tray \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } else if (what_to_do == MVMGCWhatToDo_Finalizing) { /* Need to process the finalizing queue. */ MVMuint32 i; for (i = 0; i < tc->num_finalizing; i++) MVM_gc_worklist_add(tc, worklist, &(tc->finalizing[i])); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from finalizing \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } else { /* Main collection run. Swap fromspace and tospace, allocating the * new tospace if that didn't yet happen (we don't allocate it at * startup, to cut memory use for threads that quit before a GC). */ void *fromspace = tc->nursery_tospace; void *tospace = tc->nursery_fromspace; if (!tospace) tospace = MVM_calloc(1, MVM_NURSERY_SIZE); tc->nursery_fromspace = fromspace; tc->nursery_tospace = tospace; /* Reset nursery allocation pointers to the new tospace. */ tc->nursery_alloc = tospace; tc->nursery_alloc_limit = (char *)tc->nursery_alloc + MVM_NURSERY_SIZE; /* Add permanent roots and process them; only one thread will do * this, since they are instance-wide. */ if (what_to_do != MVMGCWhatToDo_NoInstance) { MVM_gc_root_add_permanents_to_worklist(tc, worklist, NULL); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from instance permanents\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); MVM_gc_root_add_instance_roots_to_worklist(tc, worklist, NULL); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from instance roots\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } /* Add per-thread state to worklist and process it. */ MVM_gc_root_add_tc_roots_to_worklist(tc, worklist, NULL); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from TC objects\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* Add current frame to worklist. */ MVM_gc_worklist_add_frame(tc, worklist, tc->cur_frame); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from current frame\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* Add temporary roots and process them (these are per-thread). */ MVM_gc_root_add_temps_to_worklist(tc, worklist, NULL); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from thread temps\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* Add things that are roots for the first generation because they are * pointed to by objects in the second generation and process them * (also per-thread). Note we need not do this if we're doing a full * collection anyway (in fact, we must not for correctness, otherwise * the gen2 rooting keeps them alive forever). */ if (gen == MVMGCGenerations_Nursery) { MVM_gc_root_add_gen2s_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from gen2 \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } /* Process anything in the in-tray. */ add_in_tray_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from in tray \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* At this point, we have probably done most of the work we will * need to (only get more if another thread passes us more); zero * out the remaining tospace. */ memset(tc->nursery_alloc, 0, (char *)tc->nursery_alloc_limit - (char *)tc->nursery_alloc); } /* Destroy the worklist. */ MVM_gc_worklist_destroy(tc, worklist); /* Pass any work for other threads we accumulated but that didn't trigger * the work passing threshold, then cleanup work passing list. */ if (wtp.num_target_threads) { pass_leftover_work(tc, &wtp); MVM_free(wtp.target_work); } }
/* Marks a collectable item (object, type object, STable). */ void MVM_gc_mark_collectable(MVMThreadContext *tc, MVMGCWorklist *worklist, MVMCollectable *new_addr) { MVMuint16 i; MVMuint32 sc_idx; assert(!(new_addr->flags & MVM_CF_FORWARDER_VALID)); /*assert(REPR(new_addr));*/ sc_idx = MVM_get_idx_of_sc(new_addr); if (sc_idx > 0) MVM_gc_worklist_add(tc, worklist, &(tc->instance->all_scs[sc_idx]->sc)); if (!(new_addr->flags & (MVM_CF_TYPE_OBJECT | MVM_CF_STABLE))) { /* Need to view it as an object in here. */ MVMObject *new_addr_obj = (MVMObject *)new_addr; /* Add the STable to the worklist. */ MVM_gc_worklist_add(tc, worklist, &new_addr_obj->st); /* If needed, mark it. This will add addresses to the worklist * that will need updating. Note that we are passing the address * of the object *after* copying it since those are the addresses * we care about updating; the old chunk of memory is now dead! */ if (MVM_GC_DEBUG_ENABLED(MVM_GC_DEBUG_COLLECT) && !STABLE(new_addr_obj)) MVM_panic(MVM_exitcode_gcnursery, "Found an outdated reference to address %p", new_addr); if (REPR(new_addr_obj)->gc_mark) REPR(new_addr_obj)->gc_mark(tc, STABLE(new_addr_obj), OBJECT_BODY(new_addr_obj), worklist); } else if (new_addr->flags & MVM_CF_TYPE_OBJECT) { /* Add the STable to the worklist. */ MVM_gc_worklist_add(tc, worklist, &((MVMObject *)new_addr)->st); } else if (new_addr->flags & MVM_CF_STABLE) { /* Add all references in the STable to the work list. */ MVMSTable *new_addr_st = (MVMSTable *)new_addr; MVM_gc_worklist_add(tc, worklist, &new_addr_st->method_cache); for (i = 0; i < new_addr_st->type_check_cache_length; i++) MVM_gc_worklist_add(tc, worklist, &new_addr_st->type_check_cache[i]); if (new_addr_st->container_spec) if (new_addr_st->container_spec->gc_mark_data) new_addr_st->container_spec->gc_mark_data(tc, new_addr_st, worklist); if (new_addr_st->boolification_spec) MVM_gc_worklist_add(tc, worklist, &new_addr_st->boolification_spec->method); if (new_addr_st->invocation_spec) { MVM_gc_worklist_add(tc, worklist, &new_addr_st->invocation_spec->class_handle); MVM_gc_worklist_add(tc, worklist, &new_addr_st->invocation_spec->attr_name); MVM_gc_worklist_add(tc, worklist, &new_addr_st->invocation_spec->invocation_handler); MVM_gc_worklist_add(tc, worklist, &new_addr_st->invocation_spec->md_class_handle); MVM_gc_worklist_add(tc, worklist, &new_addr_st->invocation_spec->md_cache_attr_name); MVM_gc_worklist_add(tc, worklist, &new_addr_st->invocation_spec->md_valid_attr_name); } MVM_gc_worklist_add(tc, worklist, &new_addr_st->WHO); MVM_gc_worklist_add(tc, worklist, &new_addr_st->WHAT); MVM_gc_worklist_add(tc, worklist, &new_addr_st->HOW); MVM_gc_worklist_add(tc, worklist, &new_addr_st->HOW_sc); MVM_gc_worklist_add(tc, worklist, &new_addr_st->method_cache_sc); if (new_addr_st->mode_flags & MVM_PARAMETRIC_TYPE) { MVM_gc_worklist_add(tc, worklist, &new_addr_st->paramet.ric.parameterizer); MVM_gc_worklist_add(tc, worklist, &new_addr_st->paramet.ric.lookup); } else if (new_addr_st->mode_flags & MVM_PARAMETERIZED_TYPE) { MVM_gc_worklist_add(tc, worklist, &new_addr_st->paramet.erized.parametric_type); MVM_gc_worklist_add(tc, worklist, &new_addr_st->paramet.erized.parameters); } /* If it needs to have its REPR data marked, do that. */ if (new_addr_st->REPR->gc_mark_repr_data) new_addr_st->REPR->gc_mark_repr_data(tc, new_addr_st, worklist); } else { MVM_panic(MVM_exitcode_gcnursery, "Internal error: impossible case encountered in GC marking"); } }
/* Does a garbage collection run. Exactly what it does is configured by the * couple of arguments that it takes. * * The what_to_do argument specifies where it should look for things to add * to the worklist: everywhere, just at thread local stuff, or just in the * thread's in-tray. * * The gen argument specifies whether to collect the nursery or both of the * generations. Nursery collection is done by semi-space copying. Once an * object is seen/copied once in the nursery (may be tuned in the future to * twice or so - we'll see) then it is not copied to tospace, but instead * promoted to the second generation. If we are collecting generation 2 also, * then objects that are alive in the second generation are simply marked. * Since the second generation is managed as a set of sized pools, there is * much less motivation for any kind of copying/compaction; the internal * fragmentation that makes finding a right-sized gap problematic will not * happen. * * Note that it adds the roots and processes them in phases, to try to avoid * building up a huge worklist. */ void MVM_gc_collect(MVMThreadContext *tc, MVMuint8 what_to_do, MVMuint8 gen) { /* Create a GC worklist. */ MVMGCWorklist *worklist = MVM_gc_worklist_create(tc, gen != MVMGCGenerations_Nursery); /* Initialize work passing data structure. */ WorkToPass wtp; wtp.num_target_threads = 0; wtp.target_work = NULL; /* If we're starting a run (as opposed to just coming back here to do a * little more work we got after we first thought we were done...) */ if (what_to_do != MVMGCWhatToDo_InTray) { /* Swap fromspace and tospace. */ void * fromspace = tc->nursery_tospace; void * tospace = tc->nursery_fromspace; tc->nursery_fromspace = fromspace; tc->nursery_tospace = tospace; /* Reset nursery allocation pointers to the new tospace. */ tc->nursery_alloc = tospace; tc->nursery_alloc_limit = (char *)tc->nursery_alloc + MVM_NURSERY_SIZE; MVM_gc_worklist_add(tc, worklist, &tc->thread_obj); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from thread_obj\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* Add permanent roots and process them; only one thread will do * this, since they are instance-wide. */ if (what_to_do != MVMGCWhatToDo_NoInstance) { MVM_gc_root_add_permanents_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from instance permanents\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); MVM_gc_root_add_instance_roots_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from instance roots\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } /* Add per-thread state to worklist and process it. */ MVM_gc_root_add_tc_roots_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from TC objects\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* Add current frame to worklist. */ MVM_gc_worklist_add_frame(tc, worklist, tc->cur_frame); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from current frame\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* Add temporary roots and process them (these are per-thread). */ MVM_gc_root_add_temps_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from thread temps\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* Add things that are roots for the first generation because they are * pointed to by objects in the second generation and process them * (also per-thread). Note we need not do this if we're doing a full * collection anyway (in fact, we must not for correctness, otherwise * the gen2 rooting keeps them alive forever). */ if (gen == MVMGCGenerations_Nursery) { MVM_gc_root_add_gen2s_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from gen2 \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } /* Find roots in frames and process them. */ if (tc->cur_frame) { MVM_gc_worklist_add_frame(tc, worklist, tc->cur_frame); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from cur_frame \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } /* Process anything in the in-tray. */ add_in_tray_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from in tray \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* At this point, we have probably done most of the work we will * need to (only get more if another thread passes us more); zero * out the remaining tospace. */ memset(tc->nursery_alloc, 0, (char *)tc->nursery_alloc_limit - (char *)tc->nursery_alloc); } else { /* We just need to process anything in the in-tray. */ add_in_tray_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from in tray \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } /* Destroy the worklist. */ MVM_gc_worklist_destroy(tc, worklist); /* Pass any work for other threads we accumulated but that didn't trigger * the work passing threshold, then cleanup work passing list. */ if (wtp.num_target_threads) { pass_leftover_work(tc, &wtp); free(wtp.target_work); } /* If it was a full collection, some of the things in gen2 that we root * due to point to gen1 objects may be dead. */ if (gen != MVMGCGenerations_Nursery) MVM_gc_root_gen2_cleanup(tc); }
/* Does a garbage collection run. Exactly what it does is configured by the * couple of arguments that it takes. * * The what_to_do argument specifies where it should look for things to add * to the worklist: everywhere, just at thread local stuff, or just in the * thread's in-tray. * * The gen argument specifies whether to collect the nursery or both of the * generations. Nursery collection is done by semi-space copying. Once an * object is seen/copied once in the nursery (may be tuned in the future to * twice or so - we'll see) then it is not copied to tospace, but instead * promoted to the second generation. If we are collecting generation 2 also, * then objects that are alive in the second generation are simply marked. * Since the second generation is managed as a set of sized pools, there is * much less motivation for any kind of copying/compaction; the internal * fragmentation that makes finding a right-sized gap problematic will not * happen. * * Note that it adds the roots and processes them in phases, to try to avoid * building up a huge worklist. */ void MVM_gc_collect(MVMThreadContext *tc, MVMuint8 what_to_do, MVMuint8 gen) { /* Create a GC worklist. */ MVMGCWorklist *worklist = MVM_gc_worklist_create(tc, gen != MVMGCGenerations_Nursery); /* Initialize work passing data structure. */ WorkToPass wtp; wtp.num_target_threads = 0; wtp.target_work = NULL; /* See what we need to work on this time. */ if (what_to_do == MVMGCWhatToDo_InTray) { /* We just need to process anything in the in-tray. */ add_in_tray_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from in tray \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } else if (what_to_do == MVMGCWhatToDo_Finalizing) { /* Need to process the finalizing queue. */ MVMuint32 i; for (i = 0; i < tc->num_finalizing; i++) MVM_gc_worklist_add(tc, worklist, &(tc->finalizing[i])); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from finalizing \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } else { /* Main collection run. The current tospace becomes fromspace, with * the size of the current tospace becoming stashed as the size of * that fromspace. */ void *old_fromspace = tc->nursery_fromspace; MVMuint32 old_fromspace_size = tc->nursery_fromspace_size; tc->nursery_fromspace = tc->nursery_tospace; tc->nursery_fromspace_size = tc->nursery_tospace_size; /* Decide on this threads's tospace size. If fromspace was already at * the maximum nursery size, then that is the new tospace size. If * not, then see if this thread caused the current GC run, and grant * it a bigger tospace. Otherwise, new tospace size is left as the * last tospace size. */ if (tc->nursery_tospace_size < MVM_NURSERY_SIZE) { if (tc->instance->thread_to_blame_for_gc == tc) tc->nursery_tospace_size *= 2; } /* If the old fromspace matches the target size, just re-use it. If * not, free it and allocate a new tospace. */ if (old_fromspace_size == tc->nursery_tospace_size) { tc->nursery_tospace = old_fromspace; } else { MVM_free(old_fromspace); tc->nursery_tospace = MVM_calloc(1, tc->nursery_tospace_size); } /* Reset nursery allocation pointers to the new tospace. */ tc->nursery_alloc = tc->nursery_tospace; tc->nursery_alloc_limit = (char *)tc->nursery_tospace + tc->nursery_tospace_size; /* Add permanent roots and process them; only one thread will do * this, since they are instance-wide. */ if (what_to_do != MVMGCWhatToDo_NoInstance) { MVM_gc_root_add_permanents_to_worklist(tc, worklist, NULL); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from instance permanents\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); MVM_gc_root_add_instance_roots_to_worklist(tc, worklist, NULL); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from instance roots\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } /* Add per-thread state to worklist and process it. */ MVM_gc_root_add_tc_roots_to_worklist(tc, worklist, NULL); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from TC objects\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* Walk current call stack, following caller chain until we reach a * heap-allocated frame. Note that tc->cur_frame may itself be a heap * frame, in which case we put it directly on the worklist as it can * move. */ if (tc->cur_frame && MVM_FRAME_IS_ON_CALLSTACK(tc, tc->cur_frame)) { MVMFrame *cur_frame = tc->cur_frame; while (cur_frame && MVM_FRAME_IS_ON_CALLSTACK(tc, cur_frame)) { MVM_gc_root_add_frame_roots_to_worklist(tc, worklist, cur_frame); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from a stack frame\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); cur_frame = cur_frame->caller; } } else { MVM_gc_worklist_add(tc, worklist, &tc->cur_frame); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from current frame\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } /* Add temporary roots and process them (these are per-thread). */ MVM_gc_root_add_temps_to_worklist(tc, worklist, NULL); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from thread temps\n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* Add things that are roots for the first generation because they are * pointed to by objects in the second generation and process them * (also per-thread). Note we need not do this if we're doing a full * collection anyway (in fact, we must not for correctness, otherwise * the gen2 rooting keeps them alive forever). */ if (gen == MVMGCGenerations_Nursery) { MVM_gc_root_add_gen2s_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from gen2 \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); } /* Process anything in the in-tray. */ add_in_tray_to_worklist(tc, worklist); GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : processing %d items from in tray \n", worklist->items); process_worklist(tc, worklist, &wtp, gen); /* At this point, we have probably done most of the work we will * need to (only get more if another thread passes us more); zero * out the remaining tospace. */ memset(tc->nursery_alloc, 0, (char *)tc->nursery_alloc_limit - (char *)tc->nursery_alloc); } /* Destroy the worklist. */ MVM_gc_worklist_destroy(tc, worklist); /* Pass any work for other threads we accumulated but that didn't trigger * the work passing threshold, then cleanup work passing list. */ if (wtp.num_target_threads) { pass_leftover_work(tc, &wtp); MVM_free(wtp.target_work); } }