/* 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); } } } }
/* Tries to generate a specialization of the bytecode, for the given callsite * and argument tuple. */ MVMSpeshCandidate * MVM_spesh_candidate_generate(MVMThreadContext *tc, MVMStaticFrame *static_frame, MVMCallsite *callsite, MVMRegister *args) { MVMSpeshCandidate *result; MVMSpeshGuard *guards; MVMSpeshCode *sc; MVMint32 num_spesh_slots, num_guards, *deopts, num_deopts; MVMCollectable **spesh_slots; char *before, *after; /* Generate the specialization. */ MVMSpeshGraph *sg = MVM_spesh_graph_create(tc, static_frame); if (tc->instance->spesh_log_fh) before = MVM_spesh_dump(tc, sg); MVM_spesh_args(tc, sg, callsite, args); MVM_spesh_facts_discover(tc, sg); MVM_spesh_optimize(tc, sg); if (tc->instance->spesh_log_fh) after = MVM_spesh_dump(tc, sg); sc = MVM_spesh_codegen(tc, sg); num_spesh_slots = sg->num_spesh_slots; spesh_slots = sg->spesh_slots; num_guards = sg->num_guards; guards = sg->guards; num_deopts = sg->num_deopt_addrs; deopts = sg->deopt_addrs; MVM_spesh_graph_destroy(tc, sg); /* Now try to add it. Note there's a slim chance another thread beat us * to doing so. Also other threads can read the specializations without * lock, so make absolutely sure we increment the count of them after we * add the new one. */ result = NULL; uv_mutex_lock(&tc->instance->mutex_spesh_install); if (static_frame->body.num_spesh_candidates < MVM_SPESH_LIMIT) { MVMint32 num_spesh = static_frame->body.num_spesh_candidates; MVMint32 i; for (i = 0; i < num_spesh; i++) { MVMSpeshCandidate *compare = &static_frame->body.spesh_candidates[i]; if (compare->cs == callsite && compare->num_guards == num_guards && memcmp(compare->guards, guards, num_guards * sizeof(MVMSpeshGuard)) == 0) { /* Beaten! */ result = &static_frame->body.spesh_candidates[i]; break; } } if (!result) { if (!static_frame->body.spesh_candidates) static_frame->body.spesh_candidates = malloc( MVM_SPESH_LIMIT * sizeof(MVMSpeshCandidate)); result = &static_frame->body.spesh_candidates[num_spesh]; result->cs = callsite; result->num_guards = num_guards; result->guards = guards; result->bytecode = sc->bytecode; result->bytecode_size = sc->bytecode_size; result->handlers = sc->handlers; result->num_spesh_slots = num_spesh_slots; result->spesh_slots = spesh_slots; result->num_deopts = num_deopts; result->deopts = deopts; MVM_barrier(); static_frame->body.num_spesh_candidates++; if (static_frame->common.header.flags & MVM_CF_SECOND_GEN) if (!(static_frame->common.header.flags & MVM_CF_IN_GEN2_ROOT_LIST)) MVM_gc_root_gen2_add(tc, (MVMCollectable *)static_frame); if (tc->instance->spesh_log_fh) { char *c_name = MVM_string_utf8_encode_C_string(tc, static_frame->body.name); char *c_cuid = MVM_string_utf8_encode_C_string(tc, static_frame->body.cuuid); fprintf(tc->instance->spesh_log_fh, "Specialized '%s' (cuid: %s)\n\n", c_name, c_cuid); fprintf(tc->instance->spesh_log_fh, "Before:\n%s\nAfter:\n%s\n\n========\n\n", before, after); free(before); free(after); free(c_name); free(c_cuid); } } } if (!result) { free(sc->bytecode); free(sc->handlers); } uv_mutex_unlock(&tc->instance->mutex_spesh_install); free(sc); return result; }