Esempio n. 1
0
/* Adds a chunk of work to another thread's in-tray. */
static void push_work_to_thread_in_tray(MVMThreadContext *tc, MVMuint32 target, MVMGCPassedWork *work) {
    MVMGCPassedWork * volatile *target_tray;

    /* Locate the thread to pass the work to. */
    MVMThreadContext *target_tc = NULL;
    if (target == 1) {
        /* It's going to the main thread. */
        target_tc = tc->instance->main_thread;
    }
    else {
        MVMThread *t = (MVMThread *)MVM_load(&tc->instance->threads);
        do {
            if (t->body.tc && t->body.tc->thread_id == target) {
                target_tc = t->body.tc;
                break;
            }
        } while ((t = t->body.next));
        if (!target_tc)
            MVM_panic(MVM_exitcode_gcnursery, "Internal error: invalid thread ID %d in GC work pass", target);
    }

    /* Pass the work, chaining any other in-tray entries for the thread
     * after us. */
    target_tray = &target_tc->gc_in_tray;
    while (1) {
        MVMGCPassedWork *orig = *target_tray;
        work->next = orig;
        if (MVM_casptr(target_tray, orig, work) == orig)
            return;
    }
}
Esempio n. 2
0
/* Does work in a thread's in-tray, if any. Returns a non-zero value if work
 * was found and done, and zero otherwise. */
static int process_in_tray(MVMThreadContext *tc, MVMuint8 gen) {
    GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Considering extra work\n");
    if (MVM_load(&tc->gc_in_tray)) {
        GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE,
            "Thread %d run %d : Was given extra work by another thread; doing it\n");
        MVM_gc_collect(tc, MVMGCWhatToDo_InTray, gen);
        return 1;
    }
    return 0;
}
Esempio n. 3
0
/* Adds a chunk of work to another thread's in-tray. */
static void push_work_to_thread_in_tray(MVMThreadContext *tc, MVMuint32 target, MVMGCPassedWork *work) {
    MVMint32 j;
    MVMGCPassedWork * volatile *target_tray;

    /* Locate the thread to pass the work to. */
    MVMThreadContext *target_tc = NULL;
    if (target == 0) {
        /* It's going to the main thread. */
        target_tc = tc->instance->main_thread;
    }
    else {
        MVMThread *t = (MVMThread *)MVM_load(&tc->instance->threads);
        do {
            if (t->body.tc->thread_id == target) {
                target_tc = t->body.tc;
                break;
            }
        } while ((t = t->body.next));
        if (!target_tc)
            MVM_panic(MVM_exitcode_gcnursery, "Internal error: invalid thread ID in GC work pass");
    }

    /* push to sent_items list */
    if (tc->gc_sent_items) {
        tc->gc_sent_items->next_by_sender = work;
        work->last_by_sender = tc->gc_sent_items;
    }
    /* queue it up to check if the check list isn't clear */
    if (!MVM_load(&tc->gc_next_to_check)) {
        MVM_store(&tc->gc_next_to_check, work);
    }
    tc->gc_sent_items = work;

    /* Pass the work, chaining any other in-tray entries for the thread
     * after us. */
    target_tray = &target_tc->gc_in_tray;
    while (1) {
        MVMGCPassedWork *orig = *target_tray;
        work->next = orig;
        if (MVM_casptr(target_tray, orig, work) == orig)
            return;
    }
}
Esempio n. 4
0
/* Called by a thread when it thinks it is done with GC. It may get some more
 * work yet, though. */
static void clear_intrays(MVMThreadContext *tc, MVMuint8 gen) {
    MVMuint32 did_work = 1;
    while (did_work) {
        MVMThread *cur_thread;
        did_work = 0;
        cur_thread = (MVMThread *)MVM_load(&tc->instance->threads);
        while (cur_thread) {
            if (cur_thread->body.tc)
                did_work += process_in_tray(cur_thread->body.tc, gen);
            cur_thread = cur_thread->body.next;
        }
    }
}
Esempio n. 5
0
void MVM_finalize_walk_queues(MVMThreadContext *tc, MVMuint8 gen) {
    MVMThread *cur_thread = (MVMThread *)MVM_load(&tc->instance->threads);
    while (cur_thread) {
        if (cur_thread->body.tc) {
            walk_thread_finalize_queue(cur_thread->body.tc, gen);
            if (cur_thread->body.tc->num_finalizing > 0) {
                MVM_gc_collect(cur_thread->body.tc, MVMGCWhatToDo_Finalizing, gen);
                setup_finalize_handler_call(cur_thread->body.tc);
            }
        }
        cur_thread = cur_thread->body.next;
    }
}
Esempio n. 6
0
/* This is called when a thread hits an interrupt at a GC safe point. This means
 * that another thread is already trying to start a GC run, so we don't need to
 * try and do that, just enlist in the run. */
void MVM_gc_enter_from_interrupt(MVMThreadContext *tc) {
    AO_t curr;

    GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Entered from interrupt\n");

    MVM_telemetry_timestamp(tc, "gc_enter_from_interrupt");

    /* If profiling, record that GC is starting. */
    if (tc->instance->profiling)
        MVM_profiler_log_gc_start(tc, is_full_collection(tc));

    /* We'll certainly take care of our own work. */
    tc->gc_work_count = 0;
    add_work(tc, tc);

    /* Indicate that we're ready to GC. Only want to decrement it if it's 2 or
     * greater (0 should never happen; 1 means the coordinator is still counting
     * up how many threads will join in, so we should wait until it decides to
     * decrement.) */
    while ((curr = MVM_load(&tc->instance->gc_start)) < 2
            || !MVM_trycas(&tc->instance->gc_start, curr, curr - 1)) {
        /* MVM_platform_thread_yield();*/
    }

    /* Wait for all threads to indicate readiness to collect. */
    GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Waiting for other threads\n");
    while (MVM_load(&tc->instance->gc_start)) {
        /* MVM_platform_thread_yield();*/
    }

    GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Entering run_gc\n");
    run_gc(tc, MVMGCWhatToDo_NoInstance);
    GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : GC complete\n");

    /* If profiling, record that GC is over. */
    if (tc->instance->profiling)
        MVM_profiler_log_gc_end(tc);
}
Esempio n. 7
0
File: frame.c Progetto: mj41/MoarVM
/* Decreases the reference count of a frame. If it hits zero, then we can
 * free it. Returns null for convenience. */
MVMFrame * MVM_frame_dec_ref(MVMThreadContext *tc, MVMFrame *frame) {
    /* MVM_dec returns what the count was before it decremented it
     * to zero, so we look for 1 here. */
    while (MVM_decr(&frame->ref_count) == 1) {
        MVMuint32 pool_index = frame->static_info->body.pool_index;
        MVMFrame *node = tc->frame_pool_table[pool_index];
        MVMFrame *outer_to_decr = frame->outer;

        /* If there's a caller pointer, decrement that. */
        if (frame->caller)
            frame->caller = MVM_frame_dec_ref(tc, frame->caller);

        if (node && MVM_load(&node->ref_count) >= MVMFramePoolLengthLimit) {
            /* There's no room on the free list, so destruction.*/
            if (frame->env) {
                free(frame->env);
                frame->env = NULL;
            }
            if (frame->work) {
                MVM_args_proc_cleanup(tc, &frame->params);
                free(frame->work);
                frame->work = NULL;
            }
            free(frame);
        }
        else { /* Unshift it to the free list */
            MVM_store(&frame->ref_count, (frame->outer = node) ? MVM_load(&node->ref_count) + 1 : 1);
            tc->frame_pool_table[pool_index] = frame;
        }
        if (outer_to_decr)
            frame = outer_to_decr; /* and loop */
        else
            break;
    }
    return NULL;
}
Esempio n. 8
0
/* Called by a thread to indicate it is about to enter a blocking operation.
 * This tells any thread that is coordinating a GC run that this thread will
 * be unable to participate. */
void MVM_gc_mark_thread_blocked(MVMThreadContext *tc) {
    /* This may need more than one attempt. */
    while (1) {
        /* Try to set it from running to unable - the common case. */
        if (MVM_cas(&tc->gc_status, MVMGCStatus_NONE,
                MVMGCStatus_UNABLE) == MVMGCStatus_NONE)
            return;

        /* The only way this can fail is if another thread just decided we're to
         * participate in a GC run. */
        if (MVM_load(&tc->gc_status) == MVMGCStatus_INTERRUPT)
            MVM_gc_enter_from_interrupt(tc);
        else
            MVM_panic(MVM_exitcode_gcorch, "Invalid GC status observed; aborting");
    }
}
Esempio n. 9
0
static MVMint32 is_full_collection(MVMThreadContext *tc) {
    MVMuint64 percent_growth, promoted;
    size_t rss;

    /* If it's below the absolute minimum, quickly return. */
    promoted = (MVMuint64)MVM_load(&tc->instance->gc_promoted_bytes_since_last_full);
    if (promoted < MVM_GC_GEN2_THRESHOLD_MINIMUM)
        return 0;

    /* If we're heap profiling then don't consider the resident set size, as
     * it will be hugely distorted by the profile data we record. */
    if (MVM_profile_heap_profiling(tc))
        return 1;

    /* Otherwise, consider percentage of resident set size. */
    if (uv_resident_set_memory(&rss) < 0 || rss == 0)
        rss = 50 * 1024 * 1024;
    percent_growth = (100 * promoted) / (MVMuint64)rss;

    return percent_growth >= MVM_GC_GEN2_THRESHOLD_PERCENT;
}
Esempio n. 10
0
static MVMuint32 signal_all_but(MVMThreadContext *tc, MVMThread *t, MVMThread *tail) {
    MVMuint32 count = 0;
    MVMThread *next;
    if (!t) {
        return 0;
    }
    do {
        next = t->body.next;
        switch (MVM_load(&t->body.stage)) {
            case MVM_thread_stage_starting:
            case MVM_thread_stage_waiting:
            case MVM_thread_stage_started:
                if (t->body.tc != tc) {
                    count += signal_one_thread(tc, t->body.tc);
                }
                break;
            case MVM_thread_stage_exited:
                GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : queueing to clear nursery of thread %d\n", t->body.tc->thread_id);
                add_work(tc, t->body.tc);
                break;
            case MVM_thread_stage_clearing_nursery:
                GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : queueing to destroy thread %d\n", t->body.tc->thread_id);
                /* last GC run for this thread */
                add_work(tc, t->body.tc);
                break;
            case MVM_thread_stage_destroyed:
                GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : found a destroyed thread\n");
                /* will be cleaned up (removed from the lists) shortly */
                break;
            default:
                MVM_panic(MVM_exitcode_gcorch, "Corrupted MVMThread or running threads list: invalid thread stage %"MVM_PRSz"", MVM_load(&t->body.stage));
        }
    } while (next && (t = next));
    if (tail)
        MVM_gc_write_barrier(tc, (MVMCollectable *)t, (MVMCollectable *)tail);
    t->body.next = tail;
    return count;
}
Esempio n. 11
0
/* Goes through all threads but the current one and notifies them that a
 * GC run is starting. Those that are blocked are considered excluded from
 * the run, and are not counted. Returns the count of threads that should be
 * added to the finished countdown. */
static MVMuint32 signal_one_thread(MVMThreadContext *tc, MVMThreadContext *to_signal) {

    /* Loop here since we may not succeed first time (e.g. the status of the
     * thread may change between the two ways we try to twiddle it). */
    while (1) {
        switch (MVM_load(&to_signal->gc_status)) {
            case MVMGCStatus_NONE:
                /* Try to set it from running to interrupted - the common case. */
                if (MVM_cas(&to_signal->gc_status, MVMGCStatus_NONE,
                        MVMGCStatus_INTERRUPT) == MVMGCStatus_NONE) {
                    GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Signalled thread %d to interrupt\n", to_signal->thread_id);
                    return 1;
                }
                break;
            case MVMGCStatus_INTERRUPT:
                GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : thread %d already interrupted\n", to_signal->thread_id);
                return 0;
            case MVMGCStatus_UNABLE:
                /* Otherwise, it's blocked; try to set it to work Stolen. */
                if (MVM_cas(&to_signal->gc_status, MVMGCStatus_UNABLE,
                        MVMGCStatus_STOLEN) == MVMGCStatus_UNABLE) {
                    GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : A blocked thread %d spotted; work stolen\n", to_signal->thread_id);
                    add_work(tc, to_signal);
                    return 0;
                }
                break;
            /* this case occurs if a child thread is Stolen by its parent
             * before we get to it in the chain. */
            case MVMGCStatus_STOLEN:
                GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : thread %d already stolen (it was a spawning child)\n", to_signal->thread_id);
                return 0;
            default:
                MVM_panic(MVM_exitcode_gcorch, "invalid status %"MVM_PRSz" in GC orchestrate\n", MVM_load(&to_signal->gc_status));
                return 0;
        }
    }
}
Esempio n. 12
0
/* This is called when the allocator finds it has run out of memory and wants
 * to trigger a GC run. In this case, it's possible (probable, really) that it
 * will need to do that triggering, notifying other running threads that the
 * time has come to GC. */
void MVM_gc_enter_from_allocator(MVMThreadContext *tc) {
    GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Entered from allocate\n");

    /* Try to start the GC run. */
    if (MVM_trycas(&tc->instance->gc_start, 0, 1)) {
        MVMThread *last_starter = NULL;
        MVMuint32 num_threads = 0;
        MVMuint32 is_full;

        /* Need to wait for other threads to reset their gc_status. */
        while (MVM_load(&tc->instance->gc_ack)) {
            GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE,
                "Thread %d run %d : waiting for other thread's gc_ack\n");
            MVM_platform_thread_yield();
        }

        /* We are the winner of the GC starting race. This gives us some
         * extra responsibilities as well as doing the usual things.
         * First, increment GC sequence number. */
        MVM_incr(&tc->instance->gc_seq_number);
        GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE,
            "Thread %d run %d : GC thread elected coordinator: starting gc seq %d\n",
            (int)MVM_load(&tc->instance->gc_seq_number));

        /* Decide if it will be a full collection. */
        is_full = is_full_collection(tc);

        /* If profiling, record that GC is starting. */
        if (tc->instance->profiling)
            MVM_profiler_log_gc_start(tc, is_full);

        /* Ensure our stolen list is empty. */
        tc->gc_work_count = 0;

        /* Flag that we didn't agree on this run that all the in-trays are
         * cleared (a responsibility of the co-ordinator. */
        MVM_store(&tc->instance->gc_intrays_clearing, 1);

        /* We'll take care of our own work. */
        add_work(tc, tc);

        /* Find other threads, and signal or steal. */
        do {
            MVMThread *threads = (MVMThread *)MVM_load(&tc->instance->threads);
            if (threads && threads != last_starter) {
                MVMThread *head = threads;
                MVMuint32 add;
                while ((threads = (MVMThread *)MVM_casptr(&tc->instance->threads, head, NULL)) != head) {
                    head = threads;
                }

                add = signal_all_but(tc, head, last_starter);
                last_starter = head;
                if (add) {
                    GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Found %d other threads\n", add);
                    MVM_add(&tc->instance->gc_start, add);
                    num_threads += add;
                }
            }

            /* If there's an event loop thread, wake it up to participate. */
            if (tc->instance->event_loop_wakeup)
                uv_async_send(tc->instance->event_loop_wakeup);
        } while (MVM_load(&tc->instance->gc_start) > 1);

        /* Sanity checks. */
        if (!MVM_trycas(&tc->instance->threads, NULL, last_starter))
            MVM_panic(MVM_exitcode_gcorch, "threads list corrupted\n");
        if (MVM_load(&tc->instance->gc_finish) != 0)
            MVM_panic(MVM_exitcode_gcorch, "Finish votes was %"MVM_PRSz"\n", MVM_load(&tc->instance->gc_finish));

        /* gc_ack gets an extra so the final acknowledger
         * can also free the STables. */
        MVM_store(&tc->instance->gc_finish, num_threads + 1);
        MVM_store(&tc->instance->gc_ack, num_threads + 2);
        GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : finish votes is %d\n",
            (int)MVM_load(&tc->instance->gc_finish));

        /* Now we're ready to start, zero promoted since last full collection
         * counter if this is a full collect. */
        if (is_full)
            MVM_store(&tc->instance->gc_promoted_bytes_since_last_full, 0);

        /* Signal to the rest to start */
        GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : coordinator signalling start\n");
        if (MVM_decr(&tc->instance->gc_start) != 1)
            MVM_panic(MVM_exitcode_gcorch, "Start votes was %"MVM_PRSz"\n", MVM_load(&tc->instance->gc_start));

        /* Start collecting. */
        GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : coordinator entering run_gc\n");
        run_gc(tc, MVMGCWhatToDo_All);

        /* Free any STables that have been marked for deletion. It's okay for
         * us to muck around in another thread's fromspace while it's mutating
         * tospace, really. */
        GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Freeing STables if needed\n");
        MVM_gc_collect_free_stables(tc);

        /* If profiling, record that GC is over. */
        if (tc->instance->profiling)
            MVM_profiler_log_gc_end(tc);

        GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : GC complete (cooridnator)\n");
    }
    else {
        /* Another thread beat us to starting the GC sync process. Thus, act as
         * if we were interrupted to GC. */
        GCDEBUG_LOG(tc, MVM_GC_DEBUG_ORCHESTRATE, "Thread %d run %d : Lost coordinator election\n");
        MVM_gc_enter_from_interrupt(tc);
    }
}
Esempio n. 13
0
static MVMint32 is_full_collection(MVMThreadContext *tc) {
    MVMuint64 threshold = MVM_GC_GEN2_THRESHOLD_BASE +
        (tc->instance->num_user_threads * MVM_GC_GEN2_THRESHOLD_THREAD);
    return MVM_load(&tc->instance->gc_promoted_bytes_since_last_full) > threshold;
}
Esempio n. 14
0
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);

        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 {
            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));
            }
            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);
    }
}
Esempio n. 15
0
/* Checks if a thread has marked itself as blocked. Considers that the GC may
 * have stolen its work and marked it as such also. So what this really
 * answers is, "did this thread mark itself blocked, and since then not mark
 * itself unblocked", which is useful if you need to conditionally unblock
 * or re-block. If the status changes from blocked to stolen or stolen to
 * blocked between checking this and calling unblock, it's safe anyway since
 * these cases are handled in MVM_gc_mark_thread_unblocked. Note that this
 * relies on a thread itself only ever calling block/unblock. */
MVMint32 MVM_gc_is_thread_blocked(MVMThreadContext *tc) {
    AO_t gc_status = MVM_load(&(tc->gc_status));
    return gc_status == MVMGCStatus_UNABLE ||
           gc_status == MVMGCStatus_STOLEN;
}
Esempio n. 16
0
/* Goes through the unmarked objects in the second generation heap and builds
 * free lists out of them. Also does any required finalization. */
void MVM_gc_collect_free_gen2_unmarked(MVMThreadContext *tc) {
    /* Visit each of the size class bins. */
    MVMGen2Allocator *gen2 = tc->gen2;
    MVMuint32 bin, obj_size, page, i;
    char ***freelist_insert_pos;
    for (bin = 0; bin < MVM_GEN2_BINS; bin++) {
        /* If we've nothing allocated in this size class, skip it. */
        if (gen2->size_classes[bin].pages == NULL)
            continue;

        /* Calculate object size for this bin. */
        obj_size = (bin + 1) << MVM_GEN2_BIN_BITS;

        /* freelist_insert_pos is a pointer to a memory location that
         * stores the address of the last traversed free list node (char **). */
        /* Initialize freelist insertion position to free list head. */
        freelist_insert_pos = &gen2->size_classes[bin].free_list;

        /* Visit each page. */
        for (page = 0; page < gen2->size_classes[bin].num_pages; page++) {
            /* Visit all the objects, looking for dead ones and reset the
             * mark for each of them. */
            char *cur_ptr = gen2->size_classes[bin].pages[page];
            char *end_ptr = page + 1 == gen2->size_classes[bin].num_pages
                ? gen2->size_classes[bin].alloc_pos
                : cur_ptr + obj_size * MVM_GEN2_PAGE_ITEMS;
            char **last_insert_pos = NULL;
            while (cur_ptr < end_ptr) {
                MVMCollectable *col = (MVMCollectable *)cur_ptr;

                /* Is this already a free list slot? If so, it becomes the
                 * new free list insert position. */
                if (*freelist_insert_pos == (char **)cur_ptr) {
                    freelist_insert_pos = (char ***)cur_ptr;
                }

                /* Otherwise, it must be a collectable of some kind. Is it
                 * live? */
                else if (col->forwarder) {
                    /* Yes; clear the mark. */
                    col->forwarder = NULL;
                }
                else {
                    GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : collecting an object %p in the gen2\n", col);
                    /* No, it's dead. Do any cleanup. */
                    if (!(col->flags & (MVM_CF_TYPE_OBJECT | MVM_CF_STABLE))) {
                        /* Object instance; call gc_free if needed. */
                        MVMObject *obj = (MVMObject *)col;
                        if (REPR(obj)->gc_free)
                            REPR(obj)->gc_free(tc, obj);
                    }
                    else if (col->flags & MVM_CF_TYPE_OBJECT) {
                        /* Type object; doesn't have anything extra that needs freeing. */
                    }
                    else if (col->flags & MVM_CF_STABLE) {
                        if (col->sc == (MVMSerializationContext *)1) {
                            /* We marked it dead last time, kill it. */
                            MVM_6model_stable_gc_free(tc, (MVMSTable *)col);
                        }
                        else {
                            if (MVM_load(&tc->gc_status) == MVMGCStatus_NONE) {
                                /* We're in global destruction, so enqueue to the end
                                 * like we do in the nursery */
                                MVM_gc_collect_enqueue_stable_for_deletion(tc, (MVMSTable *)col);
                            } else {
                                /* There will definitely be another gc run, so mark it as "died last time". */
                                col->sc = (MVMSerializationContext *)1;
                            }
                            /* Skip the freelist updating. */
                            cur_ptr += obj_size;
                            continue;
                        }
                    }
                    else {
                        printf("item flags: %d\n", col->flags);
                        MVM_panic(MVM_exitcode_gcnursery, "Internal error: impossible case encountered in gen2 GC free");
                    }

                    /* Chain in to the free list. */
                    *((char **)cur_ptr) = (char *)*freelist_insert_pos;
                    *freelist_insert_pos = (char **)cur_ptr;

                    /* Update the pointer to the insert position to point to us */
                    freelist_insert_pos = (char ***)cur_ptr;
                }

                /* Move to the next object. */
                cur_ptr += obj_size;
            }
        }
    }
    
    /* Also need to consider overflows. */
    for (i = 0; i < gen2->num_overflows; i++) {
        if (gen2->overflows[i]) {
            MVMCollectable *col = gen2->overflows[i];
            if (col->forwarder) {
                /* A living over-sized object; just clear the mark. */
                col->forwarder = NULL;
            }
            else {
                /* Dead over-sized object. We know if it's this big it cannot
                 * be a type object or STable, so only need handle the simple
                 * object case. */
                if (!(col->flags & (MVM_CF_TYPE_OBJECT | MVM_CF_STABLE))) {
                    MVMObject *obj = (MVMObject *)col;
                    if (REPR(obj)->gc_free)
                        REPR(obj)->gc_free(tc, obj);
                }
                else {
                    MVM_panic(MVM_exitcode_gcnursery, "Internal error: gen2 overflow contains non-object");
                }
                free(col);
                gen2->overflows[i] = NULL;
            }
        }
    }
}