[Mono][jit] Emit GC transitions for "calli unmanaged" instructions (#46491)
authorAleksey Kliger (λgeek) <alklig@microsoft.com>
Wed, 6 Jan 2021 02:29:58 +0000 (21:29 -0500)
committerGitHub <noreply@github.com>
Wed, 6 Jan 2021 02:29:58 +0000 (21:29 -0500)
* [metadata] Treat CallConv bit 0x09 as MONO_CALL_UNMANAGED_MD

This is the C#9 function pointers "unmanaged"+ext calling convention.
Additional calling convention details are encoded in the modopts of the return
type.

This PR doesn't handle the modopts yet

* [marshal] Add mono_marshal_get_native_func_wrapper_indirect

This will be used to add a wrapper around "calli sig" indirect calls where "sig" has
an unmanaged calling convention.

We need to do a GC transition before calling an unmanaged function from
managed. So this wrapper does it.

This is reusing much of the code implemented for
mono_marshal_get_native_func_wrapper_aot which is used for delegates.
Unfortunately that means that the function pointer is (pointlessly) boxed when
it is passed as the first argument.

* [jit] Use a wrapper for "calli unmanaged" instructions

If there's a calli with an unmanaged signature, invoke a wrapper that
does a transition to GC Safe mode and then do the call.

The wrapper is always inlined into the callee.

Because we're reusing much of the code of
mono_marshal_get_native_func_wrapper_aot, the function pointer first has to be
boxed before it's passed to the wrapper.  In theory we should be able to just
pass it directly and get much simpler code.  But that will require changing the
code in emit_native_wrapper_ilgen to get an unboxed function pointer arg

* fixup don't emit GC transitions in runtime invoke wrapper

* fixup don't emit two wrappers on dynamic methods

* check that calli wrapper only gets blittable args

* negate logic for when to add an indirection wrapper

assume that if the callee is a wrapper, it doesn't need the transition, and
only add it to normal managed methods.

* add disabled debug printf

* Add MonoNativeWrapperFlags arg to emit_native_wrapper

Replace the 4 boolean args by a flags arg.

Also add a new EMIT_NATIVE_WRAPPER_FUNC_PARAM_UNBOXED and use it in
mono_marshal_get_native_func_wrapper_indirect.  The new flag has no effect yet.

* Address review feedback

* Use an unboxed func ptr mono_marshal_get_native_func_wrapper_indirect

Add support for the EMIT_NATIVE_WRAPPER_FUNC_PARAM_UNBOXED flag to mono_marshal_emit_native_wrapper

src/mono/mono/metadata/cominterop.c
src/mono/mono/metadata/image.c
src/mono/mono/metadata/marshal-ilgen.c
src/mono/mono/metadata/marshal-noilgen.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/metadata/metadata.h
src/mono/mono/mini/method-to-ir.c

index 4c46107..e80cca9 100644 (file)
@@ -1023,7 +1023,7 @@ cominterop_get_native_wrapper_adjusted (MonoMethod *method)
                }
        }
 
-       mono_marshal_emit_native_wrapper (m_class_get_image (method->klass), mb_native, sig_native, piinfo, mspecs, piinfo->addr, FALSE, TRUE, FALSE, FALSE);
+       mono_marshal_emit_native_wrapper (m_class_get_image (method->klass), mb_native, sig_native, piinfo, mspecs, piinfo->addr, EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS);
 
        res = mono_mb_create_method (mb_native, sig_native, sig_native->param_count + 16);      
 
index 79d3921..fe5ec7d 100644 (file)
@@ -2493,6 +2493,7 @@ mono_wrapper_caches_free (MonoWrapperCaches *cache)
        free_hash (cache->native_wrapper_aot_check_cache);
 
        free_hash (cache->native_func_wrapper_aot_cache);
+       free_hash (cache->native_func_wrapper_indirect_cache);
        free_hash (cache->remoting_invoke_cache);
        free_hash (cache->synchronized_cache);
        free_hash (cache->unbox_wrapper_cache);
index 785556b..8146753 100644 (file)
@@ -1995,13 +1995,19 @@ gc_safe_transition_builder_cleanup (GCSafeTransitionBuilder *builder)
  * \param method if non-NULL, the pinvoke method to call
  * \param check_exceptions Whenever to check for pending exceptions after the native call
  * \param func_param the function to call is passed as a boxed IntPtr as the first parameter
+ * \param func_param_unboxed combined with \p func_param, expect the function to call as an unboxed IntPtr as the first parameter
  * \param skip_gc_trans Whenever to skip GC transitions
  *
  * generates IL code for the pinvoke wrapper, the generated code calls \p func .
  */
 static void
-emit_native_wrapper_ilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans)
+emit_native_wrapper_ilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, MonoNativeWrapperFlags flags)
 {
+       gboolean aot = (flags & EMIT_NATIVE_WRAPPER_AOT) != 0;
+       gboolean check_exceptions = (flags & EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS) != 0;
+       gboolean func_param = (flags & EMIT_NATIVE_WRAPPER_FUNC_PARAM) != 0;
+       gboolean func_param_unboxed = (flags & EMIT_NATIVE_WRAPPER_FUNC_PARAM_UNBOXED) != 0;
+       gboolean skip_gc_trans = (flags & EMIT_NATIVE_WRAPPER_SKIP_GC_TRANS) != 0;
        EmitMarshalContext m;
        MonoMethodSignature *csig;
        MonoClass *klass;
@@ -2139,9 +2145,11 @@ emit_native_wrapper_ilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSi
        /* call the native method */
        if (func_param) {
                mono_mb_emit_byte (mb, CEE_LDARG_0);
-               mono_mb_emit_op (mb, CEE_UNBOX, mono_defaults.int_class);
-               mono_mb_emit_byte (mb, CEE_LDIND_I);
-               if (piinfo->piflags & PINVOKE_ATTRIBUTE_SUPPORTS_LAST_ERROR) {
+               if (!func_param_unboxed) {
+                       mono_mb_emit_op (mb, CEE_UNBOX, mono_defaults.int_class);
+                       mono_mb_emit_byte (mb, CEE_LDIND_I);
+               }
+               if (piinfo && (piinfo->piflags & PINVOKE_ATTRIBUTE_SUPPORTS_LAST_ERROR) != 0) {
                        mono_mb_emit_byte (mb, MONO_CUSTOM_PREFIX);
                        mono_mb_emit_byte (mb, CEE_MONO_SAVE_LAST_ERROR);
                }
index 6555604..cb79e2f 100644 (file)
@@ -350,7 +350,7 @@ emit_thunk_invoke_wrapper_noilgen (MonoMethodBuilder *mb, MonoMethod *method, Mo
 }
 
 static void
-emit_native_wrapper_noilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans)
+emit_native_wrapper_noilgen (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, MonoNativeWrapperFlags flags)
 {
 }
 
index cc7cd72..f0a04f4 100644 (file)
@@ -116,6 +116,8 @@ static GENERATE_TRY_GET_CLASS_WITH_CACHE (suppress_gc_transition_attribute, "Sys
 static GENERATE_TRY_GET_CLASS_WITH_CACHE (unmanaged_callers_only_attribute, "System.Runtime.InteropServices", "UnmanagedCallersOnlyAttribute")
 #endif
 
+static gboolean type_is_blittable (MonoType *type);
+
 static MonoImage*
 get_method_image (MonoMethod *method)
 {
@@ -3518,7 +3520,11 @@ mono_marshal_get_native_wrapper (MonoMethod *method, gboolean check_exceptions,
        }
 #endif
 
-       mono_marshal_emit_native_wrapper (get_method_image (mb->method), mb, csig, piinfo, mspecs, piinfo->addr, aot, check_exceptions, FALSE, skip_gc_trans);
+       MonoNativeWrapperFlags flags = aot ? EMIT_NATIVE_WRAPPER_AOT : (MonoNativeWrapperFlags)0;
+       flags |= check_exceptions ? EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS : (MonoNativeWrapperFlags)0;
+       flags |= skip_gc_trans ? EMIT_NATIVE_WRAPPER_SKIP_GC_TRANS : (MonoNativeWrapperFlags)0;
+
+       mono_marshal_emit_native_wrapper (get_method_image (mb->method), mb, csig, piinfo, mspecs, piinfo->addr, flags);
        info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_PINVOKE);
        info->d.managed_to_native.method = method;
 
@@ -3573,7 +3579,7 @@ mono_marshal_get_native_func_wrapper (MonoImage *image, MonoMethodSignature *sig
        mb = mono_mb_new (mono_defaults.object_class, name, MONO_WRAPPER_MANAGED_TO_NATIVE);
        mb->method->save_lmf = 1;
 
-       mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, func, FALSE, TRUE, FALSE, FALSE);
+       mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, func, EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS);
 
        csig = mono_metadata_signature_dup_full (image, sig);
        csig->pinvoke = 0;
@@ -3636,7 +3642,7 @@ mono_marshal_get_native_func_wrapper_aot (MonoClass *klass)
        mb = mono_mb_new (invoke->klass, name, MONO_WRAPPER_MANAGED_TO_NATIVE);
        mb->method->save_lmf = 1;
 
-       mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, NULL, FALSE, TRUE, TRUE, FALSE);
+       mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, NULL, EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS | EMIT_NATIVE_WRAPPER_FUNC_PARAM);
 
        info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NATIVE_FUNC_AOT);
        info->d.managed_to_native.method = invoke;
@@ -3659,6 +3665,78 @@ mono_marshal_get_native_func_wrapper_aot (MonoClass *klass)
 }
 
 /*
+ * Gets a wrapper for an indirect call to a function with the given signature.
+ * The actual function is passed as the first argument to the wrapper.
+ *
+ * The wrapper is
+ *
+ * retType wrapper (fnPtr, arg1... argN) {
+ *   enter_gc_safe;
+ *   ret = fnPtr (arg1, ... argN);
+ *   exit_gc_safe;
+ *   return ret;
+ * }
+ *
+ */
+MonoMethod*
+mono_marshal_get_native_func_wrapper_indirect (MonoClass *caller_class, MonoMethodSignature *sig,
+                                              gboolean aot)
+{
+       caller_class = mono_class_get_generic_type_definition (caller_class);
+       MonoImage *image = m_class_get_image (caller_class);
+       g_assert (sig->pinvoke);
+       g_assert (!sig->hasthis && ! sig->explicit_this);
+       g_assert (!sig->is_inflated && !sig->has_type_parameters);
+
+       g_assertf (type_is_blittable (sig->ret), "sig return type %s is not blittable\n", mono_type_full_name (sig->ret));
+
+       for (int i = 0; i < sig->param_count; ++i) {
+               MonoType *ty = sig->params [i];
+               g_assertf (type_is_blittable (ty), "sig param %d (type %s) is not blittable\n", i, mono_type_full_name (ty));
+       }
+       /* g_assert (every param and return type is blittable) */
+
+       GHashTable *cache = get_cache (&image->wrapper_caches.native_func_wrapper_indirect_cache,
+                                      (GHashFunc)mono_signature_hash, 
+                                      (GCompareFunc)mono_metadata_signature_equal);
+       
+       MonoMethod *res;
+       if ((res = mono_marshal_find_in_cache (cache, sig)))
+           return res;
+       
+#if 0
+       fprintf (stderr, "generating wrapper for signature %s\n", mono_signature_full_name (sig));
+#endif
+       
+       /* FIXME: better wrapper name */
+       char * name = g_strdup_printf ("wrapper_native_indirect_%p", sig);
+       MonoMethodBuilder *mb = mono_mb_new (caller_class, name, MONO_WRAPPER_MANAGED_TO_NATIVE);
+       mb->method->save_lmf = 1;
+
+       WrapperInfo *info = mono_wrapper_info_create (mb, WRAPPER_SUBTYPE_NATIVE_FUNC_INDIRECT);
+       info->d.managed_to_native.method = NULL;
+
+       MonoMethodPInvoke *piinfo = NULL;
+       MonoMarshalSpec **mspecs = g_new0 (MonoMarshalSpec *, 1 + sig->param_count);
+       MonoNativeWrapperFlags flags = aot ? EMIT_NATIVE_WRAPPER_AOT : (MonoNativeWrapperFlags)0;
+       flags |= EMIT_NATIVE_WRAPPER_FUNC_PARAM | EMIT_NATIVE_WRAPPER_FUNC_PARAM_UNBOXED;
+       mono_marshal_emit_native_wrapper (image, mb, sig, piinfo, mspecs, /*func*/NULL, flags);
+       g_free (mspecs);
+
+       MonoMethodSignature *csig = mono_metadata_signature_dup_add_this (image, sig, mono_defaults.int_class);
+       csig->pinvoke = 0;
+
+       MonoMethodSignature *key_sig = mono_metadata_signature_dup_full (image, sig);
+
+       gboolean found;
+       res = mono_mb_create_and_cache_full (cache, key_sig, mb, csig, csig->param_count + 16, info, &found);
+
+       mono_mb_free (mb);
+
+       return res;
+}
+
+/*
  * mono_marshal_emit_managed_wrapper:
  *
  *   Emit the body of a native-to-managed wrapper. INVOKE_SIG is the signature of
@@ -6383,9 +6461,9 @@ mono_marshal_lookup_pinvoke (MonoMethod *method)
 }
 
 void
-mono_marshal_emit_native_wrapper (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans)
+mono_marshal_emit_native_wrapper (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, MonoNativeWrapperFlags flags)
 {
-       get_marshal_cb ()->emit_native_wrapper (image, mb, sig, piinfo, mspecs, func, aot, check_exceptions, func_param, skip_gc_trans);
+       get_marshal_cb ()->emit_native_wrapper (image, mb, sig, piinfo, mspecs, func, flags);
 }
 
 static MonoMarshalCallbacks marshal_cb;
index b8afb83..ebb61e4 100644 (file)
@@ -115,6 +115,7 @@ typedef enum {
        /* Subtypes of MONO_WRAPPER_MANAGED_TO_NATIVE */
        WRAPPER_SUBTYPE_ICALL_WRAPPER, // specifically JIT icalls
        WRAPPER_SUBTYPE_NATIVE_FUNC_AOT,
+       WRAPPER_SUBTYPE_NATIVE_FUNC_INDIRECT,
        WRAPPER_SUBTYPE_PINVOKE,
        /* Subtypes of MONO_WRAPPER_OTHER */
        WRAPPER_SUBTYPE_SYNCHRONIZED_INNER,
@@ -300,7 +301,17 @@ typedef enum {
 } MonoStelemrefKind;
 
 
-#define MONO_MARSHAL_CALLBACKS_VERSION 4
+typedef enum {
+       EMIT_NATIVE_WRAPPER_AOT = 0x01, /* FIXME: what does "aot" mean here */
+       EMIT_NATIVE_WRAPPER_CHECK_EXCEPTIONS = 0x02,
+       EMIT_NATIVE_WRAPPER_FUNC_PARAM = 0x04,
+       EMIT_NATIVE_WRAPPER_FUNC_PARAM_UNBOXED = 0x08,
+       EMIT_NATIVE_WRAPPER_SKIP_GC_TRANS=0x10,
+} MonoNativeWrapperFlags;
+
+G_ENUM_FUNCTIONS(MonoNativeWrapperFlags);
+
+#define MONO_MARSHAL_CALLBACKS_VERSION 5
 
 typedef struct {
        int version;
@@ -325,7 +336,7 @@ typedef struct {
        void (*emit_virtual_stelemref) (MonoMethodBuilder *mb, const char **param_names, MonoStelemrefKind kind);
        void (*emit_stelemref) (MonoMethodBuilder *mb);
        void (*emit_array_address) (MonoMethodBuilder *mb, int rank, int elem_size);
-       void (*emit_native_wrapper) (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans);
+       void (*emit_native_wrapper) (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, MonoNativeWrapperFlags flags);
        void (*emit_managed_wrapper) (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle);
        void (*emit_runtime_invoke_body) (MonoMethodBuilder *mb, const char **param_names, MonoImage *image, MonoMethod *method, MonoMethodSignature *sig, MonoMethodSignature *callsig, gboolean virtual_, gboolean need_direct_wrapper);
        void (*emit_runtime_invoke_dynamic) (MonoMethodBuilder *mb);
@@ -473,6 +484,10 @@ mono_marshal_get_native_func_wrapper (MonoImage *image, MonoMethodSignature *sig
 MonoMethod*
 mono_marshal_get_native_func_wrapper_aot (MonoClass *klass);
 
+MonoMethod*
+mono_marshal_get_native_func_wrapper_indirect (MonoClass *caller_class, MonoMethodSignature *sig,
+                                              gboolean aot);
+
 MonoMethod *
 mono_marshal_get_struct_to_ptr (MonoClass *klass);
 
@@ -671,7 +686,7 @@ mono_signature_no_pinvoke (MonoMethod *method);
 /* Called from cominterop.c/remoting.c */
 
 void
-mono_marshal_emit_native_wrapper (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, gboolean aot, gboolean check_exceptions, gboolean func_param, gboolean skip_gc_trans);
+mono_marshal_emit_native_wrapper (MonoImage *image, MonoMethodBuilder *mb, MonoMethodSignature *sig, MonoMethodPInvoke *piinfo, MonoMarshalSpec **mspecs, gpointer func, MonoNativeWrapperFlags flags);
 
 void
 mono_marshal_emit_managed_wrapper (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle);
index 3dbf9b8..329f839 100644 (file)
@@ -268,6 +268,7 @@ typedef struct {
        GHashTable *native_wrapper_aot_check_cache;
 
        GHashTable *native_func_wrapper_aot_cache;
+       GHashTable *native_func_wrapper_indirect_cache; /* Indexed by MonoMethodSignature. Protected by the marshal lock */
        GHashTable *remoting_invoke_cache;
        GHashTable *synchronized_cache;
        GHashTable *unbox_wrapper_cache;
index d7a792b..73a6978 100644 (file)
@@ -2355,6 +2355,7 @@ mono_metadata_parse_method_signature_full (MonoImage *m, MonoGenericContainer *c
        case MONO_CALL_STDCALL:
        case MONO_CALL_THISCALL:
        case MONO_CALL_FASTCALL:
+       case MONO_CALL_UNMANAGED_MD:
                method->pinvoke = 1;
                break;
        }
index 965b06a..c44931d 100644 (file)
@@ -37,7 +37,11 @@ typedef enum {
        MONO_CALL_STDCALL,
        MONO_CALL_THISCALL,
        MONO_CALL_FASTCALL,
-       MONO_CALL_VARARG
+       MONO_CALL_VARARG = 0x05,
+       /* unused, */
+       /* unused, */
+       /* unused, */
+       MONO_CALL_UNMANAGED_MD = 0x09, /* default unmanaged calling convention, with additional attributed encoded in modopts */
 } MonoCallConvention;
 
 /* ECMA lamespec: the old spec had more info... */
index 462ed7c..0419214 100644 (file)
@@ -7080,6 +7080,53 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b
                                addr = mono_emit_jit_icall (cfg, mono_get_native_calli_wrapper, args);
                        }
 
+                       if (!method->dynamic && fsig->pinvoke &&
+                           !method->wrapper_type) {
+                               /* MONO_WRAPPER_DYNAMIC_METHOD dynamic method handled above in the
+                               method->dynamic case; for other wrapper types assume the code knows
+                               what its doing and added its own GC transitions */
+
+                               /* TODO: unmanaged[SuppressGCTransition] call conv will set
+                                * skip_gc_trans to TRUE*/
+                               gboolean skip_gc_trans = FALSE;
+                               if (!skip_gc_trans) {
+#if 0
+                                       fprintf (stderr, "generating wrapper for calli in method %s with wrapper type %s\n", method->name, mono_wrapper_type_to_str (method->wrapper_type));
+#endif
+                                       /* Call the wrapper that will do the GC transition instead */
+                                       MonoMethod *wrapper = mono_marshal_get_native_func_wrapper_indirect (method->klass, fsig, cfg->compile_aot);
+
+                                       fsig = mono_method_signature_internal (wrapper);
+
+                                       n = fsig->param_count - 1; /* wrapper has extra fnptr param */
+
+                                       CHECK_STACK (n);
+
+                                       /* move the args to allow room for 'this' in the first position */
+                                       while (n--) {
+                                               --sp;
+                                               sp [1] = sp [0];
+                                       }
+
+                                       sp[0] = addr; /* n+1 args, first arg is the address of the indirect method to call */
+
+                                       g_assert (!fsig->hasthis && !fsig->pinvoke);
+
+                                       gboolean inline_wrapper = cfg->opt & MONO_OPT_INLINE || cfg->compile_aot;
+                                       if (inline_wrapper) {
+                                               int costs = inline_method (cfg, wrapper, fsig, sp, ip, cfg->real_offset, TRUE);
+                                               CHECK_CFG_EXCEPTION;
+                                               g_assert (costs > 0);
+                                               cfg->real_offset += 5;
+                                               inline_costs += costs;
+                                               ins = sp[0];
+                                       } else {
+                                               ins = mono_emit_method_call (cfg, wrapper, /*args*/sp, NULL);
+                                       }
+                                       goto calli_end;
+                               }
+                       }
+
                        n = fsig->param_count + fsig->hasthis;
 
                        CHECK_STACK (n);