diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c
index 7c5d22d6a5c35d..f5ee46419ac4d3 100644
--- a/src/mono/mono/mini/interp/interp.c
+++ b/src/mono/mono/mini/interp/interp.c
@@ -3447,8 +3447,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));
diff --git a/src/mono/mono/mini/interp/mintops.def b/src/mono/mono/mini/interp/mintops.def
index e464902f816857..52b2c4bb1522cf 100644
--- a/src/mono/mono/mini/interp/mintops.def
+++ b/src/mono/mono/mini/interp/mintops.def
@@ -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)
diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c
index 511f89d6415f60..476f98eecbe465 100644
--- a/src/mono/mono/mini/interp/transform.c
+++ b/src/mono/mono/mini/interp/transform.c
@@ -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;
@@ -3141,6 +3142,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)
@@ -3149,7 +3168,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;
@@ -3300,26 +3318,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 */
@@ -3375,10 +3411,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)
@@ -3517,10 +3550,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 {
diff --git a/src/tests/issues.targets b/src/tests/issues.targets
index 645fdf8bb1731c..dc1788898d502a 100644
--- a/src/tests/issues.targets
+++ b/src/tests/issues.targets
@@ -1805,9 +1805,6 @@
https://github.com/dotnet/runtime/issues/54375
-
- https://github.com/dotnet/runtime/issues/54374
-
needs triage