/* Called at points where we can GC safely during specialization. */ static void spesh_gc_point(MVMThreadContext *tc) { #if MVM_GC_DEBUG tc->in_spesh = 0; #endif GC_SYNC_POINT(tc); #if MVM_GC_DEBUG tc->in_spesh = 1; #endif }
/* Run the global destruction phase. */ void MVM_gc_global_destruction(MVMThreadContext *tc) { char *nursery_tmp; /* Must wait until we're the only thread... */ while (tc->instance->num_user_threads) { GC_SYNC_POINT(tc); MVM_platform_thread_yield(); } /* Fake a nursery collection run by swapping the semi- * space nurseries. */ nursery_tmp = tc->nursery_fromspace; tc->nursery_fromspace = tc->nursery_tospace; tc->nursery_tospace = nursery_tmp; /* Run the objects' finalizers */ MVM_gc_collect_free_nursery_uncopied(tc, tc->nursery_alloc); MVM_gc_root_gen2_cleanup(tc); MVM_gc_collect_free_gen2_unmarked(tc); MVM_gc_collect_free_stables(tc); }
/* Enters the work loop. */ static void worker(MVMThreadContext *tc, MVMCallsite *callsite, MVMRegister *args) { MVMObject *updated_static_frames = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); MVMObject *previous_static_frames = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTArray); tc->instance->speshworker_thread_id = tc->thread_obj->body.thread_id; MVMROOT2(tc, updated_static_frames, previous_static_frames, { while (1) { MVMObject *log_obj; MVMuint64 start_time; unsigned int interval_id; if (MVM_spesh_debug_enabled(tc)) start_time = uv_hrtime(); log_obj = MVM_repr_shift_o(tc, tc->instance->spesh_queue); if (MVM_spesh_debug_enabled(tc)) { MVM_spesh_debug_printf(tc, "Received Logs\n" "=============\n\n" "Was waiting %dus for logs on the log queue.\n\n", (int)((uv_hrtime() - start_time) / 1000)); } if (tc->instance->main_thread->prof_data) MVM_profiler_log_spesh_start(tc); interval_id = MVM_telemetry_interval_start(tc, "spesh worker consuming a log"); uv_mutex_lock(&(tc->instance->mutex_spesh_sync)); tc->instance->spesh_working = 1; uv_mutex_unlock(&(tc->instance->mutex_spesh_sync)); tc->instance->spesh_stats_version++; if (log_obj->st->REPR->ID == MVM_REPR_ID_MVMSpeshLog) { MVMSpeshLog *sl = (MVMSpeshLog *)log_obj; MVM_telemetry_interval_annotate((uintptr_t)sl->body.thread->body.tc, interval_id, "from this thread"); MVMROOT(tc, sl, { MVMThreadContext *stc; MVMuint32 i; MVMuint32 n; /* Update stats, and if we're logging dump each of them. */ tc->instance->spesh_stats_version++; if (MVM_spesh_debug_enabled(tc)) start_time = uv_hrtime(); MVM_spesh_stats_update(tc, sl, updated_static_frames); n = MVM_repr_elems(tc, updated_static_frames); if (MVM_spesh_debug_enabled(tc)) { MVM_spesh_debug_printf(tc, "Statistics Updated\n" "==================\n" "%d frames had their statistics updated in %dus.\n\n", (int)n, (int)((uv_hrtime() - start_time) / 1000)); for (i = 0; i < n; i++) { char *dump = MVM_spesh_dump_stats(tc, (MVMStaticFrame* ) MVM_repr_at_pos_o(tc, updated_static_frames, i)); MVM_spesh_debug_printf(tc, "%s==========\n\n", dump); MVM_free(dump); } } MVM_telemetry_interval_annotate((uintptr_t)n, interval_id, "stats for this many frames"); GC_SYNC_POINT(tc); /* Form a specialization plan. */ if (MVM_spesh_debug_enabled(tc)) start_time = uv_hrtime(); tc->instance->spesh_plan = MVM_spesh_plan(tc, updated_static_frames); if (MVM_spesh_debug_enabled(tc)) { n = tc->instance->spesh_plan->num_planned; MVM_spesh_debug_printf(tc, "Specialization Plan\n" "===================\n" "%u specialization(s) will be produced (planned in %dus).\n\n", n, (int)((uv_hrtime() - start_time) / 1000)); for (i = 0; i < n; i++) { char *dump = MVM_spesh_dump_planned(tc, &(tc->instance->spesh_plan->planned[i])); MVM_spesh_debug_printf(tc, "%s==========\n\n", dump); MVM_free(dump); } } MVM_telemetry_interval_annotate((uintptr_t)tc->instance->spesh_plan->num_planned, interval_id, "this many specializations planned"); GC_SYNC_POINT(tc); /* Implement the plan and then discard it. */ n = tc->instance->spesh_plan->num_planned; for (i = 0; i < n; i++) { MVM_spesh_candidate_add(tc, &(tc->instance->spesh_plan->planned[i])); GC_SYNC_POINT(tc); } MVM_spesh_plan_destroy(tc, tc->instance->spesh_plan); tc->instance->spesh_plan = NULL; /* Clear up stats that didn't get updated for a while, * then add frames updated this time into the previously * updated array. */ MVM_spesh_stats_cleanup(tc, previous_static_frames); n = MVM_repr_elems(tc, updated_static_frames); for (i = 0; i < n; i++) MVM_repr_push_o(tc, previous_static_frames, MVM_repr_at_pos_o(tc, updated_static_frames, i)); /* Clear updated static frames array. */ MVM_repr_pos_set_elems(tc, updated_static_frames, 0); /* Allow the sending thread to produce more logs again, * putting a new spesh log in place if needed. */ stc = sl->body.thread->body.tc; if (stc && !sl->body.was_compunit_bumped) if (MVM_incr(&(stc->spesh_log_quota)) == 0) { stc->spesh_log = MVM_spesh_log_create(tc, sl->body.thread); MVM_telemetry_timestamp(stc, "logging restored after quota had run out"); } /* If needed, signal sending thread that it can continue. */ if (sl->body.block_mutex) { uv_mutex_lock(sl->body.block_mutex); MVM_store(&(sl->body.completed), 1); uv_cond_signal(sl->body.block_condvar); uv_mutex_unlock(sl->body.block_mutex); } { MVMSpeshLogEntry *entries = sl->body.entries; sl->body.entries = NULL; MVM_free(entries); } }); } else if (MVM_is_null(tc, log_obj)) {
MVMObject * MVM_thread_start(MVMThreadContext *tc, MVMObject *invokee, MVMObject *result_type) { int status; ThreadStart *ts; MVMObject *child_obj; /* Create a thread object to wrap it up in. */ MVM_gc_root_temp_push(tc, (MVMCollectable **)&invokee); child_obj = REPR(result_type)->allocate(tc, STABLE(result_type)); MVM_gc_root_temp_pop(tc); if (REPR(child_obj)->ID == MVM_REPR_ID_MVMThread) { MVMThread *child = (MVMThread *)child_obj; MVMThread * volatile *threads; /* Create a new thread context and set it up. */ MVMThreadContext *child_tc = MVM_tc_create(tc->instance); child->body.tc = child_tc; MVM_ASSIGN_REF(tc, child, child->body.invokee, invokee); child_tc->thread_obj = child; child_tc->thread_id = MVM_incr(&tc->instance->next_user_thread_id); /* Create the thread. Note that we take a reference to the current frame, * since it must survive to be the dynamic scope of where the thread was * started, and there's no promises that the thread won't start before * the code creating the thread returns. The count is decremented when * the thread is done. */ ts = malloc(sizeof(ThreadStart)); ts->tc = child_tc; ts->caller = MVM_frame_inc_ref(tc, tc->cur_frame); ts->thread_obj = child_obj; /* push this to the *child* tc's temp roots. */ MVM_gc_root_temp_push(child_tc, (MVMCollectable **)&ts->thread_obj); /* Signal to the GC we have a childbirth in progress. The GC * will null it for us. */ MVM_gc_mark_thread_blocked(child_tc); MVM_ASSIGN_REF(tc, tc->thread_obj, tc->thread_obj->body.new_child, child); /* push to starting threads list */ threads = &tc->instance->threads; do { MVMThread *curr = *threads; MVM_ASSIGN_REF(tc, child, child->body.next, curr); } while (MVM_casptr(threads, child->body.next, child) != child->body.next); status = uv_thread_create(&child->body.thread, &start_thread, ts); if (status < 0) { MVM_panic(MVM_exitcode_compunit, "Could not spawn thread: errorcode %d", status); } /* need to run the GC to clear our new_child field in case we try * try to launch another thread before the GC runs and before the * thread starts. */ GC_SYNC_POINT(tc); } else { MVM_exception_throw_adhoc(tc, "Thread result type must have representation MVMThread"); } return child_obj; }
/* Sees if we have an event loop processing thread set up already, and * sets it up if not. */ static void idle_handler(uv_idle_t *handle, int status) { MVMThreadContext *tc = (MVMThreadContext *)handle->data; GC_SYNC_POINT(tc); if (!setup_work(tc) && !cancel_work(tc)) MVM_thread_yield(tc); }