Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[interp] Implement tailcalls
Before this commit we supported tailcalls only for calls to the same method. Tailcalls are implemented by copying the call arguments to the start of the stack space, followed by replacing of the current executing method.
  • Loading branch information
BrzVlad committed Oct 13, 2021
commit 3f9b2c9744ec5ca33c2e3b901437fcf7e6075628
27 changes: 26 additions & 1 deletion src/mono/mono/mini/interp/interp.c
Original file line number Diff line number Diff line change
Expand Up @@ -3414,8 +3414,33 @@ interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs
LOCAL_VAR (ip [1], gint64) = READ64 (ip + 2); /* note union usage */
ip += 6;
MINT_IN_BREAK;
MINT_IN_CASE(MINT_TAILCALL)
MINT_IN_CASE(MINT_TAILCALL_VIRT)
MINT_IN_CASE(MINT_JMP) {
InterpMethod *new_method = (InterpMethod*)frame->imethod->data_items [ip [1]];
gboolean is_tailcall = *ip != MINT_JMP;
InterpMethod *new_method;

if (is_tailcall) {
guint16 params_offset = ip [1];
guint16 params_size = ip [3];

// Copy the params to their location at the start of the frame
memmove (frame->stack, (guchar*)frame->stack + params_offset, params_size);
new_method = (InterpMethod*)frame->imethod->data_items [ip [2]];

if (*ip == MINT_TAILCALL_VIRT) {
gint16 slot = (gint16)ip [4];
MonoObject *this_arg = LOCAL_VAR (0, MonoObject*);
new_method = get_virtual_method_fast (new_method, this_arg->vtable, slot);
if (m_class_is_valuetype (this_arg->vtable->klass) && m_class_is_valuetype (new_method->method->klass)) {
/* unbox */
gpointer unboxed = mono_object_unbox_internal (this_arg);
LOCAL_VAR (0, gpointer) = unboxed;
}
}
} else {
new_method = (InterpMethod*)frame->imethod->data_items [ip [1]];
}

if (frame->imethod->prof_flags & MONO_PROFILER_CALL_INSTRUMENTATION_TAIL_CALL)
MONO_PROFILER_RAISE (method_tail_call, (frame->imethod->method, new_method->method));
Expand Down
2 changes: 2 additions & 0 deletions src/mono/mono/mini/interp/mintops.def
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,8 @@ OPDEF(MINT_CALLI_NAT_DYNAMIC, "calli.nat.dynamic", 5, 1, 2, MintOpMethodToken)
OPDEF(MINT_CALLI_NAT_FAST, "calli.nat.fast", 7, 1, 2, MintOpMethodToken)
OPDEF(MINT_CALL_VARARG, "call.vararg", 6, 1, 1, MintOpMethodToken)
OPDEF(MINT_CALLRUN, "callrun", 5, 1, 1, MintOpNoArgs)
OPDEF(MINT_TAILCALL, "tailcall", 4, 0, 1, MintOpMethodToken)
OPDEF(MINT_TAILCALL_VIRT, "tailcall.virt", 5, 0, 1, MintOpMethodToken)

OPDEF(MINT_ICALL_V_V, "mono_icall_v_v", 3, 0, 1, MintOpShortInt)
OPDEF(MINT_ICALL_V_P, "mono_icall_v_p", 4, 1, 1, MintOpShortInt)
Expand Down
61 changes: 45 additions & 16 deletions src/mono/mono/mini/interp/transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -3136,6 +3136,24 @@ interp_emit_arg_conv (TransformData *td, MonoMethodSignature *csignature)
emit_convert (td, &arg_start [i], csignature->params [i]);
}

static gint16
get_virt_method_slot (MonoMethod *method)
{
if (mono_class_is_interface (method->klass))
return (gint16)(-2 * MONO_IMT_SIZE + mono_method_get_imt_slot (method));
else
return (gint16)mono_method_get_vtable_slot (method);
}

static int*
create_call_args (TransformData *td, int num_args)
{
int *call_args = (int*) mono_mempool_alloc (td->mempool, (num_args + 1) * sizeof (int));
for (int i = 0; i < num_args; i++)
call_args [i] = td->sp [i].local;
call_args [num_args] = -1;
return call_args;
}
/* Return FALSE if error, including inline failure */
static gboolean
interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target_method, MonoGenericContext *generic_context, MonoClass *constrained_class, gboolean readonly, MonoError *error, gboolean check_visibility, gboolean save_last_error, gboolean tailcall)
Expand All @@ -3144,7 +3162,6 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
MonoMethodSignature *csignature;
int is_virtual = *td->ip == CEE_CALLVIRT;
int calli = *td->ip == CEE_CALLI || *td->ip == CEE_MONO_CALLI_EXTRA_ARG;
int i;
guint32 res_size = 0;
int op = -1;
int native = 0;
Expand Down Expand Up @@ -3295,26 +3312,44 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
}

CHECK_STACK (td, csignature->param_count + csignature->hasthis);
if (tailcall && !td->gen_sdb_seq_points && !calli && op == -1 && (!is_virtual || (target_method->flags & METHOD_ATTRIBUTE_VIRTUAL) == 0) &&
if (tailcall && !td->gen_sdb_seq_points && !calli && op == -1 &&
(target_method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL) == 0 &&
(target_method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) == 0 &&
!(target_method->iflags & METHOD_IMPL_ATTRIBUTE_NOINLINING)) {
(void)mono_class_vtable_checked (target_method->klass, error);
return_val_if_nok (error, FALSE);

if (method == target_method && *(td->ip + 5) == CEE_RET && !(csignature->hasthis && m_class_is_valuetype (target_method->klass))) {
if (*(td->ip + 5) == CEE_RET) {
if (td->inlined_method)
return FALSE;

if (td->verbose_level)
g_print ("Optimize tail call of %s.%s\n", m_class_get_name (target_method->klass), target_method->name);

for (i = csignature->param_count - 1 + !!csignature->hasthis; i >= 0; --i)
store_arg (td, i);
int num_args = csignature->param_count + !!csignature->hasthis;
td->sp -= num_args;
guint32 params_stack_size = get_stack_size (td->sp, num_args);

int *call_args = create_call_args (td, num_args);

if (is_virtual) {
interp_add_ins (td, MINT_CKNULL);
interp_ins_set_sreg (td->last_ins, td->sp->local);
set_simple_type_and_local (td, td->sp, td->sp->type);
interp_ins_set_dreg (td->last_ins, td->sp->local);

interp_add_ins (td, MINT_TAILCALL_VIRT);
td->last_ins->data [2] = get_virt_method_slot (target_method);
} else {
interp_add_ins (td, MINT_TAILCALL);
}
interp_ins_set_sreg (td->last_ins, MINT_CALL_ARGS_SREG);
td->last_ins->data [0] = get_data_item_index (td, mono_interp_get_imethod (target_method, error));
return_val_if_nok (error, FALSE);
td->last_ins->data [1] = params_stack_size;
td->last_ins->flags |= INTERP_INST_FLAG_CALL;
td->last_ins->info.call_args = call_args;

interp_add_ins (td, MINT_BR);
// We are branching to the beginning of the method
td->last_ins->info.target_bb = td->entry_bb;
int in_offset = td->ip - td->il_code;
if (interp_ip_in_cbb (td, in_offset + 5))
++td->ip; /* gobble the CEE_RET if it isn't branched to */
Expand Down Expand Up @@ -3370,10 +3405,7 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
td->sp -= num_args;
guint32 params_stack_size = get_stack_size (td->sp, num_args);

int *call_args = (int*) mono_mempool_alloc (td->mempool, (num_args + 1) * sizeof (int));
for (int i = 0; i < num_args; i++)
call_args [i] = td->sp [i].local;
call_args [num_args] = -1;
int *call_args = create_call_args (td, num_args);

// We overwrite it with the return local, save it for future use
if (csignature->param_count || csignature->hasthis)
Expand Down Expand Up @@ -3512,10 +3544,7 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
td->last_ins->data [2] = params_stack_size;
} else if (is_virtual) {
interp_add_ins (td, MINT_CALLVIRT_FAST);
if (mono_class_is_interface (target_method->klass))
td->last_ins->data [1] = -2 * MONO_IMT_SIZE + mono_method_get_imt_slot (target_method);
else
td->last_ins->data [1] = mono_method_get_vtable_slot (target_method);
td->last_ins->data [1] = get_virt_method_slot (target_method);
} else if (is_virtual) {
interp_add_ins (td, MINT_CALLVIRT);
} else {
Expand Down