Skip to content

Commit

Permalink
Merge branch 'frame-walker'
Browse files Browse the repository at this point in the history
  • Loading branch information
jnthn committed Jul 17, 2018
2 parents 0daa14c + 198f5e4 commit db6cc49
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 84 deletions.
2 changes: 2 additions & 0 deletions build/Makefile.in
Expand Up @@ -209,6 +209,7 @@ OBJECTS = src/core/callsite@obj@ \
src/spesh/plan@obj@ \
src/spesh/arg_guard@obj@ \
src/spesh/plugin@obj@ \
src/spesh/frame_walker@obj@ \
src/strings/decode_stream@obj@ \
src/strings/ascii@obj@ \
src/strings/parse_num@obj@ \
Expand Down Expand Up @@ -371,6 +372,7 @@ HEADERS = src/moar.h \
src/spesh/plan.h \
src/spesh/arg_guard.h \
src/spesh/plugin.h \
src/spesh/frame_walker.h \
src/strings/unicode_gen.h \
src/strings/normalize.h \
src/strings/decode_stream.h \
Expand Down
42 changes: 17 additions & 25 deletions src/core/frame.c
Expand Up @@ -1382,34 +1382,26 @@ MVMRegister * MVM_frame_find_lexical_by_name_rel(MVMThreadContext *tc, MVMString
/* 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) {
while (cur_caller_frame != NULL) {
MVMFrame *cur_frame = cur_caller_frame;
while (cur_frame != NULL) {
MVMLexicalRegistry *lexical_names = cur_frame->static_info->body.lexical_names;
if (lexical_names) {
/* Indexes were formerly stored off-by-one to avoid semi-predicate issue. */
MVMLexicalRegistry *entry;
MVM_HASH_GET(tc, lexical_names, name, entry)
if (entry) {
if (cur_frame->static_info->body.lexical_types[entry->value] == MVM_reg_obj) {
MVMRegister *result = &cur_frame->env[entry->value];
if (!result->o)
MVM_frame_vivify_lexical(tc, cur_frame, entry->value);
return result;
}
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);
}
}
MVMSpeshFrameWalker fw;
MVM_spesh_frame_walker_init(tc, &fw, cur_caller_frame, 1);
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)) {
MVM_spesh_frame_walker_cleanup(tc, &fw);
if (found_kind == MVM_reg_obj) {
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);
}
cur_frame = cur_frame->outer;
}
cur_caller_frame = cur_caller_frame->caller;
}
MVM_spesh_frame_walker_cleanup(tc, &fw);
return NULL;
}

Expand Down
1 change: 1 addition & 0 deletions src/moar.h
Expand Up @@ -165,6 +165,7 @@ MVM_PUBLIC const MVMint32 MVM_jit_support(void);
#include "spesh/plan.h"
#include "spesh/arg_guard.h"
#include "spesh/plugin.h"
#include "spesh/frame_walker.h"
#include "strings/nfg.h"
#include "strings/normalize.h"
#include "strings/decode_stream.h"
Expand Down
119 changes: 60 additions & 59 deletions src/spesh/deopt.c
Expand Up @@ -241,6 +241,43 @@ void MVM_spesh_deopt_one_direct(MVMThreadContext *tc, MVMuint32 deopt_offset,
MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame);
}

/* Takes a frame that is *not* the one currently running on the call stack
* but is in specialized code. Finds the currently active deopt index at
* the point of its latest call. Returns -1 if none can be resolved. */
MVMint32 MVM_spesh_deopt_find_inactive_frame_deopt_idx(MVMThreadContext *tc, MVMFrame *f) {
/* Is it JITted code? */
if (f->spesh_cand->jitcode) {
MVMJitCode *jitcode = f->spesh_cand->jitcode;
MVMint32 idx = MVM_jit_code_get_active_deopt_idx(tc, jitcode, f);
if (idx < jitcode->num_deopts) {
MVMint32 deopt_idx = jitcode->deopts[idx].idx;
#if MVM_LOG_DEOPTS
fprintf(stderr, "Found deopt label for JIT (idx %d)\n", deopt_idx);
#endif
return deopt_idx;
}
}
else {
/* Not JITted; see if we can find the return address in the deopt table. */
MVMint32 ret_offset = f->return_address - f->spesh_cand->bytecode;
MVMint32 n = f->spesh_cand->num_deopts * 2;
MVMint32 i;
for (i = 0; i < n; i += 2) {
if (f->spesh_cand->deopts[i + 1] == ret_offset) {
MVMint32 deopt_idx = i / 2;
#if MVM_LOG_DEOPTS
fprintf(stderr, "Found deopt index for interpeter (idx %d)\n", deopt_idx);
#endif
return deopt_idx;
}
}
}
#if MVM_LOG_DEOPTS
fprintf(stderr, "Can't find deopt all idx\n");
#endif
return -1;
}

/* De-optimizes all specialized frames on the call stack. Used when a change
* is made the could invalidate all kinds of assumptions all over the place
* (such as a mix-in). */
Expand All @@ -259,69 +296,33 @@ void MVM_spesh_deopt_all(MVMThreadContext *tc) {
while (f) {
clear_dynlex_cache(tc, f);
if (f->spesh_cand) {
/* Found one. Is it JITted code? */
if (f->spesh_cand->jitcode) {
MVMJitCode *jitcode = f->spesh_cand->jitcode;
MVMint32 idx = MVM_jit_code_get_active_deopt_idx(tc, jitcode, f);
if (idx < jitcode->num_deopts) {
/* Resolve offset and target. */
MVMint32 deopt_idx = jitcode->deopts[idx].idx;
MVMint32 deopt_offset = f->spesh_cand->deopts[2 * deopt_idx + 1];
MVMint32 deopt_target = f->spesh_cand->deopts[2 * deopt_idx];
#if MVM_LOG_DEOPTS
fprintf(stderr, "Found deopt label for JIT (idx %d)\n", deopt_idx);
#endif

/* Re-create any frames needed if we're in an inline; if not,
* just update return address. */
if (f->spesh_cand->inlines) {
MVMROOT2(tc, f, l, {
uninline(tc, f, f->spesh_cand, deopt_offset, deopt_target, l);
});
}
else {
f->return_address = f->static_info->body.bytecode + deopt_target;
}

/* No spesh cand/slots needed now. */
deopt_named_args_used(tc, f);
f->effective_spesh_slots = NULL;
f->spesh_cand = NULL;
f->jit_entry_label = NULL;
MVMint32 deopt_idx = MVM_spesh_deopt_find_inactive_frame_deopt_idx(tc, f);
if (deopt_idx >= 0) {
/* Re-create any frames needed if we're in an inline; if not,
* just update return address. */
MVMint32 deopt_offset = f->spesh_cand->deopts[2 * deopt_idx + 1];
MVMint32 deopt_target = f->spesh_cand->deopts[2 * deopt_idx];
if (f->spesh_cand->inlines) {
MVMROOT2(tc, f, l, {
uninline(tc, f, f->spesh_cand, deopt_offset, deopt_target, l);
});
}
else {
f->return_address = f->static_info->body.bytecode + deopt_target;
}

/* No spesh cand/slots needed now. */
deopt_named_args_used(tc, f);
f->effective_spesh_slots = NULL;
if (f->spesh_cand->jitcode) {
f->spesh_cand = NULL;
f->jit_entry_label = NULL;
/* XXX This break is wrong and hides a bug. */
break;
}
}

else {
/* Not JITted; see if we can find the return address in the deopt table. */
MVMint32 ret_offset = f->return_address - f->spesh_cand->bytecode;
MVMint32 n = f->spesh_cand->num_deopts * 2;
MVMint32 i;
for (i = 0; i < n; i += 2) {
if (f->spesh_cand->deopts[i + 1] == ret_offset) {
/* Re-create any frames needed if we're in an inline; if not,
* just update return address. */
if (f->spesh_cand->inlines) {
MVMROOT2(tc, f, l, {
uninline(tc, f, f->spesh_cand, ret_offset, f->spesh_cand->deopts[i], l);
});
}
else {
f->return_address = f->static_info->body.bytecode + f->spesh_cand->deopts[i];
}

/* No spesh cand/slots needed now. */
f->effective_spesh_slots = NULL;
f->spesh_cand = NULL;

break;
}
else {
f->spesh_cand = NULL;
}
#if MVM_LOG_DEOPTS
if (i == n)
fprintf(stderr, "Interpreter: can't find deopt all idx\n");
#endif
}
}
l = f;
Expand Down
1 change: 1 addition & 0 deletions src/spesh/deopt.h
Expand Up @@ -2,3 +2,4 @@ void MVM_spesh_deopt_all(MVMThreadContext *tc);
void MVM_spesh_deopt_one(MVMThreadContext *tc, MVMuint32 deopt_target);
void MVM_spesh_deopt_one_direct(MVMThreadContext *tc, MVMuint32 deopt_offset,
MVMuint32 deopt_target);
MVMint32 MVM_spesh_deopt_find_inactive_frame_deopt_idx(MVMThreadContext *tc, MVMFrame *f);
181 changes: 181 additions & 0 deletions src/spesh/frame_walker.c
@@ -0,0 +1,181 @@
#include "moar.h"

/* The spesh-aware frame walker allows walking through the dynamic chain,
* optionally visiting its static chain at each point, and getting correct
* results even if inlining has taken place.*/

/* Initializes the frame walker. The `MVMSpeshFrameWalker` object MUST be on
* the system stack, and the cleanup function MUST be called after using it,
* except in the case of an exception. This is because, since frames are GC
* managed objects, it has to register the pointers to them with the GC, and
* unreigster them after the walk. Must call MVM_spesh_frame_walker_next after
* this to be in a valid state to interrogate the first frame. */
void MVM_spesh_frame_walker_init(MVMThreadContext *tc, MVMSpeshFrameWalker *fw, MVMFrame *start,
MVMuint8 visit_outers) {
fw->cur_caller_frame = start;
fw->cur_outer_frame = NULL;
fw->visit_outers = visit_outers;
fw->started = 0;
fw->visiting_outers = 0;
MVM_gc_root_temp_push(tc, (MVMCollectable **)&(fw->cur_caller_frame));
MVM_gc_root_temp_push(tc, (MVMCollectable **)&(fw->cur_outer_frame));
}

/* Sentinel value to indicate there's no inline to explore. */
#define NO_INLINE -2

/* Go to the next inline, if any. */
static void go_to_next_inline(MVMThreadContext *tc, MVMSpeshFrameWalker *fw) {
MVMFrame *f = fw->cur_caller_frame;
MVMSpeshCandidate *cand = f->spesh_cand;
MVMint32 i;
if (fw->inline_idx == NO_INLINE)
return;
for (i = fw->inline_idx + 1; i < cand->num_inlines; i++) {
if (fw->deopt_offset > cand->inlines[i].start && fw->deopt_offset <= cand->inlines[i].end) {
/* Found an applicable inline. */
fw->inline_idx = i;
return;
}
}

/* No inline available. */
fw->inline_idx = NO_INLINE;
}

/* See if the current frame is specialized, and if so if we are in an inline.
* If so, go to the innermost inline. */
static void go_to_first_inline(MVMThreadContext *tc, MVMSpeshFrameWalker *fw) {
MVMFrame *f = fw->cur_caller_frame;
if (f->spesh_cand && f->spesh_cand->inlines) {
MVMint32 deopt_idx = MVM_spesh_deopt_find_inactive_frame_deopt_idx(tc, f);
if (deopt_idx >= 0) {
fw->deopt_offset = f->spesh_cand->deopts[2 * deopt_idx + 1];
fw->inline_idx = -1;
go_to_next_inline(tc, fw);
return;
}
}
fw->inline_idx = NO_INLINE;
}

/* Moves to the next frame to visit. Returns non-zero if there was a next
* frame to move to, and zero if there is not (and as such the iteration is
* over). */
MVMuint32 MVM_spesh_frame_walker_next(MVMThreadContext *tc, MVMSpeshFrameWalker *fw) {
if (!fw->started) {
fw->started = 1;
go_to_first_inline(tc, fw);
return fw->cur_caller_frame ? 1 : 0;
}
else {
MVMFrame *caller;

/* If we are currently walking an outer chain, proceed along it,
* and return the next frame along unless we reached the end. */
if (fw->cur_outer_frame) {
MVMFrame *outer = fw->cur_outer_frame->outer;
if (outer) {
fw->cur_outer_frame = outer;
return 1;
}
else {
/* End of the outer chain. Clear the currently visiting outer
* chain flag. */
fw->visiting_outers = 0;
}
}

/* Otherwise, we're currently visiting a caller, and it's time to try
* visiting its outers if we're meant to do that. */
else if (fw->visit_outers) {
MVMFrame *outer;
if (fw->inline_idx == NO_INLINE) {
outer = fw->cur_caller_frame->outer;
}
else {
MVMSpeshInline *i = &(fw->cur_caller_frame->spesh_cand->inlines[fw->inline_idx]);
MVMCode *code = (MVMCode *)fw->cur_caller_frame->work[i->code_ref_reg].o;
outer = code ? code->body.outer : NULL;
}
if (outer) {
fw->cur_outer_frame = outer;
fw->visiting_outers = 1;
return 1;
}
}

/* If we get here, we're looking for the next caller, if there is
* one. Is there an inline to try and visit? If there is one, then
* we will be placed on it. If there is not one, we will be placed
* on the base frame containing inlines. Either way, we must have
* something to go to. */
if (fw->inline_idx != NO_INLINE) {
go_to_next_inline(tc, fw);
return 1;
}

/* Otherwise, really need to go out one frame, and then maybe go
* to its first inline. */
caller = fw->cur_caller_frame->caller;
if (caller) {
fw->cur_caller_frame = caller;
go_to_first_inline(tc, fw);
return 1;
}

/* If we get here, there's nowhere further to go. */
return 0;
}
}

/* Gets the lexical in the current frame we're visiting, if it declares one.
* Returns zero if there is no such lexical in that current frame. If there is
* one, returns non-zero and populates found_out and found_kind_out. Will also
* trigger vivification of the lexical if needed. */
MVMuint32 MVM_spesh_frame_walker_get_lex(MVMThreadContext *tc, MVMSpeshFrameWalker *fw,
MVMString *name, MVMRegister **found_out,
MVMuint16 *found_kind_out) {
MVMFrame *cur_frame;
MVMStaticFrame *sf;
MVMuint32 base_index;
MVMLexicalRegistry *lexical_names;
if (fw->visiting_outers) {
cur_frame = fw->cur_outer_frame;
sf = cur_frame->static_info;
base_index = 0;
}
else {
cur_frame = fw->cur_caller_frame;
if (fw->inline_idx == NO_INLINE) {
sf = cur_frame->static_info;
base_index = 0;
}
else {
sf = cur_frame->spesh_cand->inlines[fw->inline_idx].sf;
base_index = cur_frame->spesh_cand->inlines[fw->inline_idx].lexicals_start;
}
}
lexical_names = sf->body.lexical_names;
if (lexical_names) {
/* Indexes were formerly stored off-by-one to avoid semi-predicate issue. */
MVMLexicalRegistry *entry;
MVM_HASH_GET(tc, lexical_names, name, entry)
if (entry) {
MVMint32 index = base_index + entry->value;
MVMRegister *result = &cur_frame->env[index];
MVMuint16 kind = sf->body.lexical_types[entry->value];
*found_out = result;
*found_kind_out = kind;
if (kind == MVM_reg_obj && !result->o)
MVM_frame_vivify_lexical(tc, cur_frame, index);
return 1;
}
}
return 0;
}

/* Cleans up the spesh frame walker after use. */
void MVM_spesh_frame_walker_cleanup(MVMThreadContext *tc, MVMSpeshFrameWalker *fw) {
MVM_gc_root_temp_pop_n(tc, 2);
}

0 comments on commit db6cc49

Please sign in to comment.