static void process_workitems(MVMThreadContext *tc, MVMHeapSnapshotState *ss) { while (ss->num_workitems > 0) { MVMHeapSnapshotWorkItem item = pop_workitem(tc, ss); /* We take our own working copy of the collectable info, since the * collectables array can grow and be reallocated. */ MVMHeapSnapshotCollectable col; set_ref_from(tc, ss, item.col_idx); col = ss->hs->collectables[item.col_idx]; col.kind = item.kind; switch (item.kind) { case MVM_SNAPSHOT_COL_KIND_OBJECT: case MVM_SNAPSHOT_COL_KIND_TYPE_OBJECT: process_object(tc, ss, &col, (MVMObject *)item.target); break; case MVM_SNAPSHOT_COL_KIND_STABLE: { MVMuint16 i; MVMSTable *st = (MVMSTable *)item.target; process_collectable(tc, ss, &col, (MVMCollectable *)st); set_type_index(tc, ss, &col, st); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->method_cache, "Method cache"); for (i = 0; i < st->type_check_cache_length; i++) MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->type_check_cache[i], "Type cache entry"); if (st->container_spec && st->container_spec->gc_mark_data) { st->container_spec->gc_mark_data(tc, st, ss->gcwl); process_gc_worklist(tc, ss, "Container spec data item"); } if (st->boolification_spec) MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->boolification_spec->method, "Boolification method"); if (st->invocation_spec) { MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->invocation_spec->class_handle, "Invocation spec class handle"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->invocation_spec->attr_name, "Invocation spec attribute name"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->invocation_spec->invocation_handler, "Invocation spec invocation handler"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->invocation_spec->md_class_handle, "Invocation spec class handle (multi)"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->invocation_spec->md_cache_attr_name, "Invocation spec cache attribute name (multi)"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->invocation_spec->md_valid_attr_name, "Invocation spec valid attribute name (multi)"); } MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->WHO, "WHO"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->WHAT, "WHAT"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->HOW, "HOW"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->HOW_sc, "HOW serialization context"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->method_cache_sc, "Method cache serialization context"); if (st->mode_flags & MVM_PARAMETRIC_TYPE) { MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->paramet.ric.parameterizer, "Parametric type parameterizer"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->paramet.ric.lookup, "Parametric type intern table"); } else if (st->mode_flags & MVM_PARAMETERIZED_TYPE) { MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->paramet.erized.parametric_type, "Parameterized type's parametric type"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)st->paramet.erized.parameters, "Parameterized type's parameters"); } if (st->REPR->gc_mark_repr_data) { st->REPR->gc_mark_repr_data(tc, st, ss->gcwl); process_gc_worklist(tc, ss, "REPR data item"); } break; } case MVM_SNAPSHOT_COL_KIND_FRAME: { MVMFrame *frame = (MVMFrame *)item.target; col.collectable_size = sizeof(MVMFrame); set_static_frame_index(tc, ss, &col, frame->static_info); if (frame->caller && !MVM_FRAME_IS_ON_CALLSTACK(tc, frame)) add_reference_const_cstr(tc, ss, "Caller", get_frame_idx(tc, ss, frame->caller)); if (frame->outer) add_reference_const_cstr(tc, ss, "Outer", get_frame_idx(tc, ss, frame->outer)); MVM_gc_root_add_frame_registers_to_worklist(tc, ss->gcwl, frame); process_gc_worklist(tc, ss, "Register"); if (frame->work) col.unmanaged_size += frame->allocd_work; if (frame->env) { MVMuint16 i, count; MVMuint16 *type_map; MVMuint16 name_count = frame->static_info->body.num_lexicals; MVMLexicalRegistry **names = frame->static_info->body.lexical_names_list; if (frame->spesh_cand && frame->spesh_log_idx == -1 && frame->spesh_cand->lexical_types) { type_map = frame->spesh_cand->lexical_types; count = frame->spesh_cand->num_lexicals; } else { type_map = frame->static_info->body.lexical_types; count = frame->static_info->body.num_lexicals; } for (i = 0; i < count; i++) { if (type_map[i] == MVM_reg_str || type_map[i] == MVM_reg_obj) { if (i < name_count) MVM_profile_heap_add_collectable_rel_vm_str(tc, ss, (MVMCollectable *)frame->env[i].o, names[i]->key); else MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)frame->env[i].o, "Lexical (inlined)"); } } col.unmanaged_size += frame->allocd_env; } MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)frame->code_ref, "Code reference"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)frame->static_info, "Static frame"); MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)frame->dynlex_cache_name, "Dynamic lexical cache name"); if (frame->special_return_data && frame->mark_special_return_data) { frame->mark_special_return_data(tc, frame, ss->gcwl); process_gc_worklist(tc, ss, "Special return data"); } if (frame->continuation_tags) { MVMContinuationTag *tag = frame->continuation_tags; while (tag) { MVM_profile_heap_add_collectable_rel_const_cstr(tc, ss, (MVMCollectable *)tag->tag, "Continuation tag"); col.unmanaged_size += sizeof(MVMContinuationTag); tag = tag->next; } } break; } case MVM_SNAPSHOT_COL_KIND_PERM_ROOTS: MVM_gc_root_add_permanents_to_worklist(tc, NULL, ss); break; case MVM_SNAPSHOT_COL_KIND_INSTANCE_ROOTS: MVM_gc_root_add_instance_roots_to_worklist(tc, NULL, ss); break; case MVM_SNAPSHOT_COL_KIND_CSTACK_ROOTS: MVM_gc_root_add_temps_to_worklist((MVMThreadContext *)item.target, NULL, ss); break; case MVM_SNAPSHOT_COL_KIND_THREAD_ROOTS: { MVMThreadContext *thread_tc = (MVMThreadContext *)item.target; MVM_gc_root_add_tc_roots_to_worklist(thread_tc, NULL, ss); if (thread_tc->cur_frame && !MVM_FRAME_IS_ON_CALLSTACK(thread_tc, tc->cur_frame)) add_reference_const_cstr(tc, ss, "Current frame", get_frame_idx(tc, ss, thread_tc->cur_frame)); break; } case MVM_SNAPSHOT_COL_KIND_ROOT: { MVMThread *cur_thread; add_reference_const_cstr(tc, ss, "Permanent Roots", push_workitem(tc, ss, MVM_SNAPSHOT_COL_KIND_PERM_ROOTS, NULL)); add_reference_const_cstr(tc, ss, "VM Instance Roots", push_workitem(tc, ss, MVM_SNAPSHOT_COL_KIND_INSTANCE_ROOTS, NULL)); cur_thread = tc->instance->threads; while (cur_thread) { if (cur_thread->body.tc) { add_reference_const_cstr(tc, ss, "C Stack Roots", push_workitem(tc, ss, MVM_SNAPSHOT_COL_KIND_CSTACK_ROOTS, cur_thread->body.tc)); add_reference_const_cstr(tc, ss, "Thread Roots", push_workitem(tc, ss, MVM_SNAPSHOT_COL_KIND_THREAD_ROOTS, cur_thread->body.tc)); add_reference_const_cstr(tc, ss, "Inter-generational Roots", push_workitem(tc, ss, MVM_SNAPSHOT_COL_KIND_INTERGEN_ROOTS, cur_thread->body.tc)); add_reference_const_cstr(tc, ss, "Thread Call Stack Roots", push_workitem(tc, ss, MVM_SNAPSHOT_COL_KIND_CALLSTACK_ROOTS, cur_thread->body.tc)); } cur_thread = cur_thread->body.next; } break; } case MVM_SNAPSHOT_COL_KIND_INTERGEN_ROOTS: { MVMThreadContext *thread_tc = (MVMThreadContext *)item.target; MVM_gc_root_add_gen2s_to_snapshot(thread_tc, ss); break; } case MVM_SNAPSHOT_COL_KIND_CALLSTACK_ROOTS: { MVMThreadContext *thread_tc = (MVMThreadContext *)item.target; if (thread_tc->cur_frame && MVM_FRAME_IS_ON_CALLSTACK(tc, thread_tc->cur_frame)) { MVMFrame *cur_frame = thread_tc->cur_frame; MVMint32 idx = 0; while (cur_frame && MVM_FRAME_IS_ON_CALLSTACK(tc, cur_frame)) { add_reference_idx(tc, ss, idx, push_workitem(tc, ss, MVM_SNAPSHOT_COL_KIND_FRAME, (MVMCollectable *)cur_frame)); idx++; cur_frame = cur_frame->caller; } } break; } default: MVM_panic(1, "Unknown heap snapshot worklist item kind %d", item.kind); } /* Store updated collectable info into array. Note that num_refs was * updated "at a distance". */ col.num_refs = ss->hs->collectables[item.col_idx].num_refs; ss->hs->collectables[item.col_idx] = col; } }
/* 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); } }