Add support for external icalls not using a wrapper. (#45766)
authormonojenkins <jo.shields+jenkins@xamarin.com>
Wed, 9 Dec 2020 13:06:50 +0000 (08:06 -0500)
committerGitHub <noreply@github.com>
Wed, 9 Dec 2020 13:06:50 +0000 (14:06 +0100)
Add support to register an external icall getting minimal runtime overhead when called. An icall that can be used without a wrapper needs to follow specific rules in order to correctly collaborate with runtime. All functions are currently internal only and only accessible by runtime or embedders using static linking and fully acknowledge that this is not an optimization available as a public API.

Co-authored-by: lateralusX <lateralusX@users.noreply.github.com>
src/mono/mono/metadata/class-internals.h
src/mono/mono/metadata/icall-internals.h
src/mono/mono/metadata/icall.c
src/mono/mono/mini/mini-runtime.c

index e56ae5e..0f101a0 100644 (file)
@@ -112,7 +112,10 @@ struct _MonoMethodPInvoke {
        MonoMethod method;
        gpointer addr;
        /* add marshal info */
-       guint16 piflags;  /* pinvoke flags */
+       union {
+               guint16 piflags;  /* pinvoke flags */
+               guint16 icflags;  /* icall flags */
+       };
        guint32 implmap_idx;  /* index into IMPLMAP */
 };
 
index c699aa4..082a57b 100644 (file)
@@ -67,12 +67,19 @@ typedef enum {
        MONO_ICALL_FLAGS_NONE = 0,
        MONO_ICALL_FLAGS_FOREIGN = 1 << 1,
        MONO_ICALL_FLAGS_USES_HANDLES = 1 << 2,
-       MONO_ICALL_FLAGS_COOPERATIVE = 1 << 3
+       MONO_ICALL_FLAGS_COOPERATIVE = 1 << 3,
+       MONO_ICALL_FLAGS_NO_WRAPPER = 1 << 4
 } MonoInternalCallFlags;
 
 gconstpointer
 mono_lookup_internal_call_full_with_flags (MonoMethod *method, gboolean warn_on_missing, guint32 *flags);
 
+void
+mono_dangerous_add_internal_call_coop (const char *name, const void* method);
+
+void
+mono_dangerous_add_internal_call_no_wrapper (const char *name, const void* method);
+
 gboolean
 mono_is_missing_icall_addr (gconstpointer addr);
 
index e060e65..89409df 100644 (file)
@@ -9202,6 +9202,44 @@ add_internal_call_with_flags (const char *name, gconstpointer method, guint32 fl
 }
 
 /**
+* mono_dangerous_add_internal_call_coop:
+* \param name method specification to surface to the managed world
+* \param method pointer to a C method to invoke when the method is called
+*
+* Similar to \c mono_dangerous_add_raw_internal_call.
+*
+*/
+void
+mono_dangerous_add_internal_call_coop (const char *name, gconstpointer method)
+{
+       add_internal_call_with_flags (name, method, MONO_ICALL_FLAGS_COOPERATIVE);
+}
+
+/**
+* mono_dangerous_add_internal_call_no_wrapper:
+* \param name method specification to surface to the managed world
+* \param method pointer to a C method to invoke when the method is called
+*
+* Similar to \c mono_dangerous_add_raw_internal_call but with more requirements for correct
+* operation.
+*
+* The \p method must NOT:
+*
+* Run for an unbounded amount of time without calling the mono runtime.
+* Additionally, the method must switch to GC Safe mode to perform all blocking
+* operations: performing blocking I/O, taking locks, etc. The method can't throw or raise
+* exceptions or call other methods that will throw or raise exceptions since the runtime won't
+* be able to detect exeptions and unwinder won't be able to correctly find last managed frame in callstack.
+* This registration method is for icalls that needs very low overhead and follow all rules in their implementation.
+*
+*/
+void
+mono_dangerous_add_internal_call_no_wrapper (const char *name, gconstpointer method)
+{
+       add_internal_call_with_flags (name, method, MONO_ICALL_FLAGS_NO_WRAPPER);
+}
+
+/**
  * mono_add_internal_call:
  * \param name method specification to surface to the managed world
  * \param method pointer to a C method to invoke when the method is called
@@ -9241,7 +9279,7 @@ add_internal_call_with_flags (const char *name, gconstpointer method, guint32 fl
 void
 mono_add_internal_call (const char *name, gconstpointer method)
 {
-       mono_add_internal_call_with_flags (name, method, FALSE);
+       add_internal_call_with_flags (name, method, MONO_ICALL_FLAGS_FOREIGN);
 }
 
 /**
@@ -9266,7 +9304,7 @@ mono_add_internal_call (const char *name, gconstpointer method)
 void
 mono_dangerous_add_raw_internal_call (const char *name, gconstpointer method)
 {
-       mono_add_internal_call_with_flags (name, method, TRUE);
+       add_internal_call_with_flags (name, method, MONO_ICALL_FLAGS_COOPERATIVE);
 }
 
 /**
@@ -9296,7 +9334,7 @@ mono_add_internal_call_with_flags (const char *name, gconstpointer method, gbool
 void
 mono_add_internal_call_internal (const char *name, gconstpointer method)
 {
-       mono_add_internal_call_with_flags (name, method, TRUE);
+       add_internal_call_with_flags (name, method, MONO_ICALL_FLAGS_COOPERATIVE);
 }
 
 /* 
index 0aaa0b5..695f24b 100644 (file)
@@ -2381,24 +2381,38 @@ compile_special (MonoMethod *method, MonoDomain *target_domain, MonoError *error
                MonoMethodPInvoke* piinfo = (MonoMethodPInvoke *) method;
 
                if (!piinfo->addr) {
-                       if (method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL)
-                               piinfo->addr = mono_lookup_internal_call (method);
-                       else if (method->iflags & METHOD_IMPL_ATTRIBUTE_NATIVE)
+                       if (method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) {
+                               guint32 flags = MONO_ICALL_FLAGS_NONE;
+                               gpointer icall_addr;
+                               icall_addr = (gpointer)mono_lookup_internal_call_full_with_flags (method, TRUE, (guint32 *)&flags);
+                               if (flags & MONO_ICALL_FLAGS_NO_WRAPPER) {
+                                       piinfo->icflags = MONO_ICALL_FLAGS_NO_WRAPPER;
+                                       mono_memory_write_barrier ();
+                               }
+                               piinfo->addr = icall_addr;
+                       } else if (method->iflags & METHOD_IMPL_ATTRIBUTE_NATIVE) {
 #ifdef HOST_WIN32
                                g_warning ("Method '%s' in assembly '%s' contains native code that cannot be executed by Mono in modules loaded from byte arrays. The assembly was probably created using C++/CLI.\n", mono_method_full_name (method, TRUE), m_class_get_image (method->klass)->name);
 #else
                                g_warning ("Method '%s' in assembly '%s' contains native code that cannot be executed by Mono on this platform. The assembly was probably created using C++/CLI.\n", mono_method_full_name (method, TRUE), m_class_get_image (method->klass)->name);
 #endif
-                       else {
+                       else {
                                ERROR_DECL (ignored_error);
                                mono_lookup_pinvoke_call_internal (method, ignored_error);
                                mono_error_cleanup (ignored_error);
                        }
                }
 
-               MonoMethod *nm = mono_marshal_get_native_wrapper (method, TRUE, mono_aot_only);
-               gpointer compiled_method = mono_jit_compile_method_jit_only (nm, error);
-               return_val_if_nok (error, NULL);
+               mono_memory_read_barrier ();
+
+               gpointer compiled_method = NULL;
+               if ((method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) && (piinfo->icflags & MONO_ICALL_FLAGS_NO_WRAPPER)) {
+                       compiled_method = piinfo->addr;
+               } else {
+                       MonoMethod *nm = mono_marshal_get_native_wrapper (method, TRUE, mono_aot_only);
+                       compiled_method = mono_jit_compile_method_jit_only (nm, error);
+                       return_val_if_nok (error, NULL);
+               }
 
                code = mono_get_addr_from_ftnptr (compiled_method);
                jinfo = mono_jit_info_table_find (target_domain, code);