Skip to content

Commit

Permalink
[Mono]: Add support for lazy runtime init in native-to-managed wrappe…
Browse files Browse the repository at this point in the history
…r, similar to NativeAOT library build. (#82253)

* Add support for runtime init in native-to-managed wrapper.

NativeAOT supports library mode, building all needed runtime and
managed code into a static or shared library that can be loaded
and used by embedding applications. NativeAOT exports managed
functiond marked with UnmanagedCallersOnly attribute that can be
called by embedder to run managed code. NativeAOT runtime doesn't
need any initialization, meaning that calling the managed method
exported using UnmanagedCallersOnly attribute
will perform lazy runtime initialization on first call.

This commit add similar support to MonoVM giving it possibilities
to include a call to a runtime init function as part of
native-to-managed wrapper used for methods marked with
UnmanagedCallersOnly attribute + entry point. AOT compiler
accepts a new argument, runtime-init-callback, if used like
that, the native-to-managed wrapper will get a call to a default
invoke callback method implemented by runtime, that will call a
set callback once (thread safe). It is also possible to pass
runtime-init-callback=<custom symbol> to AOT compiler, and
in that case native-to-managed wrapper will call that function and its
up to implementor to do a thread safe implementation of runtime init.
This capability could be used in case where the library can't set the
callback before consumer of the library class the exported function.

Two new runtime API's have been added in this commit, one to set
the callback called by default runtime init implementation and the
other is the implementation of that function used in native-to-managed
wrapper if user doesn't use a custom runtime init callback function.
Since this integration scenario is mainly for library build scenarios
(we control the library builder), these methods are marked as
MONO_COMPONENT_API and not something that should be part of the public
API surface.

* Split mono_marshal_get_managed_wrapper into two functions.

* Drop runtime init for mono_marshal_emit_managed_wrapper.

* aquire/release semantics around runtime_init_callback.

* Detect MONO_JIT_ICALL_mono_dummy_runtime_init_callback in more places.

* Add description of new AOT compiler options.
  • Loading branch information
lateralusX authored Feb 23, 2023
1 parent fed0691 commit f295ea1
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 43 deletions.
1 change: 1 addition & 0 deletions src/mono/mono/metadata/jit-icall-reg.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ MONO_JIT_ICALL (ves_icall_string_new_wrapper) \
MONO_JIT_ICALL (ves_icall_thread_finish_async_abort) \
MONO_JIT_ICALL (mono_marshal_lookup_pinvoke) \
MONO_JIT_ICALL (mono_gsharedvt_constrained_call_fast) \
MONO_JIT_ICALL (mono_dummy_runtime_init_callback) \
\
MONO_JIT_ICALL (count) \

Expand Down
11 changes: 10 additions & 1 deletion src/mono/mono/metadata/marshal-lightweight.c
Original file line number Diff line number Diff line change
Expand Up @@ -2491,7 +2491,7 @@ emit_managed_wrapper_validate_signature (MonoMethodSignature* sig, MonoMarshalSp
}

static void
emit_managed_wrapper_ilgen (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle, MonoError *error)
emit_managed_wrapper_ilgen (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle, gboolean runtime_init_callback, MonoError *error)
{
MonoMethodSignature *sig, *csig;
int i, *tmp_locals;
Expand Down Expand Up @@ -2550,9 +2550,18 @@ emit_managed_wrapper_ilgen (MonoMethodBuilder *mb, MonoMethodSignature *invoke_s
* return ret;
*/

/* delete_old = FALSE */
mono_mb_emit_icon (mb, 0);
mono_mb_emit_stloc (mb, 2);

/*
* Transformed into a direct icall when runtime init callback is enabled for a native-to-managed wrapper.
* This icall is special cased in the JIT so it can be called in native-to-managed wrapper before
* runtime has been initialized. On return, runtime must be fully initialized.
*/
if (runtime_init_callback)
mono_mb_emit_icall (mb, mono_dummy_runtime_init_callback);

gc_unsafe_transition_builder_emit_enter(&gc_unsafe_builder);

/* we first do all conversions */
Expand Down
46 changes: 33 additions & 13 deletions src/mono/mono/metadata/marshal.c
Original file line number Diff line number Diff line change
Expand Up @@ -3910,15 +3910,15 @@ mono_marshal_get_native_func_wrapper_indirect (MonoClass *caller_class, MonoMeth
/*
* mono_marshal_emit_managed_wrapper:
*
* Emit the body of a native-to-managed wrapper. INVOKE_SIG is the signature of
* Emit the body of a native-to-managed wrapper. INVOKE_SIG is the signature of
* the delegate which wraps the managed method to be called. For closed delegates,
* it could have fewer parameters than the method it wraps.
* THIS_LOC is the memory location where the target of the delegate is stored.
*/
void
mono_marshal_emit_managed_wrapper (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle, MonoError *error)
{
get_marshal_cb ()->emit_managed_wrapper (mb, invoke_sig, mspecs, m, method, target_handle, error);
get_marshal_cb ()->emit_managed_wrapper (mb, invoke_sig, mspecs, m, method, target_handle, FALSE, error);
}

static gboolean
Expand Down Expand Up @@ -4017,16 +4017,8 @@ method_signature_is_usable_when_marshalling_disabled (MonoMethodSignature *sig)
return check_all_types_in_method_signature (sig, &type_is_usable_when_marshalling_disabled);
}

/**
* mono_marshal_get_managed_wrapper:
* Generates IL code to call managed methods from unmanaged code
* If \p target_handle is \c 0, the wrapper info will be a \c WrapperInfo structure.
*
* If \p delegate_klass is \c NULL, we're creating a wrapper for a function pointer to a method marked with
* UnamangedCallersOnlyAttribute.
*/
MonoMethod *
mono_marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass, MonoGCHandle target_handle, MonoError *error)
static MonoMethod *
marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass, MonoGCHandle target_handle, gboolean runtime_init_callback, MonoError *error)
{
MonoMethodSignature *sig, *csig, *invoke_sig;
MonoMethodBuilder *mb;
Expand Down Expand Up @@ -4192,7 +4184,7 @@ mono_marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass,
mono_custom_attrs_free (cinfo);
}

mono_marshal_emit_managed_wrapper (mb, invoke_sig, mspecs, &m, method, target_handle, error);
get_marshal_cb ()->emit_managed_wrapper (mb, invoke_sig, mspecs, &m, method, target_handle, runtime_init_callback, error);

res = NULL;
if (is_ok (error)) {
Expand Down Expand Up @@ -4225,6 +4217,34 @@ mono_marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass,
return res;
}

/**
* mono_marshal_get_managed_wrapper:
* Generates IL code to call managed methods from unmanaged code
* If \p target_handle is \c 0, the wrapper info will be a \c WrapperInfo structure.
*
* If \p delegate_klass is \c NULL, we're creating a wrapper for a function pointer to a method marked with
* UnamangedCallersOnlyAttribute.
*/
MonoMethod *
mono_marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass, MonoGCHandle target_handle, MonoError *error)
{
return marshal_get_managed_wrapper (method, delegate_klass, target_handle, FALSE, error);
}

/**
* mono_marshal_get_runtime_init_managed_wrapper:
* Generates IL code to call managed methods from unmanaged code with lazy runtime init support
* If \p target_handle is \c 0, the wrapper info will be a \c WrapperInfo structure.
*
* If \p delegate_klass is \c NULL, we're creating a wrapper for a function pointer to a method marked with
* UnamangedCallersOnlyAttribute.
*/
MonoMethod *
mono_marshal_get_runtime_init_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass, MonoGCHandle target_handle, MonoError *error)
{
return marshal_get_managed_wrapper (method, delegate_klass, target_handle, TRUE, error);
}

gpointer
mono_marshal_get_vtfixup_ftnptr (MonoImage *image, guint32 token, guint16 type)
{
Expand Down
5 changes: 4 additions & 1 deletion src/mono/mono/metadata/marshal.h
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ typedef struct {
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, MonoNativeWrapperFlags flags);
void (*emit_managed_wrapper) (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle, MonoError *error);
void (*emit_managed_wrapper) (MonoMethodBuilder *mb, MonoMethodSignature *invoke_sig, MonoMarshalSpec **mspecs, EmitMarshalContext* m, MonoMethod *method, MonoGCHandle target_handle, gboolean runtime_init_callback, MonoError *error);
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);
void (*emit_delegate_begin_invoke) (MonoMethodBuilder *mb, MonoMethodSignature *sig);
Expand Down Expand Up @@ -506,6 +506,9 @@ mono_marshal_get_string_ctor_signature (MonoMethod *method);
MonoMethod *
mono_marshal_get_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass, MonoGCHandle this_loc, MonoError *exernal_error);

MonoMethod *
mono_marshal_get_runtime_init_managed_wrapper (MonoMethod *method, MonoClass *delegate_klass, MonoGCHandle this_loc, MonoError *exernal_error);

gpointer
mono_marshal_get_vtfixup_ftnptr (MonoImage *image, guint32 token, guint16 type);

Expand Down
87 changes: 61 additions & 26 deletions src/mono/mono/mini/aot-compiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ typedef struct MonoAotOptions {
gboolean no_opt;
char *clangxx;
char *depfile;
char *runtime_init_callback;
} MonoAotOptions;

typedef enum {
Expand Down Expand Up @@ -5303,7 +5304,11 @@ MONO_RESTORE_WARNING
}
mono_reflection_free_custom_attr_data_args_noalloc (decoded_args);

wrapper = mono_marshal_get_managed_wrapper (method, NULL, 0, error);
if (acfg->aot_opts.runtime_init_callback != NULL && export_name != NULL)
wrapper = mono_marshal_get_runtime_init_managed_wrapper (method, NULL, 0, error);
else
wrapper = mono_marshal_get_managed_wrapper (method, NULL, 0, error);

if (!is_ok (error)) {
report_loader_error (acfg, error, FALSE, "Unable to generate native entry point '%s' due to '%s'.", mono_method_get_full_name (method), mono_error_get_message (error));
continue;
Expand Down Expand Up @@ -6625,13 +6630,24 @@ emit_and_reloc_code (MonoAotCompile *acfg, MonoMethod *method, guint8 *code, gui
}
} else if (patch_info->type == MONO_PATCH_INFO_JIT_ICALL_ID) {
MonoJitICallInfo * const info = mono_find_jit_icall_info (patch_info->data.jit_icall_id);
const char * const sym = info->c_symbol;
if (!got_only && sym && acfg->aot_opts.direct_icalls && info->func == info->wrapper) {
/* Call to a jit icall without a wrapper */
direct_call = TRUE;
external_call = TRUE;
g_assert (strlen (sym) < 1000);
direct_call_target = g_strdup_printf ("%s%s", acfg->user_symbol_prefix, sym);
if (!got_only && info->func == info->wrapper) {
const char * sym = NULL;
if (patch_info->data.jit_icall_id == MONO_JIT_ICALL_mono_dummy_runtime_init_callback) {
g_assert (acfg->aot_opts.static_link && acfg->aot_opts.runtime_init_callback != NULL);
sym = acfg->aot_opts.runtime_init_callback;
direct_call = TRUE;
external_call = TRUE;
} else if (info->c_symbol && acfg->aot_opts.direct_icalls) {
sym = info->c_symbol;
direct_call = TRUE;
external_call = TRUE;
}

if (sym) {
/* Call to a jit icall without a wrapper */
g_assert (strlen (sym) < 1000);
direct_call_target = g_strdup_printf ("%s%s", acfg->user_symbol_prefix, sym);
}
}
}
if (direct_call) {
Expand Down Expand Up @@ -8749,6 +8765,14 @@ mono_aot_parse_options (const char *aot_options, MonoAotOptions *opts)
}
} else if (str_begins_with (arg, "depfile=")) {
opts->depfile = g_strdup (arg + strlen ("depfile="));
} else if (str_begins_with (arg, "runtime-init-callback=")) {
opts->runtime_init_callback = g_strdup (arg + strlen ("runtime-init-callback="));
if (opts->runtime_init_callback [0] == '\0') {
printf ("Missing runtime-init-callback symbol.\n");
exit (0);
}
} else if (str_begins_with (arg, "runtime-init-callback")) {
opts->runtime_init_callback = g_strdup ("mono_invoke_runtime_init_callback");
} else if (str_begins_with (arg, "help") || str_begins_with (arg, "?")) {
printf ("Supported options for --aot:\n");
printf (" asmonly - \n");
Expand Down Expand Up @@ -8786,6 +8810,8 @@ mono_aot_parse_options (const char *aot_options, MonoAotOptions *opts)
printf (" mibc-profile=<string> - \n");
printf (" print-skipped-methods - \n");
printf (" readonly-value=<value> - \n");
printf (" runtime-init-callback - Enable default runtime init callback support for UnmanagedCallersOnly+EntryPoint native-to-managed wrappers. Requires 'static' option.\n");
printf (" runtime-init-callback=<value> - Enable custom runtime init callback support for UnmanagedCallersOnly+EntryPoint native-to-managed wrappers. Requires 'static' option. Wrapper makes a direct call to <value> symbol, initializing runtime.\n");
printf (" save-temps - \n");
printf (" soft-debug - \n");
printf (" static - \n");
Expand Down Expand Up @@ -10290,28 +10316,30 @@ mono_aot_mark_unused_llvm_plt_entry (MonoJumpInfo *patch_info)
char*
mono_aot_get_direct_call_symbol (MonoJumpInfoType type, gconstpointer data)
{
gboolean direct_calls = llvm_acfg->aot_opts.direct_icalls;
const char *sym = NULL;

if (llvm_acfg->aot_opts.direct_icalls) {
if (type == MONO_PATCH_INFO_JIT_ICALL_ADDR) {
/* Call to a C function implementing a jit icall */
sym = mono_find_jit_icall_info ((MonoJitICallId)(gsize)data)->c_symbol;
} else if (type == MONO_PATCH_INFO_ICALL_ADDR_CALL) {
MonoMethod *method = (MonoMethod *)data;
if (!(method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL))
sym = lookup_icall_symbol_name_aot (method);
else if (is_direct_pinvoke_specified_for_method (llvm_acfg, method))
get_pinvoke_import (llvm_acfg, method, NULL, &sym);
} else if (type == MONO_PATCH_INFO_JIT_ICALL_ID) {
MonoJitICallInfo const * const info = mono_find_jit_icall_info ((MonoJitICallId)(gsize)data);
char const * const name = info->c_symbol;
if (name && info->func == info->wrapper)
sym = name;
if (direct_calls && type == MONO_PATCH_INFO_JIT_ICALL_ADDR) {
/* Call to a C function implementing a jit icall */
sym = mono_find_jit_icall_info ((MonoJitICallId)(gsize)data)->c_symbol;
} else if (direct_calls && type == MONO_PATCH_INFO_ICALL_ADDR_CALL) {
MonoMethod *method = (MonoMethod *)data;
if (!(method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL))
sym = lookup_icall_symbol_name_aot (method);
else if (is_direct_pinvoke_specified_for_method (llvm_acfg, method))
get_pinvoke_import (llvm_acfg, method, NULL, &sym);
} else if (type == MONO_PATCH_INFO_JIT_ICALL_ID && (direct_calls || (MonoJitICallId)(gsize)data == MONO_JIT_ICALL_mono_dummy_runtime_init_callback)) {
MonoJitICallInfo const * const info = mono_find_jit_icall_info ((MonoJitICallId)(gsize)data);
if (info->func == info->wrapper) {
if ((MonoJitICallId)(gsize)data == MONO_JIT_ICALL_mono_dummy_runtime_init_callback) {
sym = llvm_acfg->aot_opts.runtime_init_callback;
} else {
sym = info->c_symbol ? info->c_symbol : NULL;
}
}
if (sym)
return g_strdup (sym);
}
return NULL;

return sym ? g_strdup (sym) : NULL;
}

char*
Expand Down Expand Up @@ -13890,6 +13918,7 @@ aot_opts_free (MonoAotOptions *aot_opts)
g_free (aot_opts->llvm_cpu_attr);
g_free (aot_opts->clangxx);
g_free (aot_opts->depfile);
g_free (aot_opts->runtime_init_callback);
}

static void
Expand Down Expand Up @@ -15190,6 +15219,12 @@ mono_aot_assemblies (MonoAssembly **assemblies, int nassemblies, guint32 jit_opt
goto early_exit;
}

if (aot_opts.runtime_init_callback != NULL && !aot_opts.static_link) {
fprintf (stderr, "The 'runtime-init-callback' option requires the 'static' option.\n");
res = 1;
goto early_exit;
}

if (aot_opts.dedup_include) {
/* Find the assembly which will contain the dedup-ed code */
int dedup_aindex = -1;
Expand Down
10 changes: 10 additions & 0 deletions src/mono/mono/mini/jit-icalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1708,6 +1708,16 @@ mono_dummy_jit_icall_val (gpointer val)
{
}

/* Dummy icall place holder function representing runtime init call. */
/* When used, function will be replaced with a direct icall to a custom */
/* runtime init function called from start of native-to-managed wrapper. */
/* This function should never end up being called. */
void
mono_dummy_runtime_init_callback (void)
{
g_assert (!"Runtime incorrectly configured to support runtime init callback from native-to-managed wrapper.");
}

void
mini_init_method_rgctx (MonoMethodRuntimeGenericContext *mrgctx, MonoGSharedMethodInfo *info)
{
Expand Down
2 changes: 2 additions & 0 deletions src/mono/mono/mini/jit-icalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ ICALL_EXPORT void mono_dummy_jit_icall (void);

ICALL_EXPORT void mono_dummy_jit_icall_val (gpointer ptr);

ICALL_EXPORT void mono_dummy_runtime_init_callback (void);

ICALL_EXPORT void mini_init_method_rgctx (MonoMethodRuntimeGenericContext *mrgctx, MonoGSharedMethodInfo *info);

#endif /* __MONO_JIT_ICALLS_H__ */
39 changes: 39 additions & 0 deletions src/mono/mono/mini/mini-runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@ static GPtrArray *profile_options;
static GSList *tramp_infos;
GSList *mono_interp_only_classes;

static MonoRuntimeInitCallback runtime_init_callback;
static guint64 runtime_init_thread_id = G_MAXUINT64;

static void register_icalls (void);
static void runtime_cleanup (MonoDomain *domain, gpointer user_data);
static void mini_invalidate_transformed_interp_methods (MonoAssemblyLoadContext *alc, uint32_t generation);
Expand Down Expand Up @@ -5036,6 +5039,9 @@ register_icalls (void)
register_icall_no_wrapper (mono_interp_entry_from_trampoline, mono_icall_sig_void_ptr_ptr);
register_icall_no_wrapper (mono_interp_to_native_trampoline, mono_icall_sig_void_ptr_ptr);

/* Register dummy icall placeholder for runtime init callback support in native-to-managed wrapper */
register_icall_no_wrapper (mono_dummy_runtime_init_callback, mono_icall_sig_void);

#ifdef MONO_ARCH_HAS_REGISTER_ICALL
mono_arch_register_icall ();
#endif
Expand Down Expand Up @@ -5395,3 +5401,36 @@ mini_alloc_jinfo (MonoJitMemoryManager *jit_mm, int size)
return (MonoJitInfo*)mono_mem_manager_alloc0 (jit_mm->mem_manager, size);
}
}

void
mono_set_runtime_init_callback (MonoRuntimeInitCallback callback)
{
runtime_init_callback = callback;
}

/*
* Default implementation invoking runtime init callback in native-to-managed wrapper.
*/
void
mono_invoke_runtime_init_callback (void)
{
MonoRuntimeInitCallback callback = NULL;
mono_atomic_load_acquire (callback, MonoRuntimeInitCallback, &runtime_init_callback);
if (G_UNLIKELY (callback)) {
guint64 thread_id = mono_native_thread_os_id_get ();
if (callback && (mono_atomic_load_i64 ((volatile gint64 *)&runtime_init_thread_id) == thread_id))
return;

while (mono_atomic_cas_i64 ((volatile gint64 *)&runtime_init_thread_id, (gint64)thread_id, (gint64)G_MAXUINT64) != (gint64)G_MAXUINT64)
g_usleep (1000);

mono_atomic_load_acquire (callback, MonoRuntimeInitCallback, &runtime_init_callback);
if (callback) {
if (!mono_thread_info_current_unchecked ())
callback ();
mono_atomic_store_release (&runtime_init_callback, NULL);
}

mono_atomic_xchg_i64 ((volatile gint64 *)&runtime_init_thread_id, (gint64)G_MAXUINT64);
}
}
8 changes: 8 additions & 0 deletions src/mono/mono/mini/mini-runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -718,4 +718,12 @@ void mini_register_sigterm_handler (void);
MONO_RESTORE_WARNING \
} while (0)

typedef void (*MonoRuntimeInitCallback) (void);

MONO_COMPONENT_API void
mono_set_runtime_init_callback (MonoRuntimeInitCallback callback);

MONO_COMPONENT_API void
mono_invoke_runtime_init_callback (void);

#endif /* __MONO_MINI_RUNTIME_H__ */
4 changes: 2 additions & 2 deletions src/mono/mono/mini/mini.c
Original file line number Diff line number Diff line change
Expand Up @@ -3178,8 +3178,8 @@ mini_method_compile (MonoMethod *method, guint32 opts, JitFlags flags, int parts
cfg->jit_mm = jit_mm_for_method (cfg->method);
cfg->mem_manager = m_method_get_mem_manager (cfg->method);

if (cfg->method->wrapper_type == MONO_WRAPPER_ALLOC) {
/* We can't have seq points inside gc critical regions */
if (cfg->method->wrapper_type == MONO_WRAPPER_ALLOC || cfg->method->wrapper_type == MONO_WRAPPER_NATIVE_TO_MANAGED) {
/* We can't have seq points inside gc critical regions or native-to-managed wrapper */
cfg->gen_seq_points = FALSE;
cfg->gen_sdb_seq_points = FALSE;
}
Expand Down

0 comments on commit f295ea1

Please sign in to comment.