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

Conversation

lateralusX
Copy link
Member

@lateralusX lateralusX commented Feb 16, 2023

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 functions 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= 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 lazy runtime init. This capability could be used in case where the library can't set the callback before consumer of the library calls 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.

This feature is a is used together with AOT (partial or full) and the static AOT mode, a check has been added to AOT compiler to make sure prerequisites are meet.

NOTE, this only affects the native-to-managed wrappers added for managed methods decorated with UnmanagedCallersOnly + EntryPoint, all other generated native-to-managed wrappers will not be affected by this feature.

Copy link
Member

@lambdageek lambdageek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main thing I'd like to see is a separate call for creating a n2m wrapper that include the callback from the ones that don't. It's kind of annoying to pass FALSE in 20 places just to pass TRUE in one.

I'm confused why we need two separate things that the user can modify. It seems like one should be enough? Also i'm unclear on what exactly the user is supposed to call from the callback - monovm_initialize ?

In terms of implementation, I really wish it was easier to create a wrapper around the n2m wrapper.

also I don't understand why the substitution of the callback is happening in the aot compiler's guts and not in method-to-ir

src/mono/mono/mini/aot-compiler.c Outdated Show resolved Hide resolved
src/mono/mono/mini/mini-runtime.c Show resolved Hide resolved
src/mono/mono/metadata/marshal-lightweight.c Show resolved Hide resolved
src/mono/mono/mini/aot-compiler.c Show resolved Hide resolved
src/mono/mono/mini/aot-compiler.c Outdated Show resolved Hide resolved
src/mono/mono/mini/mini-runtime.c Show resolved Hide resolved
src/mono/mono/mini/mini-runtime.c Outdated Show resolved Hide resolved
src/mono/mono/mini/mini.c Show resolved Hide resolved
@lateralusX
Copy link
Member Author

lateralusX commented Feb 17, 2023

The main thing I'd like to see is a separate call for creating a n2m wrapper that include the callback from the ones that don't. It's kind of annoying to pass FALSE in 20 places just to pass TRUE in one.

I'm confused why we need two separate things that the user can modify. It seems like one should be enough? Also i'm unclear on what exactly the user is supposed to call from the callback - monovm_initialize ?

In terms of implementation, I really wish it was easier to create a wrapper around the n2m wrapper.

also I don't understand why the substitution of the callback is happening in the aot compiler's guts and not in method-to-ir

Most of the answers are inlined above. In the end I think we have the following options in this PR:

  • Support both options as done in this PR, ability to use build in mono_invoke_runtime_init_callback or use a custom symbol compiled into native-to-managed wrapper. Supports more embedding scenarios and put less requirements on compiler/platform supporting things like C++ global constructors, __attribute__((constructor)) or compiler/linker pragmas to run code on shared library load time.
  • Drop and just keep one of the mechanism in current PR.
  • If we decide to only support mono_set_runtime_init_callback/mono_invoke_runtime_init_callback we could consider piggy back on our call to mono_threads_attach_coop and implement a wrapper version of that that gets called in our native-to-managed wrapper when user requests lazy runtime init support. So what we do then is to do a mono_threads_attach_coop_wrapper icall, when user enables runtime init callback support, use that icall instead of regular mono_threads_attach_coop in native-to-managed wrapper and then detect alternative callback in aot compiler and lower it to a direct call to a symbol, mono_threads_attach_coop_wrapper. This is very close to what we currently do with the mono_dummy_runtime_init_callback, but we roll the feature into our current call of mono_threads_attach_coop, very similar to how NativeAOT does it. That would put requirements on the embedder to be able to set the callback very early during load of shared library. We already verified that iOS/Android supports __attribute__((constructor)) to achieve this, so if we just pick one solution, maybe rolling it into a wrapper version of mono_threads_attach_coop is a simpler alternative. Thoughts?

Copy link
Member

@lambdageek lambdageek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to be careful that runtime_init_callback read/writes aren't reordered. Do we have some off the shelf spinlock in the runtime that we could use?

src/mono/mono/mini/mini-runtime.c Outdated Show resolved Hide resolved
src/mono/mono/mini/mini-runtime.c Outdated Show resolved Hide resolved
@lateralusX
Copy link
Member Author

lateralusX commented Feb 17, 2023

I think we need to be careful that runtime_init_callback read/writes aren't reordered. Do we have some off the shelf spinlock in the runtime that we could use?

I mainly replicated the implementation form NativeAOT on this one

while (PalInterlockedCompareExchangePointer((void *volatile *)&g_RuntimeInitializingThread, this, NULL) != NULL)

it does the same semantics, but I can add clear acquire/release semantics on runtime_init_callback.

@lateralusX lateralusX force-pushed the lateralusX/runtime-init-callback branch from 4118c93 to 598bf8e Compare February 21, 2023 11:07
@lateralusX
Copy link
Member Author

lateralusX commented Feb 21, 2023

@vargaz @lambdageek @mdh1418 Do you think this PR is ready to go or anything else that needs to be adjusted?

Copy link
Member

@lambdageek lambdageek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. 🎉 :shipit:

Copy link
Member

@mdh1418 mdh1418 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

@lateralusX
Copy link
Member Author

Timeouts seems to be happening on many PR's to probably unrelated.

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.
@lateralusX lateralusX force-pushed the lateralusX/runtime-init-callback branch from 598bf8e to f6fe400 Compare February 23, 2023 13:52
@lateralusX
Copy link
Member Author

Failures in runtime (Build Libraries Test Run release mono linux arm64 Debug) is a known issue.

@lateralusX lateralusX merged commit f295ea1 into dotnet:main Feb 23, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Mar 25, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants