/* This callback handles starting execution of a thread. */ static void start_thread(void *data) { ThreadStart *ts = (ThreadStart *)data; MVMThreadContext *tc = ts->tc; /* Set the current frame in the thread to be the initial caller; * the ref count for this was incremented in the original thread. */ tc->cur_frame = ts->caller; /* wait for the GC to finish if it's not finished stealing us. */ MVM_gc_mark_thread_unblocked(tc); tc->thread_obj->body.stage = MVM_thread_stage_started; /* Enter the interpreter, to run code. */ MVM_interp_run(tc, &thread_initial_invoke, ts); /* mark as exited, so the GC will know to clear our stuff. */ tc->thread_obj->body.stage = MVM_thread_stage_exited; /* Now we're done, decrement the reference count of the caller. */ MVM_frame_dec_ref(tc, ts->caller); /* Mark ourselves as dying, so that another thread will take care * of GC-ing our objects and cleaning up our thread context. */ MVM_gc_mark_thread_blocked(tc); /* hopefully pop the ts->thread_obj temp */ MVM_gc_root_temp_pop(tc); free(ts); /* Exit the thread, now it's completed. */ MVM_platform_thread_exit(NULL); }
/* Unwinds after a handler. */ static void unwind_after_handler(MVMThreadContext *tc, void *sr_data) { MVMFrame *frame; MVMException *exception; MVMuint32 goto_offset; /* Get active handler; sanity check (though it's possible other cases * should be supported). */ MVMActiveHandler *ah = (MVMActiveHandler *)sr_data; if (tc->active_handlers != ah) MVM_panic(1, "Trying to unwind from wrong handler"); /* Grab info we'll need to unwind. */ frame = ah->frame; exception = (MVMException *)ah->ex_obj; goto_offset = ah->handler->goto_offset; /* Clean up. */ tc->active_handlers = ah->next_handler; MVM_frame_dec_ref(tc, ah->frame); free(ah); /* Do the unwinding as needed. */ if (exception && exception->body.return_after_unwind) { MVM_frame_unwind_to(tc, frame->caller, NULL, 0, tc->last_handler_result); } else { MVM_frame_unwind_to(tc, frame, NULL, goto_offset, NULL); } }
/* Return/unwind do about the same thing; this factors it out. */ static MVMuint64 return_or_unwind(MVMThreadContext *tc, MVMuint8 unwind) { MVMFrame *returner = tc->cur_frame; MVMFrame *caller = returner->caller; MVMFrame *prior; /* Decrement the frame reference of the prior invocation, and then * set us as it. */ do { prior = returner->static_info->body.prior_invocation; } while (!MVM_trycas(&returner->static_info->body.prior_invocation, prior, returner)); if (prior) MVM_frame_dec_ref(tc, prior); /* Clear up argument processing leftovers, if any. */ if (returner->work) { MVM_args_proc_cleanup_for_cache(tc, &returner->params); } /* signal to the GC to ignore ->work */ returner->tc = NULL; /* Switch back to the caller frame if there is one; we also need to * decrement its reference count. */ if (caller && returner != tc->thread_entry_frame) { tc->cur_frame = caller; *(tc->interp_cur_op) = caller->return_address; *(tc->interp_bytecode_start) = caller->static_info->body.bytecode; *(tc->interp_reg_base) = caller->work; *(tc->interp_cu) = caller->static_info->body.cu; MVM_frame_dec_ref(tc, caller); returner->caller = NULL; /* Handle any special return hooks. */ if (caller->special_return) { MVMSpecialReturn sr = caller->special_return; caller->special_return = NULL; if (!unwind) sr(tc, caller->special_return_data); } return 1; } else { tc->cur_frame = NULL; return 0; } }
/* Cleans up an active handler record if we unwind over it. */ static void cleanup_active_handler(MVMThreadContext *tc, void *sr_data) { /* Get active handler; sanity check (though it's possible other cases * should be supported). */ MVMActiveHandler *ah = (MVMActiveHandler *)sr_data; if (tc->active_handlers != ah) MVM_panic(1, "Trying to unwind over wrong handler"); /* Clean up. */ tc->active_handlers = ah->next_handler; MVM_frame_dec_ref(tc, ah->frame); free(ah); }
/* Given the specified code object, sets its outer to the current scope. */ void MVM_frame_capturelex(MVMThreadContext *tc, MVMObject *code) { MVMCode *code_obj; if (REPR(code)->ID != MVM_REPR_ID_MVMCode) MVM_exception_throw_adhoc(tc, "Can only perform capturelex on object with representation MVMCode"); /* XXX Following is vulnerable to a race condition. */ code_obj = (MVMCode *)code; if (code_obj->body.outer) MVM_frame_dec_ref(tc, code_obj->body.outer); code_obj->body.outer = MVM_frame_inc_ref(tc, tc->cur_frame); }
/* Unwinds after a handler. */ static void unwind_after_handler(MVMThreadContext *tc, void *sr_data) { /* Get active handler; sanity check (though it's possible other cases * should be supported). */ MVMActiveHandler *ah = (MVMActiveHandler *)sr_data; if (tc->active_handlers != ah) MVM_panic(1, "Trying to unwind from wrong handler"); tc->active_handlers = ah->next_handler; /* Do the unwinding as needed. */ unwind_to_frame(tc, ah->frame); *tc->interp_cur_op = *tc->interp_bytecode_start + ah->handler->goto_offset; /* Clean up. */ MVM_frame_dec_ref(tc, ah->frame); free(ah); }
/* Decreases the reference count of a frame. If it hits zero, then we can * free it. Returns null for convenience. */ MVMFrame * MVM_frame_dec_ref(MVMThreadContext *tc, MVMFrame *frame) { /* MVM_dec returns what the count was before it decremented it * to zero, so we look for 1 here. */ while (MVM_decr(&frame->ref_count) == 1) { MVMuint32 pool_index = frame->static_info->body.pool_index; MVMFrame *node = tc->frame_pool_table[pool_index]; MVMFrame *outer_to_decr = frame->outer; /* If there's a caller pointer, decrement that. */ if (frame->caller) frame->caller = MVM_frame_dec_ref(tc, frame->caller); if (node && MVM_load(&node->ref_count) >= MVMFramePoolLengthLimit) { /* There's no room on the free list, so destruction.*/ if (frame->env) { free(frame->env); frame->env = NULL; } if (frame->work) { MVM_args_proc_cleanup(tc, &frame->params); free(frame->work); frame->work = NULL; } free(frame); } else { /* Unshift it to the free list */ MVM_store(&frame->ref_count, (frame->outer = node) ? MVM_load(&node->ref_count) + 1 : 1); tc->frame_pool_table[pool_index] = frame; } if (outer_to_decr) frame = outer_to_decr; /* and loop */ else break; } return NULL; }
/* If we have to deopt inside of a frame containing inlines, and we're in * an inlined frame at the point we hit deopt, we need to undo the inlining * by switching all levels of inlined frame out for a bunch of frames that * are running the de-optimized code. We may, of course, be in the original, * non-inline, bit of the code - in which case we've nothing to do. */ static void uninline(MVMThreadContext *tc, MVMFrame *f, MVMSpeshCandidate *cand, MVMint32 offset, MVMint32 deopt_offset, MVMFrame *callee) { MVMFrame *last_uninlined = NULL; MVMuint16 last_res_reg; MVMReturnType last_res_type; MVMuint32 last_return_deopt_idx; MVMint32 i; for (i = 0; i < cand->num_inlines; i++) { if (offset >= cand->inlines[i].start && offset < cand->inlines[i].end) { /* Create the frame. */ MVMCode *ucode = cand->inlines[i].code; MVMStaticFrame *usf = ucode->body.sf; MVMFrame *uf = MVM_frame_create_for_deopt(tc, usf, ucode); /*fprintf(stderr, "Recreated frame '%s' (cuid '%s')\n", MVM_string_utf8_encode_C_string(tc, usf->body.name), MVM_string_utf8_encode_C_string(tc, usf->body.cuuid));*/ /* Copy the locals and lexicals into place. */ if (usf->body.num_locals) memcpy(uf->work, f->work + cand->inlines[i].locals_start, usf->body.num_locals * sizeof(MVMRegister)); if (usf->body.num_lexicals) memcpy(uf->env, f->env + cand->inlines[i].lexicals_start, usf->body.num_lexicals * sizeof(MVMRegister)); /* Did we already uninline a frame? */ if (last_uninlined) { /* Yes; multi-level un-inline. Switch it back to deopt'd * code. */ uf->effective_bytecode = uf->static_info->body.bytecode; uf->effective_handlers = uf->static_info->body.handlers; uf->effective_spesh_slots = NULL; uf->spesh_cand = NULL; /* Set up the return location. */ uf->return_address = uf->static_info->body.bytecode + cand->deopts[2 * last_return_deopt_idx]; /* Set result type and register. */ uf->return_type = last_res_type; if (last_res_type == MVM_RETURN_VOID) uf->return_value = NULL; else uf->return_value = uf->work + last_res_reg; /* Set up last uninlined's caller to us. */ last_uninlined->caller = MVM_frame_inc_ref_by_frame(tc, uf); } else { /* First uninlined frame. Are we in the middle of the call * stack (and thus in deopt_all)? */ if (callee) { /* Tweak the callee's caller to the uninlined frame, not * the frame holding the inlinings. */ MVMFrame *orig_caller = callee->caller; callee->caller = MVM_frame_inc_ref_by_frame(tc, uf); MVM_frame_dec_ref(tc, orig_caller); /* Copy over the return location. */ uf->return_address = uf->effective_bytecode + deopt_offset; /* Set result type and register. */ uf->return_type = f->return_type; if (uf->return_type == MVM_RETURN_VOID) { uf->return_value = NULL; } else { MVMuint16 orig_reg = (MVMuint16)(f->return_value - f->work); MVMuint16 ret_reg = orig_reg - cand->inlines[i].locals_start; uf->return_value = uf->work + ret_reg; } } else { /* No, it's the deopt_one case, so this is where we'll point * the interpreter. */ tc->cur_frame = uf; *(tc->interp_cur_op) = uf->effective_bytecode + deopt_offset; *(tc->interp_bytecode_start) = uf->effective_bytecode; *(tc->interp_reg_base) = uf->work; *(tc->interp_cu) = usf->body.cu; } } /* Update tracking variables for last uninline. Note that we know * an inline ends with a goto, which is how we're able to find a * return address offset. */ last_uninlined = uf; last_res_reg = cand->inlines[i].res_reg; last_res_type = cand->inlines[i].res_type; last_return_deopt_idx = cand->inlines[i].return_deopt_idx; } } if (last_uninlined) { /* Set return address, which we need to resolve to the deopt'd one. */ f->return_address = f->static_info->body.bytecode + cand->deopts[2 * last_return_deopt_idx]; /* Set result type and register. */ f->return_type = last_res_type; if (last_res_type == MVM_RETURN_VOID) f->return_value = NULL; else f->return_value = f->work + last_res_reg; /* Set up inliner as the caller, given we now have a direct inline. */ last_uninlined->caller = MVM_frame_inc_ref_by_frame(tc, f); } else { /* Weren't in an inline after all. What kind of deopt? */ if (callee) { /* Deopt all. Move return address. */ f->return_address = f->effective_bytecode + deopt_offset; } else { /* Deopt one. Move interpreter. */ *(tc->interp_cur_op) = f->static_info->body.bytecode + deopt_offset; *(tc->interp_bytecode_start) = f->static_info->body.bytecode; } } }
/* Removes a single frame, as part of a return or unwind. Done after any exit * handler has already been run. */ static MVMuint64 remove_one_frame(MVMThreadContext *tc, MVMuint8 unwind) { MVMFrame *returner = tc->cur_frame; MVMFrame *caller = returner->caller; /* Some cleanup we only need do if we're not a frame involved in a * continuation (otherwise we need to allow for multi-shot * re-invocation). */ if (!returner->in_continuation) { /* Arguments buffer no longer in use (saves GC visiting it). */ returner->cur_args_callsite = NULL; /* Clear up argument processing leftovers, if any. */ if (returner->work) { MVM_args_proc_cleanup_for_cache(tc, &returner->params); } /* Clear up any continuation tags. */ if (returner->continuation_tags) { MVMContinuationTag *tag = returner->continuation_tags; while (tag) { MVMContinuationTag *next = tag->next; free(tag); tag = next; } returner->continuation_tags = NULL; } /* Signal to the GC to ignore ->work */ returner->tc = NULL; /* Unless we need to keep the caller chain in place, clear it up. */ if (caller) { if (!returner->keep_caller) { MVM_frame_dec_ref(tc, caller); returner->caller = NULL; } else if (unwind) { caller->keep_caller = 1; } } } #if MVM_HLL_PROFILE_CALLS tc->profile_data[returner->profile_index].duration_nanos = MVM_platform_now() - tc->profile_data[returner->profile_index].duration_nanos; #endif /* Decrement the frame's ref-count by the 1 it got by virtue of being the * currently executing frame. */ MVM_frame_dec_ref(tc, returner); /* Switch back to the caller frame if there is one. */ if (caller && returner != tc->thread_entry_frame) { tc->cur_frame = caller; *(tc->interp_cur_op) = caller->return_address; *(tc->interp_bytecode_start) = caller->effective_bytecode; *(tc->interp_reg_base) = caller->work; *(tc->interp_cu) = caller->static_info->body.cu; /* Handle any special return hooks. */ if (caller->special_return || caller->special_unwind) { MVMSpecialReturn sr = caller->special_return; MVMSpecialReturn su = caller->special_unwind; caller->special_return = NULL; caller->special_unwind = NULL; if (unwind && su) su(tc, caller->special_return_data); else if (!unwind && sr) sr(tc, caller->special_return_data); caller->mark_special_return_data = NULL; } return 1; } else { tc->cur_frame = NULL; return 0; } }