Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Mono]: Add support for lazy runtime init in native-to-managed wrapper, similar to NativeAOT library build. #82253

Merged
merged 6 commits into from
Feb 23, 2023
Merged
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);
lateralusX marked this conversation as resolved.
Show resolved Hide resolved

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);
lateralusX marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
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)
lateralusX marked this conversation as resolved.
Show resolved Hide resolved
{
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);
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
}
}
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) {
mdh1418 marked this conversation as resolved.
Show resolved Hide resolved
/* 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