Skip to content

Commit

Permalink
Provide a bind failure -> resumption mechanism
Browse files Browse the repository at this point in the history
The assertparamcheck op is used in the binding code emitted by Rakudo,
and so far has always triggered a signature binding error handler. This
makes it possible to invoke some bytecode such that if there is a
binding failure (that is, assertparamcheck is used with a zero value),
then instead of calling the bind failure callback, the frame will be
removed from the callstack and the `boot-resume` dispatcher will be
invoked. This will resume the ongoing dispatch.

This will be used in Rakudo for when we have multiple dispatch with
things like `where` clauses, which needs a bind check. Today we always
do all of the binding work twice in such a situation, which is rather
wasteful. This will hopefully - after a little re-work in Rakudo - give
us a way to avoid that in new-disp.

The resumption dispatch has an inline cache stacked up at the location
of the assertparamcheck op. Since such a bind failure will typically
take us to the next multi candidate, this should hopefully end up being
monomorphic (or only lightly polymorphic). This will also allow spesh to
find out what typically happens after the bind check fails, which maybe
offers some interesting future optimization opportunities.

Even without that, it should mean that the only step between a `where`
clause failing and us trying the next candidate is running a dispatch
program, rather than having to run any bytecode, which is also, at least
in theory, quite a win over the current situation too.
  • Loading branch information
jnthn committed Apr 14, 2021
1 parent cbb4127 commit 3d452a5
Show file tree
Hide file tree
Showing 17 changed files with 274 additions and 71 deletions.
1 change: 1 addition & 0 deletions src/6model/bootstrap.c
Expand Up @@ -587,6 +587,7 @@ static void string_consts(MVMThreadContext *tc) {
string_creator(config, "config");
string_creator(replacement, "replacement");
string_creator(dot, ".");
string_creator(boot_resume, "boot-resume");
}

/* Drives the overall bootstrap process. */
Expand Down
56 changes: 37 additions & 19 deletions src/core/args.c
Expand Up @@ -1404,23 +1404,41 @@ static void mark_sr_data(MVMThreadContext *tc, MVMFrame *frame, MVMGCWorklist *w
MVMRegister *r = (MVMRegister *)frame->extra->special_return_data;
MVM_gc_worklist_add(tc, worklist, &r->o);
}
void MVM_args_bind_failed(MVMThreadContext *tc) {
MVMRegister *res;
MVMCallsite *inv_arg_callsite;

/* Capture arguments into a call capture, to pass off for analysis. */
MVMObject *cc_obj = MVM_args_save_capture(tc, tc->cur_frame);

/* Invoke the HLL's bind failure handler. */
MVMFrame *cur_frame = tc->cur_frame;
MVMObject *bind_error = MVM_hll_current(tc)->bind_error;
if (!bind_error)
MVM_exception_throw_adhoc(tc, "Bind error occurred, but HLL has no handler");
bind_error = MVM_frame_find_invokee(tc, bind_error, NULL);
res = MVM_calloc(1, sizeof(MVMRegister));
inv_arg_callsite = MVM_callsite_get_common(tc, MVM_CALLSITE_ID_OBJ);
MVM_args_setup_thunk(tc, res, MVM_RETURN_OBJ, inv_arg_callsite);
MVM_frame_special_return(tc, cur_frame, bind_error_return, bind_error_unwind, res, mark_sr_data);
cur_frame->args[0].o = cc_obj;
STABLE(bind_error)->invoke(tc, bind_error, inv_arg_callsite, cur_frame->args);
void MVM_args_bind_failed(MVMThreadContext *tc, MVMDispInlineCacheEntry **ice_ptr) {
/* There are two situations we may be in. Either we are doing a dispatch
* that wishes to resume upon a bind failure, or we need to trigger the
* bind failure handler. This is determined by if there is a bind failure
* frame under us on the callstack in a fresh state. */
MVMCallStackRecord *under_us = tc->stack_top->prev;
if (under_us->kind == MVM_CALLSTACK_RECORD_BIND_FAILURE &&
((MVMCallStackBindFailure *)under_us)->state == MVM_BIND_FAILURE_FRESH) {
/* The dispatch resumption case. Flag the failure, then do a return
* without running an exit handlers. */
MVMCallStackBindFailure *failure_record = (MVMCallStackBindFailure *)under_us;
failure_record->state = MVM_BIND_FAILURE_FAILED;
failure_record->ice_ptr = ice_ptr;
failure_record->sf = tc->cur_frame->static_info;
MVM_frame_try_return_no_exit_handlers(tc);
}
else {
/* The error case. */
MVMRegister *res;
MVMCallsite *inv_arg_callsite;

/* Capture arguments into a call capture, to pass off for analysis. */
MVMObject *cc_obj = MVM_args_save_capture(tc, tc->cur_frame);

/* Invoke the HLL's bind failure handler. */
MVMFrame *cur_frame = tc->cur_frame;
MVMObject *bind_error = MVM_hll_current(tc)->bind_error;
if (!bind_error)
MVM_exception_throw_adhoc(tc, "Bind error occurred, but HLL has no handler");
bind_error = MVM_frame_find_invokee(tc, bind_error, NULL);
res = MVM_calloc(1, sizeof(MVMRegister));
inv_arg_callsite = MVM_callsite_get_common(tc, MVM_CALLSITE_ID_OBJ);
MVM_args_setup_thunk(tc, res, MVM_RETURN_OBJ, inv_arg_callsite);
MVM_frame_special_return(tc, cur_frame, bind_error_return, bind_error_unwind, res, mark_sr_data);
cur_frame->args[0].o = cc_obj;
STABLE(bind_error)->invoke(tc, bind_error, inv_arg_callsite, cur_frame->args);
}
}
2 changes: 1 addition & 1 deletion src/core/args.h
Expand Up @@ -147,7 +147,7 @@ MVM_PUBLIC void MVM_args_setup_thunk(MVMThreadContext *tc, MVMRegister *return_v
MVMCallsite *callsite);

/* Custom bind failure handling. */
void MVM_args_bind_failed(MVMThreadContext *tc);
void MVM_args_bind_failed(MVMThreadContext *tc, MVMDispInlineCacheEntry **ice_ptr);

/* Result setting frame constants. */
#define MVM_RETURN_CALLER_FRAME 0
Expand Down
7 changes: 7 additions & 0 deletions src/core/callsite.c
Expand Up @@ -7,6 +7,9 @@ static MVMCallsite zero_arity_callsite = { NULL, 0, 0, 0, 0, 0, 0, 0 };
static MVMCallsiteEntry obj_arg_flags[] = { MVM_CALLSITE_ARG_OBJ };
static MVMCallsite obj_callsite = { obj_arg_flags, 1, 1, 1, 0, 0, 0, 0 };

static MVMCallsiteEntry int_arg_flags[] = { MVM_CALLSITE_ARG_INT };
static MVMCallsite int_callsite = { int_arg_flags, 1, 1, 1, 0, 0, 0, 0 };

static MVMCallsiteEntry obj_obj_arg_flags[] = { MVM_CALLSITE_ARG_OBJ,
MVM_CALLSITE_ARG_OBJ };
static MVMCallsite obj_obj_callsite = { obj_obj_arg_flags, 2, 2, 2, 0, 0, NULL, NULL };
Expand Down Expand Up @@ -52,6 +55,8 @@ void MVM_callsite_initialize_common(MVMThreadContext *tc) {
MVM_callsite_intern(tc, &ptr, 0, 1);
ptr = &obj_callsite;
MVM_callsite_intern(tc, &ptr, 0, 1);
ptr = &int_callsite;
MVM_callsite_intern(tc, &ptr, 0, 1);
ptr = &obj_obj_callsite;
MVM_callsite_intern(tc, &ptr, 0, 1);
ptr = &obj_int_callsite;
Expand All @@ -75,6 +80,8 @@ MVM_PUBLIC MVMCallsite * MVM_callsite_get_common(MVMThreadContext *tc, MVMCommon
return &zero_arity_callsite;
case MVM_CALLSITE_ID_OBJ:
return &obj_callsite;
case MVM_CALLSITE_ID_INT:
return &int_callsite;
case MVM_CALLSITE_ID_OBJ_OBJ:
return &obj_obj_callsite;
case MVM_CALLSITE_ID_OBJ_INT:
Expand Down
1 change: 1 addition & 0 deletions src/core/callsite.h
Expand Up @@ -31,6 +31,7 @@ typedef enum {
typedef enum {
MVM_CALLSITE_ID_ZERO_ARITY,
MVM_CALLSITE_ID_OBJ,
MVM_CALLSITE_ID_INT,
MVM_CALLSITE_ID_OBJ_OBJ,
MVM_CALLSITE_ID_OBJ_INT,
MVM_CALLSITE_ID_OBJ_NUM,
Expand Down
43 changes: 43 additions & 0 deletions src/core/callstack.c
Expand Up @@ -162,6 +162,18 @@ MVMCallStackFlattening * MVM_callstack_allocate_flattening(MVMThreadContext *tc,
return record;
}

/* Allocate a callstack record for indicating that a bind failure in the
* next frame on the stack should be handled via dispatch resumption. */
MVMCallStackBindFailure * MVM_callstack_allocate_bind_failure(MVMThreadContext *tc,
MVMint64 flag) {
tc->stack_top = allocate_record(tc, MVM_CALLSTACK_RECORD_BIND_FAILURE,
sizeof(MVMCallStackBindFailure));
MVMCallStackBindFailure *record = (MVMCallStackBindFailure *)tc->stack_top;
record->state = MVM_BIND_FAILURE_FRESH;
record->flag.i64 = flag;
return record;
}

/* Creates a new region for a continuation. By a continuation boundary starting
* a new region, we are able to take the continuation by slicing off the entire
* region from the regions linked list. The continuation tags always go at the
Expand Down Expand Up @@ -331,6 +343,16 @@ static void exit_frame(MVMThreadContext *tc, MVMFrame *returner) {
tc->cur_frame = NULL;
}
}
static void handle_bind_failure(MVMThreadContext *tc, MVMCallStackBindFailure *failure_record) {
failure_record->state = MVM_BIND_FAILURE_EXHAUSTED;
MVMDispInlineCacheEntry **ice_ptr = failure_record->ice_ptr;
MVMDispInlineCacheEntry *ice = *ice_ptr;
MVMString *id = tc->instance->str_consts.boot_resume;
MVMCallsite *callsite = MVM_callsite_get_common(tc, MVM_CALLSITE_ID_INT);
MVMuint16 *args = MVM_args_identity_map(tc, callsite);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, &(failure_record->flag),
failure_record->sf, 0);
}
MVMFrame * MVM_callstack_unwind_frame(MVMThreadContext *tc, MVMuint8 exceptional, MVMuint32 *thunked) {
do {
/* Ensure region and stack top are in a consistent state. */
Expand Down Expand Up @@ -404,6 +426,19 @@ MVMFrame * MVM_callstack_unwind_frame(MVMThreadContext *tc, MVMuint8 exceptional
tc->stack_top = tc->stack_top->prev;
}
break;
case MVM_CALLSTACK_RECORD_BIND_FAILURE: {
MVMCallStackBindFailure *failure_record =
(MVMCallStackBindFailure *)tc->stack_top;
if (failure_record->state == MVM_BIND_FAILURE_FAILED) {
handle_bind_failure(tc, failure_record);
*thunked = 1;
}
else {
tc->stack_current_region->alloc = (char *)tc->stack_top;
tc->stack_top = tc->stack_top->prev;
}
break;
}
default:
MVM_panic(1, "Unknown call stack record type in unwind");
}
Expand Down Expand Up @@ -509,6 +544,14 @@ static void mark(MVMThreadContext *tc, MVMCallStackRecord *from_record, MVMGCWor
}
break;
}
case MVM_CALLSTACK_RECORD_BIND_FAILURE: {
MVMCallStackBindFailure *failure_record =
(MVMCallStackBindFailure *)record;
if (failure_record->state == MVM_BIND_FAILURE_FAILED)
add_collectable(tc, worklist, snapshot, failure_record->sf,
"Bind failure static frame");
break;
}
default:
MVM_panic(1, "Unknown call stack record type in GC marking");
}
Expand Down
28 changes: 28 additions & 0 deletions src/core/callstack.h
Expand Up @@ -243,6 +243,32 @@ struct MVMCallStackDispatchRun {
MVMCallsite *temp_mark_callsite;
};

/* This record appears on the callstack before a frame, and indicates that,
* should the frame have a bind failure, we wish to enact a dispatch
* resumption. */
#define MVM_CALLSTACK_RECORD_BIND_FAILURE 11
typedef enum {
MVM_BIND_FAILURE_FRESH, /* Record created in this state */
MVM_BIND_FAILURE_FAILED, /* If there's a bind failure it is set to this... */
MVM_BIND_FAILURE_EXHAUSTED /* ...but is only effective for one call, so ends up here */
} MVMBindFailureState;
struct MVMCallStackBindFailure {
/* Commonalities of all records. */
MVMCallStackRecord common;

/* The current state of the bind failure record. */
MVMBindFailureState state;

/* The flag to pass if we do resume upon a bind failure. */
MVMRegister flag;

/* If we do fail, this is the inline cache entry pointer we hang the
* resumption dispatch program off, along with the static frame it
* lives in (needed for memory management). */
MVMDispInlineCacheEntry **ice_ptr;
MVMStaticFrame *sf;
};

/* Functions for working with the call stack. */
void MVM_callstack_init(MVMThreadContext *tc);
MVMCallStackFrame * MVM_callstack_allocate_frame(MVMThreadContext *tc);
Expand All @@ -252,6 +278,8 @@ MVMCallStackDispatchRun * MVM_callstack_allocate_dispatch_run(MVMThreadContext *
MVMuint32 num_temps);
MVMCallStackFlattening * MVM_callstack_allocate_flattening(MVMThreadContext *tc,
MVMuint16 num_args, MVMuint16 num_pos);
MVMCallStackBindFailure * MVM_callstack_allocate_bind_failure(MVMThreadContext *tc,
MVMint64 flag);
void MVM_callstack_new_continuation_region(MVMThreadContext *tc, MVMObject *tag);
MVMCallStackRegion * MVM_callstack_continuation_slice(MVMThreadContext *tc, MVMObject *tag,
MVMActiveHandler **active_handlers);
Expand Down
1 change: 1 addition & 0 deletions src/core/instance.h
Expand Up @@ -93,6 +93,7 @@ struct MVMStringConsts {
MVMString *config;
MVMString *replacement;
MVMString *dot;
MVMString *boot_resume;
};

struct MVMEventSubscriptions {
Expand Down
37 changes: 25 additions & 12 deletions src/core/interp.c
Expand Up @@ -4072,8 +4072,11 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
OP(assertparamcheck): {
MVMint64 ok = GET_REG(cur_op, 0).i64;
cur_op += 2;
if (!ok)
MVM_args_bind_failed(tc);
if (!ok) {
MVMDispInlineCacheEntry **ice_ptr = MVM_disp_inline_cache_get(
cur_op, bytecode_start, tc->cur_frame);
MVM_args_bind_failed(tc, ice_ptr);
}
goto NEXT;
}
OP(hintfor): {
Expand Down Expand Up @@ -5748,7 +5751,8 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
tc->cur_frame->return_type = MVM_RETURN_VOID;
cur_op += 6 + 2 * callsite->flag_count;
tc->cur_frame->return_address = cur_op;
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, bytecode_offset);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, tc->cur_frame->work,
tc->cur_frame->static_info, bytecode_offset);
goto NEXT;
}
OP(dispatch_i): {
Expand All @@ -5763,7 +5767,8 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
tc->cur_frame->return_type = MVM_RETURN_INT;
cur_op += 8 + 2 * callsite->flag_count;
tc->cur_frame->return_address = cur_op;
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, bytecode_offset);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, tc->cur_frame->work,
tc->cur_frame->static_info, bytecode_offset);
goto NEXT;
}
OP(dispatch_n): {
Expand All @@ -5778,7 +5783,8 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
tc->cur_frame->return_type = MVM_RETURN_NUM;
cur_op += 8 + 2 * callsite->flag_count;
tc->cur_frame->return_address = cur_op;
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, bytecode_offset);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, tc->cur_frame->work,
tc->cur_frame->static_info, bytecode_offset);
goto NEXT;
}
OP(dispatch_s): {
Expand All @@ -5793,7 +5799,8 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
tc->cur_frame->return_type = MVM_RETURN_STR;
cur_op += 8 + 2 * callsite->flag_count;
tc->cur_frame->return_address = cur_op;
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, bytecode_offset);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, tc->cur_frame->work,
tc->cur_frame->static_info, bytecode_offset);
goto NEXT;
}
OP(dispatch_o): {
Expand All @@ -5808,7 +5815,8 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
tc->cur_frame->return_type = MVM_RETURN_OBJ;
cur_op += 8 + 2 * callsite->flag_count;
tc->cur_frame->return_address = cur_op;
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, bytecode_offset);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, tc->cur_frame->work,
tc->cur_frame->static_info, bytecode_offset);
goto NEXT;
}
OP(sp_guard): {
Expand Down Expand Up @@ -6032,7 +6040,8 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
tc->cur_frame->return_type = MVM_RETURN_VOID;
cur_op += 12 + 2 * callsite->flag_count;
tc->cur_frame->return_address = cur_op;
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, -1);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, tc->cur_frame->work,
tc->cur_frame->static_info, -1);
goto NEXT;
}
OP(sp_dispatch_i): {
Expand All @@ -6048,7 +6057,8 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
tc->cur_frame->return_type = MVM_RETURN_INT;
cur_op += 14 + 2 * callsite->flag_count;
tc->cur_frame->return_address = cur_op;
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, -1);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, tc->cur_frame->work,
tc->cur_frame->static_info, -1);
goto NEXT;
}
OP(sp_dispatch_n): {
Expand All @@ -6064,7 +6074,8 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
tc->cur_frame->return_type = MVM_RETURN_NUM;
cur_op += 14 + 2 * callsite->flag_count;
tc->cur_frame->return_address = cur_op;
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, -1);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, tc->cur_frame->work,
tc->cur_frame->static_info, -1);
goto NEXT;
}
OP(sp_dispatch_s): {
Expand All @@ -6080,7 +6091,8 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
tc->cur_frame->return_type = MVM_RETURN_STR;
cur_op += 14 + 2 * callsite->flag_count;
tc->cur_frame->return_address = cur_op;
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, -1);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, tc->cur_frame->work,
tc->cur_frame->static_info, -1);
goto NEXT;
}
OP(sp_dispatch_o): {
Expand All @@ -6096,7 +6108,8 @@ void MVM_interp_run(MVMThreadContext *tc, void (*initial_invoke)(MVMThreadContex
tc->cur_frame->return_type = MVM_RETURN_OBJ;
cur_op += 14 + 2 * callsite->flag_count;
tc->cur_frame->return_address = cur_op;
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, -1);
ice->run_dispatch(tc, ice_ptr, ice, id, callsite, args, tc->cur_frame->work,
tc->cur_frame->static_info, -1);
goto NEXT;
}
OP(sp_getarg_o):
Expand Down
2 changes: 1 addition & 1 deletion src/core/oplist
Expand Up @@ -615,7 +615,7 @@ continuationcontrol w(obj) r(int64) r(obj) r(obj) :invokish :maycausedeopt
continuationinvoke w(obj) r(obj) r(obj) :invokish :maycausedeopt
randscale_n w(num64) r(num64)
uniisblock w(int64) r(str) r(int64) r(str) :pure
assertparamcheck r(int64) :noinline :invokish :maycausedeopt
assertparamcheck r(int64) :noinline :invokish :maycausedeopt :cache
hintfor w(int64) r(obj) r(str)
paramnamesused :noinline
getuniname w(str) r(int64) :pure
Expand Down
2 changes: 1 addition & 1 deletion src/core/ops.c
Expand Up @@ -8353,7 +8353,7 @@ static const MVMOpInfo MVM_op_infos[] = {
1,
0,
0,
0,
1,
{ MVM_operand_read_reg | MVM_operand_int64 }
},
{
Expand Down

0 comments on commit 3d452a5

Please sign in to comment.