[interp] Optimize delegate invokes (#33099)
authormonojenkins <jo.shields+jenkins@xamarin.com>
Wed, 11 Mar 2020 18:10:09 +0000 (14:10 -0400)
committerGitHub <noreply@github.com>
Wed, 11 Mar 2020 18:10:09 +0000 (20:10 +0200)
Co-authored-by: BrzVlad <BrzVlad@users.noreply.github.com>
src/mono/mono/mini/interp/interp.c
src/mono/mono/mini/interp/mintops.def
src/mono/mono/mini/interp/transform.c

index c17919d..4ae185f 100644 (file)
@@ -3616,6 +3616,76 @@ main_loop:
                        ip = frame->imethod->code;
                        MINT_IN_BREAK;
                }
+               MINT_IN_CASE(MINT_CALL_DELEGATE) {
+                       MonoMethodSignature *csignature = (MonoMethodSignature*)frame->imethod->data_items [ip [1]];
+                       is_void = csignature->ret->type == MONO_TYPE_VOID;
+                       int param_count = csignature->param_count;
+                       MonoDelegate *del = (MonoDelegate*) sp [-param_count - 1].data.o;
+                       gboolean is_multicast = del->method == NULL;
+                       InterpMethod *del_imethod = (InterpMethod*)del->interp_invoke_impl;
+
+                       frame->ip = ip;
+                       if (!del_imethod) {
+                               if (is_multicast) {
+                                       error_init_reuse (error);
+                                       MonoMethod *invoke = mono_get_delegate_invoke_internal (del->object.vtable->klass);
+                                       del_imethod = mono_interp_get_imethod (del->object.vtable->domain, mono_marshal_get_delegate_invoke (invoke, del), error);
+                                       del->interp_invoke_impl = del_imethod;
+                                       mono_error_assert_ok (error);
+                               } else if (!del->interp_method) {
+                                       // Not created from interpreted code
+                                       error_init_reuse (error);
+                                       g_assert (del->method);
+                                       del_imethod = mono_interp_get_imethod (del->object.vtable->domain, del->method, error);
+                                       del->interp_method = del_imethod;
+                                       del->interp_invoke_impl = del_imethod;
+                                       mono_error_assert_ok (error);
+                               } else {
+                                       del_imethod = (InterpMethod*)del->interp_method;
+                                       if (del_imethod->method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL) {
+                                               error_init_reuse (error);
+                                               del_imethod = mono_interp_get_imethod (frame->imethod->domain, mono_marshal_get_native_wrapper (del_imethod->method, FALSE, FALSE), error);
+                                               mono_error_assert_ok (error);
+                                               del->interp_invoke_impl = del_imethod;
+                                       } else if (del_imethod->method->flags & METHOD_ATTRIBUTE_VIRTUAL && !del->target) {
+                                               // 'this' is passed dynamically, we need to recompute the target method
+                                               // with each call
+                                               del_imethod = get_virtual_method (del_imethod, sp [-param_count].data.o->vtable);
+                                       } else {
+                                               del->interp_invoke_impl = del_imethod;
+                                       }
+                               }
+                       }
+                       cmethod = del_imethod;
+                       retval = sp;
+                       sp->data.p = vt_sp;
+                       sp -= param_count + 1;
+                       if (!is_multicast) {
+                               if (cmethod->param_count == param_count + 1) {
+                                       // Target method is static but the delegate has a target object. We handle
+                                       // this separately from the case below, because, for these calls, the instance
+                                       // is allowed to be null.
+                                       sp [0].data.o = del->target;
+                               } else if (del->target) {
+                                       MonoObject *this_arg = del->target;
+
+                                       // replace the MonoDelegate* on the stack with 'this' pointer
+                                       if (m_class_is_valuetype (this_arg->vtable->klass)) {
+                                               gpointer unboxed = mono_object_unbox_internal (this_arg);
+                                               sp [0].data.p = unboxed;
+                                       } else {
+                                               sp [0].data.o = this_arg;
+                                       }
+                               } else {
+                                       // skip the delegate pointer for static calls
+                                       // FIXME we could avoid memmove
+                                       memmove (sp, sp + 1, param_count * sizeof (stackval));
+                               }
+                       }
+                       ip += 2;
+
+                       goto call;
+               }
                MINT_IN_CASE(MINT_CALLI) {
                        MonoMethodSignature *csignature;
 
index be5bef2..d79bd17 100644 (file)
@@ -719,6 +719,7 @@ OPDEF(MINT_CALLVIRT, "callvirt", 3, VarPop, Push1, MintOpMethodToken)
 OPDEF(MINT_VCALLVIRT, "vcallvirt", 3, VarPop, Push0, MintOpMethodToken)
 OPDEF(MINT_CALLVIRT_FAST, "callvirt.fast", 3, VarPop, Push1, MintOpMethodToken)
 OPDEF(MINT_VCALLVIRT_FAST, "vcallvirt.fast", 3, VarPop, Push0, MintOpMethodToken)
+OPDEF(MINT_CALL_DELEGATE, "call.delegate", 2, VarPop, VarPush, MintOpMethodToken)
 OPDEF(MINT_CALLI, "calli", 2, VarPop, VarPush, MintOpMethodToken)
 OPDEF(MINT_CALLI_NAT, "calli.nat", 3, VarPop, VarPush, MintOpMethodToken)
 OPDEF(MINT_CALLI_NAT_FAST, "calli.nat.fast", 4, VarPop, VarPush, MintOpMethodToken)
index 81b137a..d3f4bf0 100644 (file)
@@ -2109,6 +2109,7 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
        int native = 0;
        int is_void = 0;
        int need_null_check = is_virtual;
+       gboolean is_delegate_invoke = FALSE;
 
        guint32 token = read32 (td->ip + 1);
 
@@ -2332,12 +2333,8 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
        /* We need to convert delegate invoke to a indirect call on the interp_invoke_impl field */
        if (target_method && m_class_get_parent (target_method->klass) == mono_defaults.multicastdelegate_class) {
                const char *name = target_method->name;
-               if (*name == 'I' && (strcmp (name, "Invoke") == 0)) {
-                       calli = TRUE;
-                       interp_add_ins (td, MINT_LD_DELEGATE_INVOKE_IMPL);
-                       td->last_ins->data [0] = csignature->param_count + 1;
-                       PUSH_SIMPLE_TYPE (td, STACK_TYPE_I);
-               }
+               if (*name == 'I' && (strcmp (name, "Invoke") == 0))
+                       is_delegate_invoke = TRUE;
        }
 
        /* Pop the function pointer */
@@ -2394,7 +2391,7 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
                        td->last_ins->data [1] = get_data_item_index (td, mono_method_signature_internal (target_method));
                }
 #endif
-       } else if (!calli && !is_virtual && mono_interp_jit_call_supported (target_method, csignature)) {
+       } else if (!calli && !is_delegate_invoke && !is_virtual && mono_interp_jit_call_supported (target_method, csignature)) {
                interp_add_ins (td, MINT_JIT_CALL);
                td->last_ins->data [0] = get_data_item_index (td, (void *)mono_interp_get_imethod (domain, target_method, error));
                mono_error_assert_ok (error);
@@ -2406,6 +2403,8 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
 #endif
                if (csignature->call_convention == MONO_CALL_VARARG)
                        interp_add_ins (td, MINT_CALL_VARARG);
+               else if (is_delegate_invoke)
+                       interp_add_ins (td, MINT_CALL_DELEGATE);
                else if (calli)
                        interp_add_ins (td, native ? ((op != -1) ? MINT_CALLI_NAT_FAST : MINT_CALLI_NAT) : MINT_CALLI);
                else if (is_virtual && !mono_class_is_marshalbyref (target_method->klass))
@@ -2415,7 +2414,9 @@ interp_transform_call (TransformData *td, MonoMethod *method, MonoMethod *target
                else
                        interp_add_ins (td, is_void ? MINT_VCALL : MINT_CALL);
 
-               if (calli) {
+               if (is_delegate_invoke) {
+                       td->last_ins->data [0] = get_data_item_index (td, (void *)csignature);
+               } else if (calli) {
                        td->last_ins->data [0] = get_data_item_index (td, (void *)csignature);
                        if (op != -1) {
                                td->last_ins->data[1] = op;
@@ -6316,6 +6317,12 @@ get_inst_stack_usage (TransformData *td, InterpInst *ins, int *pop, int *push)
                        break;
                }
 #endif
+               case MINT_CALL_DELEGATE: {
+                       MonoMethodSignature *csignature = (MonoMethodSignature*) td->data_items [ins->data [0]];
+                       *pop = csignature->param_count + 1;
+                       *push = csignature->ret->type != MONO_TYPE_VOID;
+                       break;
+               }
                case MINT_CALLI:
                case MINT_CALLI_NAT:
                case MINT_CALLI_NAT_FAST: {