[mono][aot] Enabling AOT wrappers for virtual delegates (#85643)
authorVlad-Alexandru Ionescu <114913836+LeVladIonescu@users.noreply.github.com>
Mon, 22 May 2023 10:35:01 +0000 (12:35 +0200)
committerGitHub <noreply@github.com>
Mon, 22 May 2023 10:35:01 +0000 (12:35 +0200)
* Adding AOT virtual delegates wrappers

Signed-off-by: Vlad - Alexandru Ionescu <vlad-alexandruionescu@Vlads-MacBook-Pro-5.local>
---------

Signed-off-by: Vlad - Alexandru Ionescu <vlad-alexandruionescu@Vlads-MacBook-Pro-5.local>
Co-authored-by: Vlad - Alexandru Ionescu <vlad-alexandruionescu@Vlads-MacBook-Pro-5.local>
src/mono/mono/metadata/loader-internals.h
src/mono/mono/metadata/marshal-lightweight.c
src/mono/mono/metadata/marshal.c
src/mono/mono/metadata/marshal.h
src/mono/mono/metadata/metadata-internals.h
src/mono/mono/metadata/metadata.c
src/mono/mono/mini/aot-compiler.c
src/mono/mono/mini/aot-runtime.c

index 3f64f48..af05fa3 100644 (file)
@@ -52,11 +52,12 @@ struct _MonoDllMap {
 #endif
 
 typedef struct {
+       GHashTable *delegate_invoke_cache;
+       GHashTable *delegate_invoke_virtual_cache;
        /*
         * indexed by MonoMethodSignature
         * Protected by the marshal lock
         */
-       GHashTable *delegate_invoke_cache;
        GHashTable *delegate_begin_invoke_cache;
        GHashTable *delegate_end_invoke_cache;
        GHashTable *runtime_invoke_signature_cache;
index 06a2012..a4fb731 100644 (file)
@@ -1989,8 +1989,10 @@ emit_delegate_end_invoke_ilgen (MonoMethodBuilder *mb, MonoMethodSignature *sig)
                mono_mb_emit_restore_result (mb, sig->ret);
 }
 
+#define MONO_TYPE_IS_PRIMITIVE(t) ((!m_type_is_byref ((t)) && ((((t)->type >= MONO_TYPE_BOOLEAN && (t)->type <= MONO_TYPE_R8) || ((t)->type >= MONO_TYPE_I && (t)->type <= MONO_TYPE_U)))))
+
 static void
-emit_delegate_invoke_internal_ilgen (MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodSignature *invoke_sig, MonoMethodSignature *target_method_sig, gboolean static_method_with_first_arg_bound, gboolean callvirt, gboolean closed_over_null, MonoMethod *method, MonoMethod *target_method, MonoClass *target_class, MonoGenericContext *ctx, MonoGenericContainer *container)
+emit_delegate_invoke_internal_ilgen (MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodSignature *invoke_sig, MonoMethodSignature *target_method_sig, gboolean static_method_with_first_arg_bound, gboolean callvirt, gboolean closed_over_null, MonoMethod *method, MonoMethod *target_method, MonoGenericContext *ctx, MonoGenericContainer *container)
 {
        int local_i, local_len, local_delegates, local_d, local_target, local_res = 0;
        int pos0, pos1, pos2;
@@ -2088,9 +2090,15 @@ emit_delegate_invoke_internal_ilgen (MonoMethodBuilder *mb, MonoMethodSignature
 
        if (callvirt) {
                if (!closed_over_null) {
-                       for (i = 1; i <= sig->param_count; ++i)
+                       for (i = 1; i <= sig->param_count; ++i) {
                                mono_mb_emit_ldarg (mb, i);
-                       mono_mb_emit_ldarg (mb, 1);
+                               if (i == 1 && (MONO_TYPE_ISSTRUCT (sig->params [0]) || MONO_TYPE_IS_PRIMITIVE (sig->params [0])))
+                                       mono_mb_emit_op (mb, CEE_BOX, mono_class_from_mono_type_internal (sig->params [0]));
+                       }
+                       if (MONO_TYPE_ISSTRUCT (sig->params [0]) || MONO_TYPE_IS_PRIMITIVE (sig->params [0]))
+                               mono_mb_emit_ldarg_addr (mb, 1);
+                       else
+                               mono_mb_emit_ldarg (mb, 1);
                        mono_mb_emit_ldarg (mb, 0);
                        mono_mb_emit_icall (mb, mono_get_addr_compiled_method);
                        mono_mb_emit_op (mb, CEE_CALLI, target_method_sig);
index 61cc96b..0307dc1 100644 (file)
@@ -2092,7 +2092,6 @@ mono_marshal_get_delegate_invoke_internal (MonoMethod *method, gboolean callvirt
        GHashTable *cache;
        gpointer cache_key = NULL;
        char *name;
-       MonoClass *target_class = NULL;
        gboolean closed_over_null = FALSE;
        MonoGenericContext *ctx = NULL;
        MonoGenericContainer *container = NULL;
@@ -2111,28 +2110,8 @@ mono_marshal_get_delegate_invoke_internal (MonoMethod *method, gboolean callvirt
         * call is made to that method with the first delegate argument as this. This is
         * a non-documented .NET feature.
         */
-       if (callvirt) {
+       if (callvirt)
                subtype = WRAPPER_SUBTYPE_DELEGATE_INVOKE_VIRTUAL;
-               if (target_method->is_inflated) {
-                       ERROR_DECL (error);
-                       MonoType *target_type;
-
-                       g_assert (method->signature->hasthis);
-                       target_type = mono_class_inflate_generic_type_checked (method->signature->params [0],
-                               mono_method_get_context (method), error);
-                       mono_error_assert_ok (error); /* FIXME don't swallow the error */
-                       target_class = mono_class_from_mono_type_internal (target_type);
-               } else {
-                       target_class = target_method->klass;
-               }
-
-               closed_over_null = sig->param_count == mono_method_signature_internal (target_method)->param_count;
-
-               /*
-                * We don't want to use target_method's signature because it can be freed early
-                */
-               target_method_sig = mono_method_signature_internal (target_method);
-       }
 
        if (static_method_with_first_arg_bound) {
                subtype = WRAPPER_SUBTYPE_DELEGATE_INVOKE_BOUND;
@@ -2150,7 +2129,7 @@ mono_marshal_get_delegate_invoke_internal (MonoMethod *method, gboolean callvirt
        /*
         * For generic delegates, create a generic wrapper, and return an instance to help AOT.
         */
-       if (method->is_inflated && subtype == WRAPPER_SUBTYPE_NONE) {
+       if (method->is_inflated && (subtype == WRAPPER_SUBTYPE_NONE || subtype == WRAPPER_SUBTYPE_DELEGATE_INVOKE_VIRTUAL)) {
                ctx = &((MonoMethodInflated*)method)->context;
                method = ((MonoMethodInflated*)method)->declaring;
 
@@ -2162,11 +2141,23 @@ mono_marshal_get_delegate_invoke_internal (MonoMethod *method, gboolean callvirt
                invoke_sig = sig = mono_signature_no_pinvoke (method);
        }
 
+       if (subtype == WRAPPER_SUBTYPE_DELEGATE_INVOKE_VIRTUAL) {
+               /*
+                * We don't want to use target_method's signature because it can be freed early
+                */
+               target_method_sig = mono_metadata_signature_dup_delegate_invoke_to_target (invoke_sig);
+
+               closed_over_null = sig->param_count == target_method_sig->param_count;
+       }
+
        /*
         * Check cache
         */
        if (ctx) {
-               cache = get_cache (&((MonoMethodInflated*)orig_method)->owner->wrapper_caches.delegate_invoke_cache, mono_aligned_addr_hash, NULL);
+               if (callvirt)
+                       cache = get_cache (&((MonoMethodInflated*)orig_method)->owner->wrapper_caches.delegate_invoke_virtual_cache, mono_aligned_addr_hash, NULL);
+               else
+                       cache = get_cache (&((MonoMethodInflated*)orig_method)->owner->wrapper_caches.delegate_invoke_cache, mono_aligned_addr_hash, NULL);
                res = check_generic_delegate_wrapper_cache (cache, orig_method, method, ctx);
                if (res)
                        return res;
@@ -2241,7 +2232,7 @@ mono_marshal_get_delegate_invoke_internal (MonoMethod *method, gboolean callvirt
                /* FIXME: Other subtypes */
                mb->mem_manager = m_method_get_mem_manager (method);
 
-       get_marshal_cb ()->emit_delegate_invoke_internal (mb, sig, invoke_sig, target_method_sig, static_method_with_first_arg_bound, callvirt, closed_over_null, method, target_method, target_class, ctx, container);
+       get_marshal_cb ()->emit_delegate_invoke_internal (mb, sig, invoke_sig, target_method_sig, static_method_with_first_arg_bound, callvirt, closed_over_null, method, target_method, ctx, container);
 
        get_marshal_cb ()->mb_skip_visibility (mb);
 
@@ -6384,6 +6375,7 @@ void
 mono_wrapper_caches_free (MonoWrapperCaches *cache)
 {
        free_hash (cache->delegate_invoke_cache);
+       free_hash (cache->delegate_invoke_virtual_cache);
        free_hash (cache->delegate_begin_invoke_cache);
        free_hash (cache->delegate_end_invoke_cache);
        free_hash (cache->delegate_bound_static_invoke_cache);
index e6ad315..9f261cf 100644 (file)
@@ -328,7 +328,7 @@ typedef struct {
        void (*emit_runtime_invoke_dynamic) (MonoMethodBuilder *mb);
        void (*emit_delegate_begin_invoke) (MonoMethodBuilder *mb, MonoMethodSignature *sig);
        void (*emit_delegate_end_invoke) (MonoMethodBuilder *mb, MonoMethodSignature *sig);
-       void (*emit_delegate_invoke_internal) (MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodSignature *invoke_sig, MonoMethodSignature *target_method_sig, gboolean static_method_with_first_arg_bound, gboolean callvirt, gboolean closed_over_null, MonoMethod *method, MonoMethod *target_method, MonoClass *target_class, MonoGenericContext *ctx, MonoGenericContainer *container);
+       void (*emit_delegate_invoke_internal) (MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodSignature *invoke_sig, MonoMethodSignature *target_method_sig, gboolean static_method_with_first_arg_bound, gboolean callvirt, gboolean closed_over_null, MonoMethod *method, MonoMethod *target_method, MonoGenericContext *ctx, MonoGenericContainer *container);
        void (*emit_synchronized_wrapper) (MonoMethodBuilder *mb, MonoMethod *method, MonoGenericContext *ctx, MonoGenericContainer *container, MonoMethod *enter_method, MonoMethod *exit_method, MonoMethod *gettypefromhandle_method);
        void (*emit_unbox_wrapper) (MonoMethodBuilder *mb, MonoMethod *method);
        void (*emit_array_accessor_wrapper) (MonoMethodBuilder *mb, MonoMethod *method, MonoMethodSignature *sig, MonoGenericContext *ctx);
index e073ab2..99aa04e 100644 (file)
@@ -991,6 +991,7 @@ MonoMethodSignature  *mono_metadata_signature_dup_full (MonoImage *image,MonoMet
 MonoMethodSignature  *mono_metadata_signature_dup_mempool (MonoMemPool *mp, MonoMethodSignature *sig);
 MonoMethodSignature  *mono_metadata_signature_dup_mem_manager (MonoMemoryManager *mem_manager, MonoMethodSignature *sig);
 MonoMethodSignature  *mono_metadata_signature_dup_add_this (MonoImage *image, MonoMethodSignature *sig, MonoClass *klass);
+MonoMethodSignature  *mono_metadata_signature_dup_delegate_invoke_to_target (MonoMethodSignature *sig);
 
 MonoGenericInst *
 mono_get_shared_generic_inst (MonoGenericContainer *container);
index c564777..b9b30d6 100644 (file)
@@ -2512,6 +2512,29 @@ mono_metadata_signature_dup (MonoMethodSignature *sig)
        return mono_metadata_signature_dup_full (NULL, sig);
 }
 
+/**
+ * mono_metadata_signature_dup_delegate_invoke_to_target:
+ * \param sig method signature
+ *
+ * Duplicate an existing \c MonoMethodSignature but removes first param from it so it can
+ * be used as signature for a delegate target method.
+ * This is a Mono runtime internal function.
+ *
+ * \returns the new \c MonoMethodSignature structure.
+ */
+MonoMethodSignature*
+mono_metadata_signature_dup_delegate_invoke_to_target (MonoMethodSignature *sig)
+{
+       MonoMethodSignature *res = mono_metadata_signature_dup_full (NULL, sig);
+
+       for (int i = 0 ; i < sig->param_count - 1; i ++) {
+               res->params [i] = sig->params [i + 1];
+       }
+       res->param_count --;
+
+       return res;
+}
+
 /*
  * mono_metadata_signature_size:
  *
index a5d36d9..52147fc 100644 (file)
@@ -3922,12 +3922,15 @@ encode_method_ref (MonoAotCompile *acfg, MonoMethod *method, guint8 *buf, guint8
                                encode_klass_ref (acfg, method->klass, p, &p);
                        } else {
                                MonoMethodSignature *sig = mono_method_signature_internal (method);
-                               WrapperInfo *wrapper_info = mono_marshal_get_wrapper_info (method);
 
                                encode_value (0, p, &p);
                                if (method->wrapper_type == MONO_WRAPPER_DELEGATE_INVOKE)
-                                       encode_value (wrapper_info ? wrapper_info->subtype : 0, p, &p);
-                               encode_signature (acfg, sig, p, &p);
+                                       encode_value (info ? info->subtype : 0, p, &p);
+
+                               if (info && info->subtype == WRAPPER_SUBTYPE_DELEGATE_INVOKE_VIRTUAL)
+                                       encode_klass_ref (acfg, info->d.delegate_invoke.method->klass, p, &p);
+                               else
+                                       encode_signature (acfg, sig, p, &p);
                        }
                        break;
                }
@@ -4546,6 +4549,41 @@ can_marshal_struct (MonoClass *klass)
        return can_marshal;
 }
 
+/* Create a ref shared instantiation */
+static void
+create_ref_shared_inst (MonoAotCompile *acfg, MonoMethod *method, MonoGenericContext *ctx)
+{
+       MonoGenericContext shared_context;
+       MonoType **args;
+       MonoGenericInst *inst;
+       MonoGenericContainer *container;
+
+       memset (ctx, 0, sizeof (MonoGenericContext));
+
+       if (mono_class_is_gtd (method->klass)) {
+               shared_context = mono_class_get_generic_container (method->klass)->context;
+               inst = shared_context.class_inst;
+
+               args = g_new0 (MonoType*, inst->type_argc);
+               for (guint i = 0; i < inst->type_argc; ++i)
+                       args [i] = mono_get_object_type ();
+               ctx->class_inst = mono_metadata_get_generic_inst (inst->type_argc, args);
+               g_free (args);
+       }
+       if (method->is_generic) {
+               container = mono_method_get_generic_container (method);
+               g_assert (!container->is_anonymous && container->is_method);
+               shared_context = container->context;
+               inst = shared_context.method_inst;
+
+               args = g_new0 (MonoType*, inst->type_argc);
+               for (int i = 0; i < container->type_argc; ++i)
+                       args [i] = mono_get_object_type ();
+               ctx->method_inst = mono_metadata_get_generic_inst (inst->type_argc, args);
+               g_free (args);
+       }
+}
+
 static void
 create_gsharedvt_inst (MonoAotCompile *acfg, MonoMethod *method, MonoGenericContext *ctx)
 {
@@ -4962,13 +5000,39 @@ add_full_aot_wrappers (MonoAotCompile *acfg)
                if (!m_class_is_delegate (klass) || klass == mono_defaults.delegate_class || klass == mono_defaults.multicastdelegate_class)
                        continue;
 
+               method = mono_get_delegate_invoke_internal (klass);
+               if (mono_class_is_gtd (klass)) {
+                       MonoGenericContext ctx;
+                       MonoMethod *inst, *gshared;
+
+                       create_ref_shared_inst (acfg, method, &ctx);
+
+                       inst = mono_class_inflate_generic_method_checked (method, &ctx, error);
+                       g_assert (is_ok (error)); /* FIXME don't swallow the error */
+
+                       sig = mono_method_signature_internal (method);
+                       if (sig->param_count && !m_class_is_byreflike (mono_class_from_mono_type_internal (sig->params [0])) && !m_type_is_byref (sig->params [0])) {
+                               m = mono_marshal_get_delegate_invoke_internal (inst, TRUE, FALSE, NULL);
+
+                               gshared = mini_get_shared_method_full (m, SHARE_MODE_NONE, error);
+                               mono_error_assert_ok (error);
+
+                               add_extra_method (acfg, gshared);
+                       }
+               }
+
                if (!mono_class_is_gtd (klass)) {
-                       method = mono_get_delegate_invoke_internal (klass);
 
                        m = mono_marshal_get_delegate_invoke (method, NULL);
 
                        add_method (acfg, m);
 
+                       sig = mono_method_signature_internal (method);
+                       if (sig->param_count && !m_class_is_byreflike (mono_class_from_mono_type_internal (sig->params [0])) && !m_type_is_byref (sig->params [0])) {
+                               m = mono_marshal_get_delegate_invoke_internal (method, TRUE, FALSE, NULL);
+                               add_method (acfg, m);
+                       }
+
                        method = try_get_method_nofail (klass, "BeginInvoke", -1, 0);
                        if (method)
                                add_method (acfg, mono_marshal_get_delegate_begin_invoke (method));
index 1b273de..261f95b 100644 (file)
@@ -1269,15 +1269,25 @@ decode_method_ref_with_target (MonoAotModule *module, MethodRef *ref, MonoMethod
                                        ref->method = wrapper;
                                }
                        } else {
-                               /*
-                                * These wrappers are associated with a signature, not with a method.
-                                * Since we can't decode them into methods, they need a target method.
-                                */
-                               if (!target)
-                                       return FALSE;
+                               MonoClass *klass;
+                               MonoMethod *invoke;
+
 
                                if (wrapper_type == MONO_WRAPPER_DELEGATE_INVOKE) {
                                        subtype = (WrapperSubtype)decode_value (p, &p);
+                                       if (subtype == WRAPPER_SUBTYPE_DELEGATE_INVOKE_VIRTUAL) {
+                                               klass = decode_klass_ref (module, p, &p, error);
+                                               invoke = mono_get_delegate_invoke_internal (klass);
+                                               ref->method = mono_marshal_get_delegate_invoke_internal(invoke, TRUE, FALSE, NULL);
+                                               break;
+                                       }
+
+                                       /*
+                                        * These wrappers are associated with a signature, not with a method.
+                                        * Since we can't decode them into methods, they need a target method.
+                                        */
+                                       if (!target)
+                                               return FALSE;
                                        info = mono_marshal_get_wrapper_info (target);
                                        if (info) {
                                                if (info->subtype != subtype)
@@ -1287,7 +1297,7 @@ decode_method_ref_with_target (MonoAotModule *module, MethodRef *ref, MonoMethod
                                                        return FALSE;
                                        }
                                }
-                               if (sig_matches_target (module, target, p, &p))
+                               if (target && sig_matches_target (module, target, p, &p))
                                        ref->method = target;
                                else
                                        return FALSE;