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; }
/* Processes the current worklist. */ static void process_worklist(MVMThreadContext *tc, MVMGCWorklist *worklist, WorkToPass *wtp, MVMuint8 gen) { MVMGen2Allocator *gen2; MVMCollectable **item_ptr; MVMCollectable *new_addr; MVMuint32 gen2count; /* Grab the second generation allocator; we may move items into the * old generation. */ gen2 = tc->gen2; MVM_gc_worklist_mark_frame_roots(tc, worklist); while ((item_ptr = MVM_gc_worklist_get(tc, worklist))) { /* Dereference the object we're considering. */ MVMCollectable *item = *item_ptr; MVMuint8 item_gen2; MVMuint8 to_gen2 = 0; /* If the item is NULL, that's fine - it's just a null reference and * thus we've no object to consider. */ if (item == NULL) continue; /* If it's in the second generation and we're only doing a nursery, * collection, we have nothing to do. */ item_gen2 = item->flags & MVM_CF_SECOND_GEN; if (item_gen2) { if (gen == MVMGCGenerations_Nursery) continue; if (item->flags & MVM_CF_GEN2_LIVE) { /* gen2 and marked as live. */ continue; } } else if (item->flags & MVM_CF_FORWARDER_VALID) { /* If the item was already seen and copied, then it will have a * forwarding address already. Just update this pointer to the * new address and we're done. */ assert(*item_ptr != item->sc_forward_u.forwarder); if (MVM_GC_DEBUG_ENABLED(MVM_GC_DEBUG_COLLECT)) { if (*item_ptr != item->sc_forward_u.forwarder) { GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : updating handle %p from %p to forwarder %p\n", item_ptr, item, item->sc_forward_u.forwarder); } else { GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : already visited handle %p to forwarder %p\n", item_ptr, item->sc_forward_u.forwarder); } } *item_ptr = item->sc_forward_u.forwarder; continue; } else { /* If the pointer is already into tospace (the bit we've already copied into), we already updated it, so we're done. */ if (item >= (MVMCollectable *)tc->nursery_tospace && item < (MVMCollectable *)tc->nursery_alloc) { continue; } } /* If it's owned by a different thread, we need to pass it over to * the owning thread. */ if (item->owner != tc->thread_id) { GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : sending a handle %p to object %p to thread %d\n", item_ptr, item, item->owner); pass_work_item(tc, wtp, item_ptr); continue; } /* If it's in to-space but *ahead* of our copy offset then it's an out-of-date pointer and we have some kind of corruption. */ if (item >= (MVMCollectable *)tc->nursery_alloc && item < (MVMCollectable *)tc->nursery_alloc_limit) MVM_panic(1, "Heap corruption detected: pointer %p to past fromspace", item); /* At this point, we didn't already see the object, which means we * need to take some action. Go on the generation... */ if (item_gen2) { assert(!(item->flags & MVM_CF_FORWARDER_VALID)); /* It's in the second generation. We'll just mark it. */ new_addr = item; if (MVM_GC_DEBUG_ENABLED(MVM_GC_DEBUG_COLLECT)) { GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : handle %p was already %p\n", item_ptr, new_addr); } item->flags |= MVM_CF_GEN2_LIVE; assert(*item_ptr == new_addr); } else { /* Catch NULL stable (always sign of trouble) in debug mode. */ if (MVM_GC_DEBUG_ENABLED(MVM_GC_DEBUG_COLLECT) && !STABLE(item)) { GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : found a zeroed handle %p to object %p\n", item_ptr, item); printf("%d", ((MVMCollectable *)1)->owner); } /* Did we see it in the nursery before, or should we move it to * gen2 anyway since it a persistent ID was requested? */ if (item->flags & (MVM_CF_NURSERY_SEEN | MVM_CF_HAS_OBJECT_ID)) { /* Yes; we should move it to the second generation. Allocate * space in the second generation. */ to_gen2 = 1; new_addr = item->flags & MVM_CF_HAS_OBJECT_ID ? MVM_gc_object_id_use_allocation(tc, item) : MVM_gc_gen2_allocate(gen2, item->size); /* Add on to the promoted amount (used by profiler). */ tc->gc_promoted_bytes += item->size; /* Copy the object to the second generation and mark it as * living there. */ GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : copying an object %p of size %d to gen2 %p\n", item, item->size, new_addr); memcpy(new_addr, item, item->size); new_addr->flags ^= MVM_CF_NURSERY_SEEN; new_addr->flags |= MVM_CF_SECOND_GEN; /* If it references frames or static frames, we need to keep * on visiting it. */ if (!(new_addr->flags & (MVM_CF_TYPE_OBJECT | MVM_CF_STABLE))) { MVMObject *new_obj_addr = (MVMObject *)new_addr; if (REPR(new_obj_addr)->refs_frames) MVM_gc_root_gen2_add(tc, (MVMCollectable *)new_obj_addr); } /* If we're going to sweep the second generation, also need * to mark it as live. */ if (gen == MVMGCGenerations_Both) new_addr->flags |= MVM_CF_GEN2_LIVE; } else { /* No, so it will live in the nursery for another GC * iteration. Allocate space in the nursery. */ new_addr = (MVMCollectable *)tc->nursery_alloc; tc->nursery_alloc = (char *)tc->nursery_alloc + item->size; GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : copying an object %p (reprid %d) of size %d to tospace %p\n", item, REPR(item)->ID, item->size, new_addr); /* Copy the object to tospace and mark it as seen in the * nursery (so the next time around it will move to the * older generation, if it survives). */ memcpy(new_addr, item, item->size); new_addr->flags |= MVM_CF_NURSERY_SEEN; } /* Store the forwarding pointer and update the original * reference. */ if (MVM_GC_DEBUG_ENABLED(MVM_GC_DEBUG_COLLECT) && new_addr != item) { GCDEBUG_LOG(tc, MVM_GC_DEBUG_COLLECT, "Thread %d run %d : updating handle %p from referent %p (reprid %d) to %p\n", item_ptr, item, REPR(item)->ID, new_addr); } *item_ptr = new_addr; item->sc_forward_u.forwarder = new_addr; /* Set the flag on the copy of item *in fromspace* to mark that the forwarder pointer is valid. */ item->flags |= MVM_CF_FORWARDER_VALID; } /* make sure any rooted frames mark their stuff */ MVM_gc_worklist_mark_frame_roots(tc, worklist); /* Finally, we need to mark the collectable (at its moved address). * Track how many items we had before we mark it, in case we need * to write barrier them post-move to uphold the generational * invariant. */ gen2count = worklist->items; MVM_gc_mark_collectable(tc, worklist, new_addr); /* make sure any rooted frames mark their stuff */ MVM_gc_worklist_mark_frame_roots(tc, worklist); /* In moving an object to generation 2, we may have left it pointing * to nursery objects. If so, make sure it's in the gen2 roots. */ if (to_gen2) { MVMCollectable **j; MVMuint32 max = worklist->items, k; for (k = gen2count; k < max; k++) { j = worklist->list[k]; if (*j) MVM_gc_write_barrier(tc, new_addr, *j); } } } }