/* 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; } }
/* 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 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; } }
/* 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); } }
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; }