#include "moar.h" /* This allows the dynlex cache to be disabled when bug hunting, if needed. */ #define MVM_DYNLEX_CACHE_ENABLED 1 /* Check spesh candidate pre-selections match the guards. */ #define MVM_SPESH_CHECK_PRESELECTION 0 /* Computes the initial work area for a frame or a specialization of a frame. */ MVMRegister * MVM_frame_initial_work(MVMThreadContext *tc, MVMuint16 *local_types, MVMuint16 num_locals) { MVMuint16 i; MVMRegister *work_initial = MVM_calloc(num_locals, sizeof(MVMRegister)); for (i = 0; i < num_locals; i++) if (local_types[i] == MVM_reg_obj) work_initial[i].o = tc->instance->VMNull; return work_initial; } /* Takes a static frame and does various one-off calculations about what * space it shall need. Also triggers bytecode verification of the frame's * bytecode. Assumes we are holding the CU's deserialize frame mutex (at * the time of writing, this is only called from instrumentation_level_barrier). */ static void prepare_and_verify_static_frame(MVMThreadContext *tc, MVMStaticFrame *static_frame) { MVMStaticFrameBody *static_frame_body = &static_frame->body; MVMCompUnit *cu = static_frame_body->cu; /* Ensure the frame is fully deserialized. */ if (!static_frame_body->fully_deserialized) { MVMROOT(tc, static_frame, { MVM_bytecode_finish_frame(tc, cu, static_frame, 0); }); } /* If we never invoked this compilation unit before, and we have spesh * enabled, we might either have no spesh log or a nearly full one. This * will cause problems with gathering data to OSR hot loops. */ if (!cu->body.invoked) { cu->body.invoked = 1; if (tc->instance->spesh_enabled) MVM_spesh_log_new_compunit(tc); } /* Work size is number of locals/registers plus size of the maximum * call site argument list. */ static_frame_body->work_size = sizeof(MVMRegister) * static_frame_body->num_locals; /* Validate the bytecode. */ MVMROOT(tc, static_frame, { MVM_validate_static_frame(tc, static_frame); }); /* Compute work area initial state that we can memcpy into place each * time. */ if (static_frame_body->num_locals) static_frame_body->work_initial = MVM_frame_initial_work(tc, static_frame_body->local_types, static_frame_body->num_locals); /* Check if we have any state var lexicals. */ if (static_frame_body->static_env_flags) { MVMuint8 *flags = static_frame_body->static_env_flags; MVMint64 numlex = static_frame_body->num_lexicals; MVMint64 i; for (i = 0; i < numlex; i++) if (flags[i] == 2) { static_frame_body->has_state_vars = 1; break; } } /* Allocate the frame's spesh data structure; do it in gen2, both for * the sake of not triggering GC here to avoid a deadlock risk, but * also because then it can be assigned into the gen2 static frame * without causing it to become an inter-gen root. */ MVM_gc_allocate_gen2_default_set(tc); MVM_ASSIGN_REF(tc, &(static_frame->common.header), static_frame_body->spesh, /* no GC error */ MVM_repr_alloc_init(tc, tc->instance->StaticFrameSpesh)); MVM_gc_allocate_gen2_default_clear(tc); } /* When we don't match the current instrumentation level, we hit this. It may * simply be that we never invoked the frame, in which case we prepare and * verify it. It may also be because we need to instrument the code for * profiling. */ static void instrumentation_level_barrier(MVMThreadContext *tc, MVMStaticFrame *static_frame) { MVMCompUnit *cu = static_frame->body.cu; MVMROOT2(tc, static_frame, cu, { /* Obtain mutex, so we don't end up with instrumentation races. */ MVM_reentrantmutex_lock(tc, (MVMReentrantMutex *)cu->body.deserialize_frame_mutex); /* Re-check instrumentation level in case of races. */ if (static_frame->body.instrumentation_level != tc->instance->instrumentation_level) { /* Prepare and verify if needed. */ if (static_frame->body.instrumentation_level == 0) prepare_and_verify_static_frame(tc, static_frame); /* Add profiling instrumentation if needed. */ if (tc->instance->profiling) MVM_profile_instrument(tc, static_frame); else if (tc->instance->cross_thread_write_logging) MVM_cross_thread_write_instrument(tc, static_frame); else if (tc->instance->coverage_logging) MVM_line_coverage_instrument(tc, static_frame); else if (tc->instance->debugserver) MVM_breakpoint_instrument(tc, static_frame); else /* XXX uninstrumenting is currently turned off, due to multithreading * woes. If you add an instrumentation that has to be "turned off" * again at some point, a solution for this problem must be found. */ MVM_profile_ensure_uninstrumented(tc, static_frame); /* Set up inline cache for the frame. */ MVM_disp_inline_cache_setup(tc, static_frame); /* Mark frame as being at the current instrumentation level. */ MVM_barrier(); static_frame->body.instrumentation_level = tc->instance->instrumentation_level; } /* Release the lock. */ MVM_reentrantmutex_unlock(tc, (MVMReentrantMutex *)cu->body.deserialize_frame_mutex); }); } /* Called when the GC destroys a frame. Since the frame may have been alive as * part of a continuation that was taken but never invoked, we should check * things normally cleaned up on return don't need cleaning up also. */ void MVM_frame_destroy(MVMThreadContext *tc, MVMFrame *frame) { MVM_args_proc_cleanup(tc, &frame->params); if (frame->env && !MVM_FRAME_IS_ON_CALLSTACK(tc, frame)) MVM_free(frame->env); if (frame->extra) { MVMFrameExtra *e = frame->extra; MVM_free(e); } } /* Creates a frame for usage as a context only, possibly forcing all of the * static lexicals to be deserialized if it's used for auto-close purposes. * Since we're not creating it to run bytecode, just for the purpose of a * serialized closure, we don't create any call stack record for it. */ static MVMFrame * create_context_only(MVMThreadContext *tc, MVMStaticFrame *static_frame, MVMObject *code_ref, MVMint32 autoclose) { MVMFrame *frame; MVMROOT2(tc, static_frame, code_ref, { /* Ensure the frame is fully deserialized. */ if (!static_frame->body.fully_deserialized) { MVM_reentrantmutex_lock(tc, (MVMReentrantMutex *)static_frame->body.cu->body.deserialize_frame_mutex); if (!static_frame->body.fully_deserialized) MVM_bytecode_finish_frame(tc, static_frame->body.cu, static_frame, 0); MVM_reentrantmutex_unlock(tc, (MVMReentrantMutex *)static_frame->body.cu->body.deserialize_frame_mutex); } frame = MVM_gc_allocate_frame(tc); }); /* Set static frame and code ref. */ MVM_ASSIGN_REF(tc, &(frame->header), frame->static_info, static_frame); MVM_ASSIGN_REF(tc, &(frame->header), frame->code_ref, code_ref); /* Allocate space for lexicals, copying the default lexical environment * into place and, if we're auto-closing, making sure anything we'd clone * is vivified to prevent the clone (which is what creates the correct * BEGIN/INIT semantics). */ if (static_frame->body.env_size) { frame->env = MVM_calloc(1, static_frame->body.env_size); frame->allocd_env = static_frame->body.env_size; if (autoclose) { MVMROOT2(tc, frame, static_frame, { MVMuint16 i; MVMuint16 num_lexicals = static_frame->body.num_lexicals; for (i = 0; i < num_lexicals; i++) { if (!static_frame->body.static_env[i].o && static_frame->body.static_env_flags[i] == 1) { MVMuint32 scid; MVMuint32 objid; if (MVM_bytecode_find_static_lexical_scref(tc, static_frame->body.cu, static_frame, i, &scid, &objid)) { MVMObject *resolved; MVMSerializationContext *sc = MVM_sc_get_sc(tc, static_frame->body.cu, scid); if (sc == NULL) MVM_exception_throw_adhoc(tc, "SC not yet resolved; lookup failed"); resolved = MVM_sc_get_object(tc, sc, objid); MVM_ASSIGN_REF(tc, &(static_frame->common.header), static_frame->body.static_env[i].o, resolved); } } } }); } memcpy(frame->env, static_frame->body.static_env, static_frame->body.env_size); } return frame; } /* Creates a frame that is suitable for deserializing a context into. Starts * with a ref count of 1 due to being held by an SC. */ MVMFrame * MVM_frame_create_context_only(MVMThreadContext *tc, MVMStaticFrame *static_frame, MVMObject *code_ref) { return create_context_only(tc, static_frame, code_ref, 0); } /* Provides auto-close functionality, for the handful of cases where we have * not ever been in the outer frame of something we're invoking. In this case, * we fake up a frame based on the static lexical environment. */ static MVMFrame * autoclose(MVMThreadContext *tc, MVMStaticFrame *needed) { MVMFrame *result; /* First, see if we can find one on the call stack; return it if so. */ MVMFrame *candidate = tc->cur_frame; while (candidate) { if (candidate->static_info->body.bytecode == needed->body.bytecode) return candidate; candidate = candidate->caller; } /* If not, fake up a frame See if it also needs an outer. */ MVMROOT(tc, needed, { result = create_context_only(tc, needed, (MVMObject *)needed->body.static_code, 1); }); if (needed->body.outer) { /* See if the static code object has an outer. */ MVMCode *outer_code = needed->body.outer->body.static_code; if (outer_code->body.outer && outer_code->body.outer->static_info->body.bytecode == needed->body.bytecode) { /* Yes, just take it. */ MVM_ASSIGN_REF(tc, &(result->header), result->outer, outer_code->body.outer); } else { /* Otherwise, recursively auto-close. */ MVMROOT(tc, result, { MVMFrame *ac = autoclose(tc, needed->body.outer); MVM_ASSIGN_REF(tc, &(result->header), result->outer, ac); }); } } return result; } /* Obtains memory for a frame that we are about to enter and run bytecode in. Prefers * the callstack by default, but can put the frame onto the heap if it tends to be * promoted there anyway. Returns a pointer to the frame wherever in memory it ends * up living. Separate versions for specialized and unspecialized frames. */ static MVMFrame * allocate_unspecialized_frame(MVMThreadContext *tc, MVMStaticFrame *static_frame, MVMint32 heap) { MVMFrame *frame; MVMint32 work_size = static_frame->body.work_size; MVMint32 env_size = static_frame->body.env_size; if (heap) { /* Allocate frame on the heap. The callstack record includes space * for the work registers and ->work will have been set up already. */ MVMROOT(tc, static_frame, { if (tc->cur_frame) MVM_frame_force_to_heap(tc, tc->cur_frame); frame = MVM_callstack_allocate_heap_frame(tc, work_size)->frame; }); /* If we have an environment, that needs allocating separately for * heap-based frames. */ if (env_size) { frame->env = MVM_calloc(1, env_size); frame->allocd_env = env_size; } } else { /* Allocate the frame on the call stack. The callstack record includes * space for both the work registers and the environment, and both the * ->work and ->env pointers will have been set up already, but we do * need to clear the environment. */ MVMCallStackFrame *record = MVM_callstack_allocate_frame(tc, work_size, env_size); frame = &(record->frame); memset(frame->env, 0, env_size); } /* Copy frame template with VMNulls in to place. */ memcpy(frame->work, static_frame->body.work_initial, sizeof(MVMRegister) * static_frame->body.num_locals); /* Set static frame and caller before we let this frame escape and the GC * see it. */ frame->static_info = static_frame; frame->caller = tc->cur_frame; return frame; } static MVMFrame * allocate_specialized_frame(MVMThreadContext *tc, MVMStaticFrame *static_frame, MVMSpeshCandidate *spesh_cand, MVMint32 heap) { MVMFrame *frame; MVMint32 work_size = spesh_cand->body.work_size; MVMint32 env_size = spesh_cand->body.env_size; if (heap) { /* Allocate frame on the heap. The callstack record includes space * for the work registers and ->work will have been set up already. */ MVMROOT2(tc, static_frame, spesh_cand, { if (tc->cur_frame) MVM_frame_force_to_heap(tc, tc->cur_frame); frame = MVM_callstack_allocate_heap_frame(tc, work_size)->frame; }); /* Zero out the work memory. Spesh makes sure we have VMNull setup in * the places we need it. */ memset(frame->work, 0, work_size); /* If we have an environment, that needs allocating separately for * heap-based frames. */ if (env_size) { frame->env = MVM_calloc(1, env_size); frame->allocd_env = env_size; } } else { /* Allocate the frame on the call stack. The callstack record includes * space for both the work registers and the environment, and both the * ->work and ->env pointers will have been set up already. We need to * zero out the work and env; we rely on them being contiguous and so * zero them with a single memset call. */ MVMCallStackFrame *record = MVM_callstack_allocate_frame(tc, work_size, env_size); frame = &(record->frame); memset(frame->work, 0, work_size + env_size); } /* Set static frame and caller before we let this frame escape and the GC * see it. */ frame->static_info = static_frame; frame->caller = tc->cur_frame; return frame; } /* Set up a deopt frame. */ void MVM_frame_setup_deopt(MVMThreadContext *tc, MVMFrame *frame, MVMStaticFrame *static_frame, MVMCode *code_ref) { /* Initialize various frame properties. */ frame->static_info = static_frame; frame->code_ref = (MVMObject *)code_ref; frame->outer = code_ref->body.outer; frame->spesh_cand = NULL; frame->spesh_correlation_id = 0; } /* Sets up storage for state variables. We do this after tc->cur_frame became * the current frame, to make sure these new objects will certainly get marked * if GC is triggered along the way. */ static void setup_state_vars(MVMThreadContext *tc, MVMStaticFrame *static_frame) { /* Drag everything out of static_frame_body before we start, * as GC action may invalidate it. */ MVMFrame *frame = tc->cur_frame; MVMRegister *env = static_frame->body.static_env; MVMuint8 *flags = static_frame->body.static_env_flags; MVMint64 numlex = static_frame->body.num_lexicals; MVMRegister *state = NULL; MVMint64 state_act = 0; /* 0 = none so far, 1 = first time, 2 = later */ MVMint64 i; MVMROOT(tc, frame, { for (i = 0; i < numlex; i++) { if (flags[i] == 2) { redo_state: switch (state_act) { case 0: if (MVM_UNLIKELY(!frame->code_ref)) MVM_exception_throw_adhoc(tc, "Frame must have code-ref to have state variables"); state = ((MVMCode *)frame->code_ref)->body.state_vars; if (state) { /* Already have state vars; pull them from this. */ state_act = 2; } else { /* Allocate storage for state vars. */ state = (MVMRegister *)MVM_calloc(1, frame->static_info->body.env_size); ((MVMCode *)frame->code_ref)->body.state_vars = state; state_act = 1; /* Note that this frame should run state init code. */ frame->flags |= MVM_FRAME_FLAG_STATE_INIT; } goto redo_state; case 1: { MVMObject *cloned = MVM_repr_clone(tc, env[i].o); frame->env[i].o = cloned; MVM_ASSIGN_REF(tc, &(frame->code_ref->header), state[i].o, cloned); break; } case 2: frame->env[i].o = state[i].o; break; } } } }); } /* Produces an error on outer frame mis-match. */ static void report_outer_conflict(MVMThreadContext *tc, MVMStaticFrame *static_frame, MVMFrame *outer) { char *frame_cuuid = MVM_string_utf8_encode_C_string(tc, static_frame->body.cuuid); char *frame_name; char *outer_cuuid = MVM_string_utf8_encode_C_string(tc, outer->static_info->body.cuuid); char *outer_name; char *frame_outer_cuuid = MVM_string_utf8_encode_C_string(tc, static_frame->body.outer ? static_frame->body.outer->body.cuuid : tc->instance->str_consts.empty); char *frame_outer_name; char *waste[7] = { frame_cuuid, outer_cuuid, frame_outer_cuuid, NULL, NULL, NULL, NULL }; int waste_counter = 3; if (static_frame->body.name) { frame_name = MVM_string_utf8_encode_C_string(tc, static_frame->body.name); waste[waste_counter++] = frame_name; } else { frame_name = ""; } if (outer->static_info->body.name) { outer_name = MVM_string_utf8_encode_C_string(tc, outer->static_info->body.name); waste[waste_counter++] = outer_name; } else { outer_name = ""; } if (static_frame->body.outer && static_frame->body.outer->body.name) { frame_outer_name = MVM_string_utf8_encode_C_string(tc, static_frame->body.outer->body.name); waste[waste_counter++] = frame_outer_name; } else { frame_outer_name = ""; } MVM_exception_throw_adhoc_free(tc, waste, "When invoking %s '%s', provided outer frame %p (%s '%s') does not match expected static frame %p (%s '%s')", frame_cuuid, frame_name, outer->static_info, outer_cuuid, outer_name, static_frame->body.outer, frame_outer_cuuid, frame_outer_name); } /* Dispatches execution to the specified code object with the specified args. */ void MVM_frame_dispatch(MVMThreadContext *tc, MVMCode *code, MVMArgs args, MVMint32 spesh_cand) { /* Did we get given a specialization? */ MVMStaticFrame *static_frame = code->body.sf; MVMStaticFrameSpesh *spesh; if (spesh_cand < 0) { /* No. In that case it's possible we never even invoked this frame * before, or never at the current instrumentation level; check and * handle this situation if so. */ if (MVM_UNLIKELY(static_frame->body.instrumentation_level != tc->instance->instrumentation_level)) { MVMROOT2(tc, static_frame, code, { instrumentation_level_barrier(tc, static_frame); }); } /* Run the specialization argument guard to see if we can use one. */ spesh = static_frame->body.spesh; spesh_cand = MVM_spesh_arg_guard_run(tc, spesh->body.spesh_arg_guard, args, NULL); } else { spesh = static_frame->body.spesh; #if MVM_SPESH_CHECK_PRESELECTION MVMint32 certain = -1; MVMint32 correct = MVM_spesh_arg_guard_run(tc, spesh->body.spesh_arg_guard, args, &certain); if (spesh_cand != correct && spesh_cand != certain) { fprintf(stderr, "Inconsistent spesh preselection of '%s' (%s): got %d, not %d\n", MVM_string_utf8_encode_C_string(tc, static_frame->body.name), MVM_string_utf8_encode_C_string(tc, static_frame->body.cuuid), spesh_cand, correct); MVM_dump_backtrace(tc); } #endif } /* Ensure we have an outer if needed. This is done ahead of allocating the * new frame, since an autoclose will force the callstack on to the heap. */ MVMFrame *outer = code->body.outer; if (outer) { /* We were provided with an outer frame. Ensure that it is based on the * correct static frame (compare on bytecode address to cope with * nqp::freshcoderef). */ if (MVM_UNLIKELY(static_frame->body.outer == 0 || outer->static_info->body.orig_bytecode != static_frame->body.outer->body.orig_bytecode)) report_outer_conflict(tc, static_frame, outer); } else if (static_frame->body.static_code) { MVMCode *static_code = static_frame->body.static_code; if (static_code->body.outer) { /* We're lacking an outer, but our static code object may have one. * This comes up in the case of cloned protoregexes, for example. */ outer = static_code->body.outer; } else if (static_frame->body.outer) { /* Auto-close, and cache it in the static frame. */ MVMROOT3(tc, static_frame, code, static_code, { MVM_frame_force_to_heap(tc, tc->cur_frame); outer = autoclose(tc, static_frame->body.outer); MVM_ASSIGN_REF(tc, &(static_code->common.header), static_code->body.outer, outer); }); } else { outer = NULL; } } /* Now go by whether we have a specialization. */ MVMFrame *frame; MVMuint8 *chosen_bytecode; if (spesh_cand >= 0) { MVMSpeshCandidate *chosen_cand = spesh->body.spesh_candidates[spesh_cand]; if (static_frame->body.allocate_on_heap) { MVMROOT4(tc, static_frame, code, outer, chosen_cand, { frame = allocate_specialized_frame(tc, static_frame, chosen_cand, 1); }); } else { frame = allocate_specialized_frame(tc, static_frame, chosen_cand, 0); frame->spesh_correlation_id = 0; } frame->code_ref = (MVMObject *)code; frame->outer = outer; if (chosen_cand->body.jitcode) { chosen_bytecode = chosen_cand->body.jitcode->bytecode; frame->jit_entry_label = chosen_cand->body.jitcode->labels[0]; } else { chosen_bytecode = chosen_cand->body.bytecode; } frame->effective_spesh_slots = chosen_cand->body.spesh_slots; frame->spesh_cand = chosen_cand; /* Initialize argument processing. */ MVM_args_proc_setup(tc, &(frame->params), args); } else { MVMint32 on_heap = static_frame->body.allocate_on_heap; if (on_heap) { MVMROOT3(tc, static_frame, code, outer, { frame = allocate_unspecialized_frame(tc, static_frame, 1); }); } else { frame = allocate_unspecialized_frame(tc, static_frame, 0); frame->spesh_cand = NULL; frame->effective_spesh_slots = NULL; frame->spesh_correlation_id = 0; } frame->code_ref = (MVMObject *)code; frame->outer = outer; chosen_bytecode = static_frame->body.bytecode; /* Initialize argument processing. Do this before the GC might process the frame. */ MVM_args_proc_setup(tc, &(frame->params), args); /* If we should be spesh logging, set the correlation ID. */ if (tc->instance->spesh_enabled && tc->spesh_log && static_frame->body.bytecode_size < MVM_SPESH_MAX_BYTECODE_SIZE) { if (spesh->body.spesh_entries_recorded++ < MVM_SPESH_LOG_LOGGED_ENOUGH) { MVMint32 id = ++tc->spesh_cid; frame->spesh_correlation_id = id; MVMROOT3(tc, static_frame, code, outer, { if (on_heap) { MVMROOT(tc, frame, { MVM_spesh_log_entry(tc, id, static_frame, args); }); } else { MVMROOT2(tc, frame->caller, frame->static_info, { MVM_spesh_log_entry(tc, id, static_frame, args); }); } }); } } } MVM_jit_code_trampoline(tc); /* Update interpreter and thread context, so next execution will use this * frame. */ tc->cur_frame = frame; *(tc->interp_cur_op) = chosen_bytecode; *(tc->interp_bytecode_start) = chosen_bytecode; *(tc->interp_reg_base) = frame->work; *(tc->interp_cu) = static_frame->body.cu; if (static_frame->body.has_state_vars) setup_state_vars(tc, static_frame); } /* Dispatches to a frame with zero args. Convenience for various entrypoint * style locations. */ void MVM_frame_dispatch_zero_args(MVMThreadContext *tc, MVMCode *code) { MVMArgs args = { .callsite = MVM_callsite_get_common(tc, MVM_CALLSITE_ID_ZERO_ARITY), .source = NULL, .map = NULL }; MVM_frame_dispatch(tc, code, args, -1); } /* Dispatches to a frame with args set up by C code. Also sets the expected * return type and destination for the return value. */ void MVM_frame_dispatch_from_c(MVMThreadContext *tc, MVMCode *code, MVMCallStackArgsFromC *args_record, MVMRegister *return_value, MVMReturnType return_type) { MVMFrame *cur_frame = tc->cur_frame; cur_frame->return_value = return_value; cur_frame->return_type = return_type; cur_frame->return_address = *(tc->interp_cur_op); MVM_frame_dispatch(tc, code, args_record->args, -1); } /* Moves the specified frame from the stack and on to the heap. Must only * be called if the frame is not already there. Use MVM_frame_force_to_heap * when not sure. */ MVMFrame * MVM_frame_move_to_heap(MVMThreadContext *tc, MVMFrame *frame) { /* To keep things simple, we'll promote all non-promoted frames on the call * stack. We walk the call stack to find them. */ MVMFrame *cur_to_promote = NULL; MVMFrame *new_cur_frame = NULL; MVMFrame *update_caller = NULL; MVMFrame *result = NULL; MVMCallStackIterator iter; MVM_callstack_iter_frame_init(tc, &iter, tc->stack_top); MVM_CHECK_CALLER_CHAIN(tc, cur_to_promote); MVMROOT4(tc, new_cur_frame, update_caller, cur_to_promote, result, { while (MVM_callstack_iter_move_next(tc, &iter)) { /* Check this isn't already a heap or promoted frame; if it is, we're * done. */ MVMCallStackRecord *record = MVM_callstack_iter_current(tc, &iter); if (MVM_callstack_kind_ignoring_deopt(record) != MVM_CALLSTACK_RECORD_FRAME) break; MVMCallStackFrame *unpromoted_record = (MVMCallStackFrame *)record; cur_to_promote = &(unpromoted_record->frame); /* Move any lexical environment to the heap, as it may now * out-live the callstack entry. */ MVMuint32 env_size = cur_to_promote->allocd_env; if (env_size) { MVMRegister *heap_env = MVM_malloc(env_size); memcpy(heap_env, cur_to_promote->env, env_size); cur_to_promote->env = heap_env; } else { /* Stack frames may set up the env pointer even if it's to * an empty area (avoids branches); ensure it is nulled out * so we don't try to do a bogus free later. */ cur_to_promote->env = NULL; } /* Clear any dynamic lexical cache entry, as it may point into an * environment that gets moved to the heap. */ MVMFrameExtra *e = cur_to_promote->extra; if (e) e->dynlex_cache_name = NULL; /* Allocate a heap frame. */ /* frame is safe from the GC as we wouldn't be here if it wasn't on the stack */ MVMFrame *promoted = MVM_gc_allocate_frame(tc); /* Bump heap promotion counter, to encourage allocating this kind * of frame directly on the heap in the future. If the frame was * entered at least 50 times, and over 80% of the entries lead to * an eventual heap promotion, them we'll mark it to be allocated * right away on the heap. Note that entries is only bumped when * spesh logging is taking place, so we only bump the number of * heap promotions in that case too. */ MVMStaticFrame *sf = cur_to_promote->static_info; if (!sf->body.allocate_on_heap && cur_to_promote->spesh_correlation_id) { MVMuint32 promos = sf->body.spesh->body.num_heap_promotions++; MVMuint32 entries = sf->body.spesh->body.spesh_entries_recorded; if (entries > 50 && promos > (4 * entries) / 5) sf->body.allocate_on_heap = 1; } /* Copy current frame's body to it. */ memcpy( (char *)promoted + sizeof(MVMCollectable), (char *)cur_to_promote + sizeof(MVMCollectable), sizeof(MVMFrame) - sizeof(MVMCollectable)); /* Update stack record to indicate the promotion, and make it * reference the heap frame. */ if (record->kind == MVM_CALLSTACK_RECORD_DEOPT_FRAME) record->orig_kind = MVM_CALLSTACK_RECORD_PROMOTED_FRAME; else record->kind = MVM_CALLSTACK_RECORD_PROMOTED_FRAME; ((MVMCallStackPromotedFrame *)record)->frame = promoted; /* Update caller of previously promoted frame, if any. This is the * only reference that might point to a non-heap frame. */ if (update_caller) { MVM_ASSIGN_REF(tc, &(update_caller->header), update_caller->caller, promoted); } /* If we're the first time through the lopo, then we're instead * replacing the current stack top. Note we do it at the end, * so that the GC can still walk unpromoted frames if it runs * in this loop. */ else { new_cur_frame = promoted; } /* If the frame we're promoting was in the active handlers list, * update the address there. */ if (tc->active_handlers) { MVMActiveHandler *ah = tc->active_handlers; while (ah) { if (ah->frame == cur_to_promote) ah->frame = promoted; ah = ah->next_handler; } } /* If we're replacing the frame we were asked to promote, that will * become our result. */ if (cur_to_promote == frame) result = promoted; /* Check if there's a caller, or if we reached the end of the * chain. */ if (cur_to_promote->caller) { if (MVM_FRAME_IS_ON_CALLSTACK(tc, cur_to_promote->caller)) { /* Clear caller in promoted frame, to avoid a heap -> stack * reference if we GC during this loop. */ promoted->caller = NULL; update_caller = promoted; } else { if (cur_to_promote == tc->thread_entry_frame) tc->thread_entry_frame = promoted; MVM_gc_write_barrier(tc, (MVMCollectable*)promoted, (MVMCollectable*)promoted->caller); } } else { /* End of caller chain; check if we promoted the entry * frame */ if (cur_to_promote == tc->thread_entry_frame) tc->thread_entry_frame = promoted; } } }); MVM_CHECK_CALLER_CHAIN(tc, new_cur_frame); /* All is promoted. Update thread's current frame pointer. */ tc->cur_frame = new_cur_frame; /* Hand back new location of promoted frame. */ if (!result) MVM_panic(1, "Failed to find frame to promote on call stack"); return result; } /* This function is to be used by the debugserver if a thread is currently * blocked. */ MVMFrame * MVM_frame_debugserver_move_to_heap(MVMThreadContext *debug_tc, MVMThreadContext *owner, MVMFrame *frame) { /* To keep things simple, we'll promote all non-promoted frames on the call * stack. We walk the call stack to find them. */ MVMFrame *cur_to_promote = NULL; MVMFrame *new_cur_frame = NULL; MVMFrame *update_caller = NULL; MVMFrame *result = NULL; MVMCallStackIterator iter; MVM_callstack_iter_frame_init(owner, &iter, owner->stack_top); MVM_CHECK_CALLER_CHAIN(owner, cur_to_promote); MVMROOT4(debug_tc, new_cur_frame, update_caller, cur_to_promote, result, { while (MVM_callstack_iter_move_next(owner, &iter)) { /* Check this isn't already a heap or promoted frame; if it is, we're * done. */ MVMCallStackRecord *record = MVM_callstack_iter_current(owner, &iter); if (MVM_callstack_kind_ignoring_deopt(record) != MVM_CALLSTACK_RECORD_FRAME) break; MVMCallStackFrame *unpromoted_record = (MVMCallStackFrame *)record; cur_to_promote = &(unpromoted_record->frame); /* Allocate a heap frame. */ /* frame is safe from the GC as we wouldn't be here if it wasn't on the stack */ MVMFrame *promoted = MVM_gc_allocate_frame(debug_tc); /* Copy current frame's body to it. */ memcpy( (char *)promoted + sizeof(MVMCollectable), (char *)cur_to_promote + sizeof(MVMCollectable), sizeof(MVMFrame) - sizeof(MVMCollectable)); /* Update stack record to indicate the promotion, and make it * reference the heap frame. */ if (record->kind == MVM_CALLSTACK_RECORD_DEOPT_FRAME) record->orig_kind = MVM_CALLSTACK_RECORD_PROMOTED_FRAME; else record->kind = MVM_CALLSTACK_RECORD_PROMOTED_FRAME; ((MVMCallStackPromotedFrame *)record)->frame = promoted; /* Update caller of previously promoted frame, if any. This is the * only reference that might point to a non-heap frame. */ if (update_caller) { MVM_ASSIGN_REF(debug_tc, &(update_caller->header), update_caller->caller, promoted); } /* If we're the first time through the lopo, then we're instead * replacing the current stack top. Note we do it at the end, * so that the GC can still walk unpromoted frames if it runs * in this loop. */ else { new_cur_frame = promoted; } /* If the frame we're promoting was in the active handlers list, * update the address there. */ if (owner->active_handlers) { MVMActiveHandler *ah = owner->active_handlers; while (ah) { if (ah->frame == cur_to_promote) ah->frame = promoted; ah = ah->next_handler; } } /* If we're replacing the frame we were asked to promote, that will * become our result. */ if (cur_to_promote == frame) result = promoted; /* Check if there's a caller, or if we reached the end of the * chain. */ if (cur_to_promote->caller) { if (MVM_FRAME_IS_ON_CALLSTACK(owner, cur_to_promote->caller)) { /* Clear caller in promoted frame, to avoid a heap -> stack * reference if we GC during this loop. */ promoted->caller = NULL; update_caller = promoted; } else { if (cur_to_promote == owner->thread_entry_frame) owner->thread_entry_frame = promoted; MVM_gc_write_barrier(debug_tc, (MVMCollectable*)promoted, (MVMCollectable*)promoted->caller); } } else { /* End of caller chain; check if we promoted the entry * frame */ if (cur_to_promote == owner->thread_entry_frame) owner->thread_entry_frame = promoted; } } }); MVM_CHECK_CALLER_CHAIN(owner, new_cur_frame); /* All is promoted. Update thread's current frame pointer. */ owner->cur_frame = new_cur_frame; /* Hand back new location of promoted frame. */ if (!result) MVM_panic(1, "Failed to find frame to promote on foreign thread's call stack"); return result; } /* Attempt to return from the current frame. Returns non-zero if we can, * and zero if there is nowhere to return to (which would signal the exit * of the interpreter). */ static void remove_after_handler(MVMThreadContext *tc, void *sr_data) { MVM_callstack_unwind_frame(tc, 0); } MVMuint64 MVM_frame_try_return(MVMThreadContext *tc) { MVMFrame *cur_frame = tc->cur_frame; if (cur_frame->static_info->body.has_exit_handler && !(cur_frame->flags & MVM_FRAME_FLAG_EXIT_HAND_RUN)) { /* Set us up to run exit handler, and make it so we'll really exit the * frame when that has been done. */ if (tc->cur_frame == tc->thread_entry_frame) MVM_exception_throw_adhoc(tc, "Thread entry point frame cannot have an exit handler"); MVMFrame *caller = cur_frame->caller; if (!caller) MVM_exception_throw_adhoc(tc, "Entry point frame cannot have an exit handler"); MVMHLLConfig *hll = MVM_hll_current(tc); MVMObject *result; if (caller->return_type == MVM_RETURN_OBJ) { result = caller->return_value->o; if (!result) result = tc->instance->VMNull; } else { MVMROOT(tc, cur_frame, { switch (caller->return_type) { case MVM_RETURN_INT: result = MVM_repr_box_int(tc, hll->int_box_type, caller->return_value->i64); break; case MVM_RETURN_UINT: result = MVM_repr_box_uint(tc, hll->int_box_type, caller->return_value->u64); break; case MVM_RETURN_NUM: result = MVM_repr_box_num(tc, hll->num_box_type, caller->return_value->n64); break; case MVM_RETURN_STR: result = MVM_repr_box_str(tc, hll->str_box_type, caller->return_value->s); break; case MVM_RETURN_VOID: result = cur_frame->extra && cur_frame->extra->exit_handler_result ? cur_frame->extra->exit_handler_result : tc->instance->VMNull; break; default: result = tc->instance->VMNull; } }); } cur_frame->flags |= MVM_FRAME_FLAG_EXIT_HAND_RUN; MVM_callstack_allocate_special_return(tc, remove_after_handler, NULL, NULL, 0); MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_OBJ_OBJ)); args_record->args.source[0].o = cur_frame->code_ref; args_record->args.source[1].o = result; MVM_frame_dispatch_from_c(tc, hll->exit_handler, args_record, NULL, MVM_RETURN_VOID); return 1; } else { /* No exit handler, so a straight return. */ return MVM_callstack_unwind_frame(tc, 0); } } /* Try a return from the current frame; skip running any exit handlers. */ MVMuint64 MVM_frame_try_return_no_exit_handlers(MVMThreadContext *tc) { return MVM_callstack_unwind_frame(tc, 0); } /* Unwinds execution state to the specified frame, placing control flow at either * an absolute or relative (to start of target frame) address and optionally * setting a returned result. */ typedef struct { MVMFrame *frame; MVMuint8 *abs_addr; MVMuint32 rel_addr; void *jit_return_label; } MVMUnwindData; static void mark_unwind_data(MVMThreadContext *tc, void *sr_data, MVMGCWorklist *worklist) { MVMUnwindData *ud = (MVMUnwindData *)sr_data; MVM_gc_worklist_add(tc, worklist, &(ud->frame)); } static void continue_unwind(MVMThreadContext *tc, void *sr_data) { MVMUnwindData *ud = (MVMUnwindData *)sr_data; MVMFrame *frame = ud->frame; MVMuint8 *abs_addr = ud->abs_addr; MVMuint32 rel_addr = ud->rel_addr; void *jit_return_label = ud->jit_return_label; MVM_frame_unwind_to(tc, frame, abs_addr, rel_addr, NULL, jit_return_label); } void MVM_frame_unwind_to(MVMThreadContext *tc, MVMFrame *frame, MVMuint8 *abs_addr, MVMuint32 rel_addr, MVMObject *return_value, void *jit_return_label) { /* Lazy deopt means that we might have located an exception handler in * optimized code, but then at the point we call MVM_callstack_unwind_frame we'll * end up deoptimizing it. That means the address here will be out of date. * This can only happen if we actually have frames to unwind; if we are * already in the current frame it cannot. So first handle that local * jump case... */ if (tc->cur_frame == frame) { if (abs_addr) *tc->interp_cur_op = abs_addr; else if (rel_addr) *tc->interp_cur_op = *tc->interp_bytecode_start + rel_addr; if (jit_return_label) MVM_jit_code_set_current_position(tc, tc->cur_frame->spesh_cand->body.jitcode, tc->cur_frame, jit_return_label); } /* Failing that, we'll set things up as if we're doing a return into the * frame, thus tweaking its return address and JIT label. That will cause * MVM_callstack_unwind_frame and any lazy deopt to move use to the right place. */ else { while (tc->cur_frame != frame) { MVMFrame *cur_frame = tc->cur_frame; if (cur_frame->static_info->body.has_exit_handler && !(cur_frame->flags & MVM_FRAME_FLAG_EXIT_HAND_RUN)) { /* We're unwinding a frame with an exit handler. Thus we need to * pause the unwind, run the exit handler, and keep enough info * around in order to finish up the unwind afterwards. */ if (return_value) MVM_exception_throw_adhoc(tc, "return_value + exit_handler case NYI"); /* Force the frame onto the heap, since we'll reference it from the * unwind data. */ MVMROOT3(tc, frame, cur_frame, return_value, { frame = MVM_frame_force_to_heap(tc, frame); cur_frame = tc->cur_frame; }); MVMFrame *caller = cur_frame->caller; if (!caller) MVM_exception_throw_adhoc(tc, "Entry point frame cannot have an exit handler"); if (cur_frame == tc->thread_entry_frame) MVM_exception_throw_adhoc(tc, "Thread entry point frame cannot have an exit handler"); MVMHLLConfig *hll = MVM_hll_current(tc); MVMUnwindData *ud = MVM_callstack_allocate_special_return(tc, continue_unwind, NULL, mark_unwind_data, sizeof(MVMUnwindData)); ud->frame = frame; ud->abs_addr = abs_addr; ud->rel_addr = rel_addr; ud->jit_return_label = jit_return_label; cur_frame->flags |= MVM_FRAME_FLAG_EXIT_HAND_RUN; MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_OBJ_OBJ)); args_record->args.source[0].o = cur_frame->code_ref; args_record->args.source[1].o = tc->instance->VMNull; MVM_frame_dispatch_from_c(tc, hll->exit_handler, args_record, NULL, MVM_RETURN_VOID); return; } else { /* If we're profiling, log an exit. */ if (tc->instance->profiling) MVM_profile_log_unwind(tc); /* No exit handler, so just remove the frame, first tweaking * the return address so we can have the interpreter moved to * the right place. */ MVMFrame *caller = cur_frame->caller; if (caller == frame) { if (abs_addr) caller->return_address = abs_addr; else if (rel_addr) caller->return_address = MVM_frame_effective_bytecode(caller) + rel_addr; if (jit_return_label) caller->jit_entry_label = jit_return_label; } if (MVM_FRAME_IS_ON_CALLSTACK(tc, frame)) { MVMROOT(tc, return_value, { if (!MVM_callstack_unwind_frame(tc, 1)) MVM_panic(1, "Internal error: Unwound entire stack and missed handler"); }); } else { MVMROOT2(tc, return_value, frame, { if (!MVM_callstack_unwind_frame(tc, 1)) MVM_panic(1, "Internal error: Unwound entire stack and missed handler"); }); } } } } if (return_value) MVM_args_set_result_obj(tc, return_value, 1); } /* Gets a code object for a frame, lazily deserializing it if needed. */ MVMObject * MVM_frame_get_code_object(MVMThreadContext *tc, MVMCode *code) { if (MVM_UNLIKELY(REPR(code)->ID != MVM_REPR_ID_MVMCode)) MVM_exception_throw_adhoc(tc, "getcodeobj needs a code ref"); if (!code->body.code_object) { MVMStaticFrame *sf = code->body.sf; if (sf->body.code_obj_sc_dep_idx > 0) { MVMObject *resolved; MVMSerializationContext *sc = MVM_sc_get_sc(tc, sf->body.cu, sf->body.code_obj_sc_dep_idx - 1); if (MVM_UNLIKELY(sc == NULL)) MVM_exception_throw_adhoc(tc, "SC not yet resolved; lookup failed"); MVMROOT(tc, code, { resolved = MVM_sc_get_object(tc, sc, sf->body.code_obj_sc_idx); }); MVM_ASSIGN_REF(tc, &(code->common.header), code->body.code_object, resolved); } } return code->body.code_object ? code->body.code_object : tc->instance->VMNull; } /* Given the specified code object, sets its outer to the current scope. */ void MVM_frame_capturelex(MVMThreadContext *tc, MVMObject *code) { MVMFrame *captured; if (MVM_UNLIKELY(REPR(code)->ID != MVM_REPR_ID_MVMCode)) MVM_exception_throw_adhoc(tc, "Can only perform capturelex on object with representation MVMCode"); MVMROOT(tc, code, { captured = MVM_frame_force_to_heap(tc, tc->cur_frame); }); MVM_ASSIGN_REF(tc, &(code->header), ((MVMCode*)code)->body.outer, captured); } /* This is used for situations in Perl 6 like: * supply { * my $x = something(); * whenever $supply { * QUIT { $x.foo() } * } * } * Here, the QUIT may be called without an invocation of the whenever ever * having taken place. At the point we closure-clone the whenever block, we * will capture_inner the QUIT phaser. This creates a fake outer for the * QUIT, but makes *its* outer point to the nearest instance of the relevant * static frame on the call stack, so that the QUIT will discover the correct * $x. */ void MVM_frame_capture_inner(MVMThreadContext *tc, MVMObject *code) { MVMFrame *outer; MVMROOT(tc, code, { MVMStaticFrame *sf_outer = ((MVMCode*)code)->body.sf->body.outer; MVMROOT(tc, sf_outer, { outer = create_context_only(tc, sf_outer, (MVMObject *)sf_outer->body.static_code, 1); }); MVMROOT(tc, outer, { MVMFrame *outer_outer = autoclose(tc, sf_outer->body.outer); MVM_ASSIGN_REF(tc, &(outer->header), outer->outer, outer_outer); }); }); MVM_ASSIGN_REF(tc, &(code->header), ((MVMCode*)code)->body.outer, outer); } /* Given the specified code object, copies it and returns a copy which * captures a closure over the current scope. */ MVMObject * MVM_frame_takeclosure(MVMThreadContext *tc, MVMObject *code) { MVMCode *closure; MVMFrame *captured; if (MVM_UNLIKELY(REPR(code)->ID != MVM_REPR_ID_MVMCode)) MVM_exception_throw_adhoc(tc, "Can only perform takeclosure on object with representation MVMCode"); MVMROOT(tc, code, { closure = (MVMCode *)REPR(code)->allocate(tc, STABLE(code)); MVMROOT(tc, closure, { captured = MVM_frame_force_to_heap(tc, tc->cur_frame); }); }); MVM_ASSIGN_REF(tc, &(closure->common.header), closure->body.sf, ((MVMCode *)code)->body.sf); MVM_ASSIGN_REF(tc, &(closure->common.header), closure->body.name, ((MVMCode *)code)->body.name); MVM_ASSIGN_REF(tc, &(closure->common.header), closure->body.outer, captured); MVM_ASSIGN_REF(tc, &(closure->common.header), closure->body.code_object, ((MVMCode *)code)->body.code_object); return (MVMObject *)closure; } /* Vivifies a lexical in a frame. */ MVMObject * MVM_frame_vivify_lexical(MVMThreadContext *tc, MVMFrame *f, MVMuint16 idx) { MVMuint8 *flags; MVMint16 flag; MVMRegister *static_env; MVMuint16 effective_idx = 0; MVMStaticFrame *effective_sf; if (idx < f->static_info->body.num_lexicals) { flags = f->static_info->body.static_env_flags; static_env = f->static_info->body.static_env; effective_idx = idx; effective_sf = f->static_info; } else if (f->spesh_cand) { MVMuint32 i; flags = NULL; for (i = 0; i < f->spesh_cand->body.num_inlines; i++) { MVMStaticFrame *isf = f->spesh_cand->body.inlines[i].sf; effective_idx = idx - f->spesh_cand->body.inlines[i].lexicals_start; if (effective_idx < isf->body.num_lexicals) { flags = isf->body.static_env_flags; static_env = isf->body.static_env; effective_sf = isf; break; } } } else { flags = NULL; } flag = flags ? flags[effective_idx] : -1; if (flag != -1 && static_env[effective_idx].o == NULL) { MVMuint32 scid, objid; if (MVM_bytecode_find_static_lexical_scref(tc, effective_sf->body.cu, effective_sf, effective_idx, &scid, &objid)) { MVMSerializationContext *sc; MVMObject *resolved; /* XXX This really ought to go into the bytecode validator * instead for better performance and earlier crashing */ if (effective_sf->body.cu->body.num_scs <= scid) { MVM_exception_throw_adhoc(tc, "Bytecode corruption: illegal sc dependency of lexical: %d > %d", scid, effective_sf->body.cu->body.num_scs); } sc = MVM_sc_get_sc(tc, effective_sf->body.cu, scid); if (sc == NULL) MVM_exception_throw_adhoc(tc, "SC not yet resolved; lookup failed"); MVMROOT2(tc, f, effective_sf, { resolved = MVM_sc_get_object(tc, sc, objid); }); MVM_ASSIGN_REF(tc, &(effective_sf->common.header), effective_sf->body.static_env[effective_idx].o, resolved); } } if (flag == 0) { MVMObject *viv = static_env[effective_idx].o; if (!viv) viv = tc->instance->VMNull; MVM_ASSIGN_REF(tc, &(f->header), f->env[idx].o, viv); return viv; } else if (flag == 1) { MVMObject *viv; MVMROOT(tc, f, { viv = MVM_repr_clone(tc, static_env[effective_idx].o); MVM_ASSIGN_REF(tc, &(f->header), f->env[idx].o, viv); }); return viv; } else { return tc->instance->VMNull; } } /* Looks up the address of the lexical with the specified name and the * specified type. Non-existing object lexicals produce NULL, expected * (for better or worse) by various things. Otherwise, an error is thrown * if it does not exist. Incorrect type always throws. */ int MVM_frame_find_lexical_by_name(MVMThreadContext *tc, MVMString *name, MVMuint16 type, MVMRegister *r) { MVMSpeshFrameWalker fw; MVM_spesh_frame_walker_init_for_outers(tc, &fw, tc->cur_frame); MVMRegister *res = MVM_frame_lexical_lookup_using_frame_walker(tc, &fw, name, type); if (res == NULL) { MVMCode *resolver = tc->cur_frame->static_info->body.cu->body.resolver; if (resolver) { MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_STR)); args_record->args.source[0].s = name; MVM_frame_dispatch_from_c(tc, resolver, args_record, r, MVM_RETURN_OBJ); } else if (MVM_UNLIKELY(type != MVM_reg_obj)) { char *c_name = MVM_string_utf8_encode_C_string(tc, name); char *waste[] = { c_name, NULL }; MVM_exception_throw_adhoc_free(tc, waste, "No lexical found with name '%s'", c_name); } return 0; } else { *r = *res; return 1; } } /* Binds the specified value to the given lexical, finding it along the static * chain. */ MVM_PUBLIC void MVM_frame_bind_lexical_by_name(MVMThreadContext *tc, MVMString *name, MVMuint16 type, MVMRegister value) { MVMFrame *cur_frame = tc->cur_frame; while (cur_frame != NULL) { if (cur_frame->static_info->body.num_lexicals) { MVMuint32 idx = MVM_get_lexical_by_name(tc, cur_frame->static_info, name); if (idx != MVM_INDEX_HASH_NOT_FOUND) { if (cur_frame->static_info->body.lexical_types[idx] == type) { if (type == MVM_reg_obj || type == MVM_reg_str) { MVM_ASSIGN_REF(tc, &(cur_frame->header), cur_frame->env[idx].o, value.o); } else { cur_frame->env[idx] = value; } return; } else { char *c_name = MVM_string_utf8_encode_C_string(tc, name); char *waste[] = { c_name, NULL }; MVM_exception_throw_adhoc_free(tc, waste, "Lexical with name '%s' has wrong type", c_name); } } } cur_frame = cur_frame->outer; } { char *c_name = MVM_string_utf8_encode_C_string(tc, name); char *waste[] = { c_name, NULL }; MVM_exception_throw_adhoc_free(tc, waste, "No lexical found with name '%s'", c_name); } } /* Finds a lexical in the outer frame, throwing if it's not there. */ void MVM_frame_find_lexical_by_name_outer(MVMThreadContext *tc, MVMString *name, MVMRegister *result) { int found; MVMROOT(tc, name, { found = MVM_frame_find_lexical_by_name_rel(tc, name, tc->cur_frame->outer, result); }); if (MVM_UNLIKELY(!found)) { char *c_name = MVM_string_utf8_encode_C_string(tc, name); char *waste[] = { c_name, NULL }; MVM_exception_throw_adhoc_free(tc, waste, "No lexical found with name '%s'", c_name); } } /* Looks up the address of the lexical with the specified name, starting with * the specified frame. Only works if it's an object lexical. */ int MVM_frame_find_lexical_by_name_rel(MVMThreadContext *tc, MVMString *name, MVMFrame *cur_frame, MVMRegister *r) { while (cur_frame != NULL) { if (cur_frame->static_info->body.num_lexicals) { MVMuint32 idx = MVM_get_lexical_by_name(tc, cur_frame->static_info, name); if (idx != MVM_INDEX_HASH_NOT_FOUND) { if (cur_frame->static_info->body.lexical_types[idx] == MVM_reg_obj) { MVMRegister *result = &cur_frame->env[idx]; if (!result->o) MVM_frame_vivify_lexical(tc, cur_frame, idx); *r = *result; return 1; } else { char *c_name = MVM_string_utf8_encode_C_string(tc, name); char *waste[] = { c_name, NULL }; MVM_exception_throw_adhoc_free(tc, waste, "Lexical with name '%s' has wrong type", c_name); } } } cur_frame = cur_frame->outer; } MVMCode *resolver = tc->cur_frame->static_info->body.cu->body.resolver; if (resolver) { MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_STR)); args_record->args.source[0].s = name; MVM_frame_dispatch_from_c(tc, resolver, args_record, r, MVM_RETURN_OBJ); return 1; } return 0; } /* Performs some kind of lexical lookup using the frame walker. The exact walk * that is done depends on the frame walker setup. */ MVMRegister * MVM_frame_lexical_lookup_using_frame_walker(MVMThreadContext *tc, MVMSpeshFrameWalker *fw, MVMString *name, MVMuint16 type) { MVM_gc_root_temp_push(tc, (MVMCollectable **)&(name)); while (MVM_spesh_frame_walker_next(tc, fw)) { MVMRegister *found; MVMuint16 found_kind; if (MVM_spesh_frame_walker_get_lex(tc, fw, name, &found, &found_kind, 1, NULL)) { MVM_spesh_frame_walker_cleanup(tc, fw); MVM_gc_root_temp_pop(tc); if (found_kind == type) { return found; } else { char *c_name = MVM_string_utf8_encode_C_string(tc, name); char *waste[] = { c_name, NULL }; MVM_exception_throw_adhoc_free(tc, waste, "Lexical with name '%s' has wrong type", c_name); } } } MVM_spesh_frame_walker_cleanup(tc, fw); MVM_gc_root_temp_pop(tc); return NULL; } /* Looks up the address of the lexical with the specified name, starting with * the specified frame. It checks all outer frames of the caller frame chain. */ MVMRegister * MVM_frame_find_lexical_by_name_rel_caller(MVMThreadContext *tc, MVMString *name, MVMFrame *cur_caller_frame) { MVMSpeshFrameWalker fw; MVM_spesh_frame_walker_init(tc, &fw, cur_caller_frame, 1); return MVM_frame_lexical_lookup_using_frame_walker(tc, &fw, name, MVM_reg_obj); } /* Looks up the address of the lexical with the specified name and the * specified type. Returns null if it does not exist. */ static void try_cache_dynlex(MVMThreadContext *tc, MVMFrame *from, MVMFrame *to, MVMString *name, MVMRegister *reg, MVMuint16 type, MVMuint32 fcost, MVMuint32 icost) { #if MVM_DYNLEX_CACHE_ENABLED MVMint32 next = 0; MVMint32 frames = 0; MVMuint32 desperation = 0; if (fcost+icost > 20) desperation = 1; while (from && from != to) { frames++; if (frames >= next) { if (!from->extra || !from->extra->dynlex_cache_name || (desperation && frames > 1)) { MVMFrameExtra *e = MVM_frame_extra(tc, from); MVM_ASSIGN_REF(tc, &(from->header), e->dynlex_cache_name, name); e->dynlex_cache_reg = reg; e->dynlex_cache_type = type; if (desperation && next == 3) { next = fcost / 2; } else { if (next) return; next = 3; } } } from = from->caller; } #endif } MVMRegister * MVM_frame_find_dynamic_using_frame_walker(MVMThreadContext *tc, MVMSpeshFrameWalker *fw, MVMString *name, MVMuint16 *type, MVMFrame *initial_frame, MVMint32 vivify, MVMFrame **found_frame) { FILE *dlog = tc->instance->dynvar_log_fh; MVMuint32 fcost = 0; /* frames traversed */ MVMuint32 icost = 0; /* inlines traversed */ MVMuint32 ecost = 0; /* frames traversed with empty cache */ MVMuint32 xcost = 0; /* frames traversed with wrong name */ MVMFrame *last_real_frame = initial_frame; char *c_name; MVMuint64 start_time; MVMuint64 last_time; if (MVM_UNLIKELY(!name)) MVM_exception_throw_adhoc(tc, "Contextual name cannot be null"); if (MVM_UNLIKELY(dlog)) { c_name = MVM_string_utf8_encode_C_string(tc, name); start_time = uv_hrtime(); last_time = tc->instance->dynvar_log_lasttime; } /* Traverse with the frame walker. */ MVM_gc_root_temp_push(tc, (MVMCollectable **)&initial_frame); MVM_gc_root_temp_push(tc, (MVMCollectable **)&last_real_frame); MVM_gc_root_temp_push(tc, (MVMCollectable **)&name); while (MVM_spesh_frame_walker_next(tc, fw)) { MVMRegister *result; /* If we're not currently visiting an inline, then see if we've a * cache entry on this frame. Also track costs. */ if (!MVM_spesh_frame_walker_is_inline(tc, fw)) { MVMFrameExtra *e; last_real_frame = MVM_spesh_frame_walker_current_frame(tc, fw); e = last_real_frame->extra; if (e && e->dynlex_cache_name) { if (MVM_string_equal(tc, name, e->dynlex_cache_name)) { /* Matching cache entry; if it's far from us, try to cache * it closer to us. */ MVMRegister *result = e->dynlex_cache_reg; *type = e->dynlex_cache_type; if (fcost+icost > 5) try_cache_dynlex(tc, initial_frame, last_real_frame, name, result, *type, fcost, icost); if (dlog) { fprintf(dlog, "C %s %d %d %d %d %"PRIu64" %"PRIu64" %"PRIu64"\n", c_name, fcost, icost, ecost, xcost, last_time, start_time, uv_hrtime()); fflush(dlog); MVM_free(c_name); tc->instance->dynvar_log_lasttime = uv_hrtime(); } *found_frame = last_real_frame; MVM_gc_root_temp_pop_n(tc, 3); MVM_spesh_frame_walker_cleanup(tc, fw); return result; } else xcost++; } else ecost++; fcost++; } else icost++; /* See if we have the lexical at this location. */ if (MVM_spesh_frame_walker_get_lex(tc, fw, name, &result, type, vivify, found_frame)) { /* Yes, found it. If we walked some way, try to cache it. */ if (fcost+icost > 1) try_cache_dynlex(tc, initial_frame, last_real_frame, name, result, *type, fcost, icost); if (dlog) { fprintf(dlog, "%s %s %d %d %d %d %"PRIu64" %"PRIu64" %"PRIu64"\n", MVM_spesh_frame_walker_is_inline(tc, fw) ? "I" : "F", c_name, fcost, icost, ecost, xcost, last_time, start_time, uv_hrtime()); fflush(dlog); MVM_free(c_name); tc->instance->dynvar_log_lasttime = uv_hrtime(); } MVM_gc_root_temp_pop_n(tc, 3); MVM_spesh_frame_walker_cleanup(tc, fw); return result; } } /* Not found. */ MVM_gc_root_temp_pop_n(tc, 3); MVM_spesh_frame_walker_cleanup(tc, fw); if (dlog) { fprintf(dlog, "N %s %d %d %d %d %"PRIu64" %"PRIu64" %"PRIu64"\n", c_name, fcost, icost, ecost, xcost, last_time, start_time, uv_hrtime()); fflush(dlog); MVM_free(c_name); tc->instance->dynvar_log_lasttime = uv_hrtime(); } *found_frame = NULL; return NULL; } MVMRegister * MVM_frame_find_contextual_by_name(MVMThreadContext *tc, MVMString *name, MVMuint16 *type, MVMFrame *initial_frame, MVMint32 vivify, MVMFrame **found_frame) { MVMSpeshFrameWalker fw; MVM_spesh_frame_walker_init(tc, &fw, initial_frame, 0); return MVM_frame_find_dynamic_using_frame_walker(tc, &fw, name, type, initial_frame, vivify, found_frame); } void MVM_frame_getdynlex_with_frame_walker(MVMThreadContext *tc, MVMSpeshFrameWalker *fw, MVMString *name, MVMRegister *r) { MVMuint16 type; MVMFrame *found_frame; MVMRegister *lex_reg = MVM_frame_find_dynamic_using_frame_walker(tc, fw, name, &type, MVM_spesh_frame_walker_current_frame(tc, fw), 1, &found_frame); MVMObject *result = NULL, *result_type = NULL; if (lex_reg) { switch (MVM_EXPECT(type, MVM_reg_obj)) { case MVM_reg_int64: result_type = (*tc->interp_cu)->body.hll_config->int_box_type; if (!result_type) MVM_exception_throw_adhoc(tc, "missing int box type"); result = REPR(result_type)->allocate(tc, STABLE(result_type)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&result); if (REPR(result)->initialize) REPR(result)->initialize(tc, STABLE(result), result, OBJECT_BODY(result)); REPR(result)->box_funcs.set_int(tc, STABLE(result), result, OBJECT_BODY(result), lex_reg->i64); MVM_gc_root_temp_pop(tc); break; case MVM_reg_num64: result_type = (*tc->interp_cu)->body.hll_config->num_box_type; if (!result_type) MVM_exception_throw_adhoc(tc, "missing num box type"); result = REPR(result_type)->allocate(tc, STABLE(result_type)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&result); if (REPR(result)->initialize) REPR(result)->initialize(tc, STABLE(result), result, OBJECT_BODY(result)); REPR(result)->box_funcs.set_num(tc, STABLE(result), result, OBJECT_BODY(result), lex_reg->n64); MVM_gc_root_temp_pop(tc); break; case MVM_reg_str: result_type = (*tc->interp_cu)->body.hll_config->str_box_type; if (!result_type) MVM_exception_throw_adhoc(tc, "missing str box type"); result = REPR(result_type)->allocate(tc, STABLE(result_type)); MVM_gc_root_temp_push(tc, (MVMCollectable **)&result); if (REPR(result)->initialize) REPR(result)->initialize(tc, STABLE(result), result, OBJECT_BODY(result)); REPR(result)->box_funcs.set_str(tc, STABLE(result), result, OBJECT_BODY(result), lex_reg->s); MVM_gc_root_temp_pop(tc); break; case MVM_reg_obj: result = lex_reg->o; break; default: MVM_exception_throw_adhoc(tc, "invalid register type in getdynlex: %d", type); } } if (result) { r->o = result; } else { MVMCode *resolver = tc->cur_frame->static_info->body.cu->body.dynamic_resolver; if (resolver) { MVMCallStackArgsFromC *args_record = MVM_callstack_allocate_args_from_c(tc, MVM_callsite_get_common(tc, MVM_CALLSITE_ID_STR)); args_record->args.source[0].s = name; MVM_frame_dispatch_from_c(tc, resolver, args_record, r, MVM_RETURN_OBJ); } else { r->o = tc->instance->VMNull; } } } void MVM_frame_getdynlex(MVMThreadContext *tc, MVMString *name, MVMFrame *cur_frame, MVMRegister *r) { MVMSpeshFrameWalker fw; MVM_spesh_frame_walker_init(tc, &fw, cur_frame, 0); MVM_frame_getdynlex_with_frame_walker(tc, &fw, name, r); } void MVM_frame_binddynlex(MVMThreadContext *tc, MVMString *name, MVMObject *value, MVMFrame *cur_frame) { MVMuint16 type; MVMFrame *found_frame; MVMRegister *lex_reg; MVMROOT2(tc, name, value, { lex_reg = MVM_frame_find_contextual_by_name(tc, name, &type, cur_frame, 0, &found_frame); }); if (!lex_reg) { char *c_name = MVM_string_utf8_encode_C_string(tc, name); char *waste[] = { c_name, NULL }; MVM_exception_throw_adhoc_free(tc, waste, "Dynamic variable '%s' not found", c_name); } switch (type) { case MVM_reg_int64: lex_reg->i64 = REPR(value)->box_funcs.get_int(tc, STABLE(value), value, OBJECT_BODY(value)); break; case MVM_reg_num64: lex_reg->n64 = REPR(value)->box_funcs.get_num(tc, STABLE(value), value, OBJECT_BODY(value)); break; case MVM_reg_str: MVM_ASSIGN_REF(tc, &(found_frame->header), lex_reg->s, REPR(value)->box_funcs.get_str(tc, STABLE(value), value, OBJECT_BODY(value))); break; case MVM_reg_obj: MVM_ASSIGN_REF(tc, &(found_frame->header), lex_reg->o, value); break; default: MVM_exception_throw_adhoc(tc, "invalid register type in binddynlex"); } } /* Returns the storage unit for the lexical in the specified frame. Does not * try to vivify anything - gets exactly what is there. */ MVMRegister * MVM_frame_lexical(MVMThreadContext *tc, MVMFrame *f, MVMString *name) { if (MVM_LIKELY(f->static_info->body.num_lexicals != 0)) { MVMuint32 idx = MVM_get_lexical_by_name(tc, f->static_info, name); if (idx != MVM_INDEX_HASH_NOT_FOUND) return &f->env[idx]; } { char *c_name = MVM_string_utf8_encode_C_string(tc, name); char *waste[] = { c_name, NULL }; MVM_exception_throw_adhoc_free(tc, waste, "Frame has no lexical with name '%s'", c_name); } } /* Returns the storage unit for the lexical in the specified frame. */ MVMRegister * MVM_frame_try_get_lexical(MVMThreadContext *tc, MVMFrame *f, MVMString *name, MVMuint16 type) { if (f->static_info->body.num_lexicals) { MVMuint32 idx = MVM_get_lexical_by_name(tc, f->static_info, name); if (idx != MVM_INDEX_HASH_NOT_FOUND && f->static_info->body.lexical_types[idx] == type) { MVMRegister *result = &f->env[idx]; if (type == MVM_reg_obj && !result->o) MVM_frame_vivify_lexical(tc, f, idx); return result; } } return NULL; } /* Translates a register kind into a primitive storage spec constant. */ MVMuint16 MVM_frame_translate_to_primspec(MVMThreadContext *tc, MVMuint16 kind) { switch (MVM_EXPECT(kind, MVM_reg_obj)) { case MVM_reg_int64: return MVM_STORAGE_SPEC_BP_INT; case MVM_reg_num64: return MVM_STORAGE_SPEC_BP_NUM; case MVM_reg_str: return MVM_STORAGE_SPEC_BP_STR; case MVM_reg_obj: return MVM_STORAGE_SPEC_BP_NONE; case MVM_reg_int8: return MVM_STORAGE_SPEC_BP_INT8; case MVM_reg_int16: return MVM_STORAGE_SPEC_BP_INT16; case MVM_reg_int32: return MVM_STORAGE_SPEC_BP_INT32; case MVM_reg_uint8: return MVM_STORAGE_SPEC_BP_UINT8; case MVM_reg_uint16: return MVM_STORAGE_SPEC_BP_UINT16; case MVM_reg_uint32: return MVM_STORAGE_SPEC_BP_UINT32; case MVM_reg_uint64: return MVM_STORAGE_SPEC_BP_UINT64; default: MVM_exception_throw_adhoc(tc, "Unhandled lexical type '%s' in lexprimspec", MVM_reg_get_debug_name(tc, kind)); } } /* Returns the primitive type specification for a lexical. */ MVMuint16 MVM_frame_lexical_primspec(MVMThreadContext *tc, MVMFrame *f, MVMString *name) { if (f->static_info->body.num_lexicals) { MVMuint32 idx = MVM_get_lexical_by_name(tc, f->static_info, name); if (idx != MVM_INDEX_HASH_NOT_FOUND) return MVM_frame_translate_to_primspec(tc, f->static_info->body.lexical_types[idx]); } { char *c_name = MVM_string_utf8_encode_C_string(tc, name); char *waste[] = { c_name, NULL }; MVM_exception_throw_adhoc_free(tc, waste, "Frame has no lexical with name '%s'", c_name); } } /* Gets, allocating if needed, the frame extra data structure for the given * frame. This is used to hold data that only a handful of frames need. */ MVMFrameExtra * MVM_frame_extra(MVMThreadContext *tc, MVMFrame *f) { if (!f->extra) f->extra = MVM_calloc(1, sizeof(MVMFrameExtra)); return f->extra; } /* Gets the code object of the caller, provided there is one. Works even in * the face that the caller was an inline (however, the current frame that is * using the op must not be itself inlined). */ MVMObject * MVM_frame_caller_code(MVMThreadContext *tc) { MVMObject *result; MVMFrame *f = tc->cur_frame; if (f->caller) { MVMSpeshFrameWalker fw; MVM_spesh_frame_walker_init(tc, &fw, f, 0); MVM_spesh_frame_walker_move_caller(tc, &fw); result = MVM_spesh_frame_walker_get_code(tc, &fw); MVM_spesh_frame_walker_cleanup(tc, &fw); } else { result = tc->instance->VMNull; } return result; }