コード例 #1
0
ファイル: instrument.c プロジェクト: baby-gnu/MoarVM
/* 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);
}
コード例 #2
0
ファイル: collect.c プロジェクト: tony-o/perl6-travis-bin
/* 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;
    }
}
コード例 #3
0
ファイル: roots.c プロジェクト: MoarVM/MoarVM
/* 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]);
    }
}
コード例 #4
0
ファイル: arg_guard.c プロジェクト: MasterDuke17/MoarVM
/* 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
        }
    }
}
コード例 #5
0
ファイル: 6model.c プロジェクト: MattOates/MoarVM
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);
}
コード例 #6
0
ファイル: 6model.c プロジェクト: MattOates/MoarVM
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);
}
コード例 #7
0
ファイル: containers.c プロジェクト: baby-gnu/MoarVM
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);
}
コード例 #8
0
ファイル: loadbytecode.c プロジェクト: krunen/MoarVM
static void mark_sr_data(MVMThreadContext *tc, MVMFrame *frame, MVMGCWorklist *worklist) {
    MVM_gc_worklist_add(tc, worklist, &frame->special_return_data);
}
コード例 #9
0
ファイル: collect.c プロジェクト: tony-o/perl6-travis-bin
/* 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);
    }
}
コード例 #10
0
ファイル: collect.c プロジェクト: tony-o/perl6-travis-bin
/* 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");
    }
}
コード例 #11
0
ファイル: collect.c プロジェクト: etheleon/MoarVM
/* 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);
}
コード例 #12
0
ファイル: collect.c プロジェクト: usev6/MoarVM
/* 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);
    }
}