#include "moar.h" void MVM_continuation_reset(MVMThreadContext *tc, MVMObject *tag, MVMObject *code, MVMRegister *res_reg) { /* Continuations always have their base in a stack region, so we can easily * slice it off at the continuation control point. There are three cases we * might have here. * 1. A reset with code. In this case, we will make a new stack region and * put the tag in it. Common. * 2. A reset with a continuation that is unprotected. In this case, we do * not need to make a new segment, we'll just steal the tag slot in the * continuation we're invoking. Also common. * 3. A reset with a continuation that is protected. Presumably unusual. * We'll make a new stack region with our tag in it. */ /* Were we passed code or a continuation? */ if (REPR(code)->ID == MVM_REPR_ID_MVMContinuation) { /* Continuation; invoke it. */ MVMContinuation *cont = (MVMContinuation *)code; if (cont->body.protected_tag) { MVM_callstack_new_continuation_region(tc, tag); MVM_continuation_invoke(tc, (MVMContinuation *)code, NULL, res_reg, NULL); } else { MVM_continuation_invoke(tc, (MVMContinuation *)code, NULL, res_reg, tag); } } else if (MVM_code_iscode(tc, code)) { /* Run the passed code. */ MVM_callstack_new_continuation_region(tc, tag); MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_ZERO_ARITY)); MVM_frame_dispatch_from_c(tc, (MVMCode *)code, args_record, res_reg, MVM_RETURN_OBJ); } else { MVM_exception_throw_adhoc(tc, "continuationreset requires a continuation or a code handle"); } MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame); } void MVM_continuation_control(MVMThreadContext *tc, MVMint64 protect, MVMObject *tag, MVMObject *code, MVMRegister *res_reg) { if (!MVM_code_iscode(tc, code)) MVM_exception_throw_adhoc(tc, "continuationcontrol requires a code handle"); /* Allocate the continuation (done here so that we don't have any more * allocation while we're slicing the stack frames off). */ MVM_jit_code_trampoline(tc); MVMObject *cont; MVMROOT2(tc, tag, code, { cont = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTContinuation); }); /* Find the tag and slice the required regions off the callstack. */ MVMActiveHandler *active_handler_at_reset; MVMCallStackRecord *orig_top = tc->stack_top; MVMCallStackRegion *taken_region = MVM_callstack_continuation_slice(tc, tag, &active_handler_at_reset); if (!taken_region) MVM_exception_throw_adhoc(tc, "No matching continuation reset found"); /* Clear the caller of the first frame in the taken region. */ MVMFrame *first_frame = MVM_callstack_first_frame_from_region(tc, taken_region); first_frame->caller = NULL; /* Set up the continuation. */ ((MVMContinuation *)cont)->body.stack_top = orig_top; ((MVMContinuation *)cont)->body.first_region = taken_region; ((MVMContinuation *)cont)->body.addr = *tc->interp_cur_op; ((MVMContinuation *)cont)->body.res_reg = res_reg; if (tc->instance->profiling) ((MVMContinuation *)cont)->body.prof_cont = MVM_profile_log_continuation_control(tc, first_frame); /* Save and clear any active exception handler(s) added since reset. */ if (tc->active_handlers != active_handler_at_reset) { MVMActiveHandler *ah = tc->active_handlers; while (ah) { if (ah->next_handler == active_handler_at_reset) { /* Found the handler at the point of reset. Slice off the more * recent ones. */ ((MVMContinuation *)cont)->body.active_handlers = tc->active_handlers; tc->active_handlers = ah->next_handler; ah->next_handler = NULL; break; } ah = ah->next_handler; } } /* Move back to the frame with the reset in it. */ tc->cur_frame = MVM_callstack_current_frame(tc); *(tc->interp_cur_op) = tc->cur_frame->return_address; *(tc->interp_bytecode_start) = MVM_frame_effective_bytecode(tc->cur_frame); *(tc->interp_reg_base) = tc->cur_frame->work; *(tc->interp_cu) = tc->cur_frame->static_info->body.cu; /* If we're protecting the tag, we need to re-instate it again, as reset * would, and also remember it in the continuation. */ if (protect) { MVM_callstack_new_continuation_region(tc, tag); MVM_ASSIGN_REF(tc, &(cont->header), ((MVMContinuation *)cont)->body.protected_tag, tag); } /* Invoke specified code, passing the continuation. We return to * interpreter to run this, which then returns control to the * original reset or invoke. */ MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_OBJ)); args_record->args.source[0].o = cont; MVM_frame_dispatch_from_c(tc, (MVMCode *)code, args_record, tc->cur_frame->return_value, tc->cur_frame->return_type); MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame); } void MVM_continuation_invoke(MVMThreadContext *tc, MVMContinuation *cont, MVMObject *code, MVMRegister *res_reg, MVMObject *insert_tag) { /* First of all do a repr id check */ if (REPR(cont)->ID != MVM_REPR_ID_MVMContinuation) MVM_exception_throw_adhoc(tc, "continuationinvoke expects an MVMContinuation"); if (!(MVM_is_null(tc, code) || MVM_code_iscode(tc, code))) MVM_exception_throw_adhoc(tc, "continuationinvoke requires a code handle"); /* Ensure we are the only invoker of the continuation. */ if (!MVM_trycas(&(cont->body.invoked), 0, 1)) MVM_exception_throw_adhoc(tc, "This continuation has already been invoked"); /* Walk the frames we are going to put atop of the stack, and see if any * are heap frames. Also clear their dynamic tag. */ MVMCallStackIterator iter; MVMFrame *bottom_frame = NULL; MVMuint32 have_heap_frame = 0; MVM_callstack_iter_frame_init(tc, &iter, cont->body.stack_top); while (MVM_callstack_iter_move_next(tc, &iter)) { MVMFrame *cur_frame = MVM_callstack_iter_current_frame(tc, &iter); if (cur_frame->extra) cur_frame->extra->dynlex_cache_name = NULL; if (!MVM_FRAME_IS_ON_CALLSTACK(tc, cur_frame)) have_heap_frame = 1; bottom_frame = cur_frame; } if (!bottom_frame) MVM_exception_throw_adhoc(tc, "Corrupt continuation: failed to find bottom frame"); /* Force current frames to heap if there are heap frames in the continuation, * to maintain the no heap -> stack invariant. */ if (have_heap_frame) { MVMROOT3(tc, cont, code, bottom_frame, { MVM_frame_force_to_heap(tc, tc->cur_frame); }); } /* Switch caller of the root to current invoker. */ if (MVM_FRAME_IS_ON_CALLSTACK(tc, tc->cur_frame)) { bottom_frame->caller = tc->cur_frame; } else { MVM_ASSIGN_REF(tc, &(bottom_frame->header), bottom_frame->caller, tc->cur_frame); } /* Splice the call stack region(s) from the continuation onto the top * of our callstack, optionally replacing the continuation tag record. * Then detach it from the wrapper object. */ MVM_callstack_continuation_append(tc, cont->body.first_region, cont->body.stack_top, cont->body.protected_tag ? cont->body.protected_tag : insert_tag); cont->body.first_region = NULL; cont->body.stack_top = NULL; /* Set up current frame to receive result. */ tc->cur_frame->return_value = res_reg; tc->cur_frame->return_type = MVM_RETURN_OBJ; tc->cur_frame->return_address = *(tc->interp_cur_op); MVM_jit_code_trampoline(tc); /* Sync up current frame to the target frame and the interpreter. */ tc->cur_frame = MVM_callstack_current_frame(tc); *(tc->interp_cur_op) = cont->body.addr; *(tc->interp_bytecode_start) = MVM_frame_effective_bytecode(tc->cur_frame); *(tc->interp_reg_base) = tc->cur_frame->work; *(tc->interp_cu) = tc->cur_frame->static_info->body.cu; /* Put saved active handlers list in place. */ /* If we ever need to support multi-shot, continuations this needs a re-visit. * As it is, Rakudo's gather/take only needs single-invoke continuations, * so we'll punt on the issue for now. */ if (cont->body.active_handlers) { MVMActiveHandler *ah = cont->body.active_handlers; while (ah->next_handler) ah = ah->next_handler; ah->next_handler = tc->active_handlers; tc->active_handlers = cont->body.active_handlers; cont->body.active_handlers = NULL; } /* If we're profiling, get it back in sync. */ if (cont->body.prof_cont && tc->instance->profiling) MVM_profile_log_continuation_invoke(tc, cont->body.prof_cont); /* Provided we have it, invoke the specified code, putting its result in * the specified result register. Otherwise, put a NULL there. */ if (MVM_is_null(tc, code)) { cont->body.res_reg->o = tc->instance->VMNull; } else { MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_ZERO_ARITY)); MVM_frame_dispatch_from_c(tc, (MVMCode *)code, args_record, cont->body.res_reg, MVM_RETURN_OBJ); } MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame); }