Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mono][interp] Implement tailcalls #59799

Merged
merged 3 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
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
62 changes: 46 additions & 16 deletions src/mono/mono/mini/interp/transform.c
Original file line number Diff line number Diff line change
Expand Up @@ -1424,6 +1424,7 @@ dump_interp_ins_data (InterpInst *ins, gint32 ins_offset, const guint16 *data, g
target = ins_offset + *(gint16*)(data + 1);
g_string_append_printf (str, " %u, IR_%04x", *(guint16*)data, target);
}
break;
case MintOpPair2:
g_string_append_printf (str, " %u <- %u, %u <- %u", data [0], data [1], data [2], data [3]);
break;
Expand Down Expand Up @@ -3135,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 @@ -3143,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 @@ -3294,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 @@ -3369,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 @@ -3511,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
3 changes: 0 additions & 3 deletions src/tests/issues.targets
Original file line number Diff line number Diff line change
Expand Up @@ -1820,9 +1820,6 @@
<ExcludeList Include = "$(XunitTestBinBase)/JIT/IL_Conformance/Old/Conformance_Base/div_r8/**">
<Issue>https://github.com/dotnet/runtime/issues/54375</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/JIT/Directed/tailcall/tailcall/**">
<Issue>https://github.com/dotnet/runtime/issues/54374</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/JIT/Methodical/Invoke/deep/_il_reldeep1/**">
<Issue>needs triage</Issue>
</ExcludeList>
Expand Down