/* Allocate the specified amount of memory from the nursery. Will * trigger a GC run if there is not enough. */ void * MVM_gc_allocate_nursery(MVMThreadContext *tc, size_t size) { void *allocated; /* Before an allocation is a GC safe-point and thus a good GC sync point * also; check if we've been signalled to collect. */ /* Don't use a MVM_load(&tc->gc_status) here for performance, it's okay * if the interrupt is delayed a bit. */ if (tc->gc_status) MVM_gc_enter_from_interrupt(tc); /* Guard against 0-byte allocation. */ if (size > 0) { /* Do a GC run if this allocation won't fit in what we have * left in the nursery. Note this is a loop to handle a * pathological case: all the objects in the nursery are very * young and thus survive in the nursery, meaning that no space * actually gets freed up. The next run will promote them to the * second generation. Note that this circumstance is exceptionally * unlikely in any non-contrived situation. */ while ((char *)tc->nursery_alloc + size >= (char *)tc->nursery_alloc_limit) { if (size > MVM_NURSERY_SIZE) MVM_panic(MVM_exitcode_gcalloc, "Attempt to allocate more than the maximum nursery size"); MVM_gc_enter_from_allocator(tc); } /* Allocate (just bump the pointer). */ allocated = tc->nursery_alloc; tc->nursery_alloc = (char *)tc->nursery_alloc + size; } else { MVM_panic(MVM_exitcode_gcalloc, "Cannot allocate 0 bytes of memory in the nursery"); } return allocated; }
/* 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"); } }
/* 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); } }
/* 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) { GCORCH_LOG(tc, "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; /* 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. */ tc->instance->gc_seq_number++; GCORCH_LOG(tc, "Thread %d run %d : GC thread elected coordinator: starting gc seq %d\n", tc->instance->gc_seq_number); /* Ensure our stolen list is empty. */ tc->gc_work_count = 0; /* need to wait for other threads to reset their gc_status. */ while (tc->instance->gc_ack) apr_thread_yield(); add_work(tc, tc); /* grab our child */ signal_child(tc); do { if (tc->instance->threads && tc->instance->threads != last_starter) { MVMThread *head; MVMuint32 add; while (!MVM_trycas(&tc->instance->threads, (head = tc->instance->threads), NULL)); add = signal_all_but(tc, head, last_starter); last_starter = head; if (add) { GCORCH_LOG(tc, "Thread %d run %d : Found %d other threads\n", add); MVM_atomic_add(&tc->instance->gc_start, add); num_threads += add; } } } while (tc->instance->gc_start > 1); if (!MVM_trycas(&tc->instance->threads, NULL, last_starter)) MVM_panic(MVM_exitcode_gcorch, "threads list corrupted\n"); if (tc->instance->gc_finish != 0) MVM_panic(MVM_exitcode_gcorch, "finish votes was %d\n", tc->instance->gc_finish); tc->instance->gc_ack = tc->instance->gc_finish = num_threads + 1; GCORCH_LOG(tc, "Thread %d run %d : finish votes is %d\n", (int)tc->instance->gc_finish); /* signal to the rest to start */ if (MVM_atomic_decr(&tc->instance->gc_start) != 1) MVM_panic(MVM_exitcode_gcorch, "start votes was %d\n", tc->instance->gc_finish); run_gc(tc, MVMGCWhatToDo_All); } else { /* Another thread beat us to starting the GC sync process. Thus, act as * if we were interrupted to GC. */ GCORCH_LOG(tc, "Thread %d run %d : Lost coordinator election\n"); MVM_gc_enter_from_interrupt(tc); } }