-
Notifications
You must be signed in to change notification settings - Fork 206
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
[NativeAOT LLVM] Support dotnet.js #2434
Comments
cc @dotnet/nativeaot-llvm @pavelsavara @ivanpovazan |
A few comments on the overall architecture.
The current NAOT-LLVM build is very simple (pass a few things to Another point is that the JS runtime contains things that are not the runtime's responsibility, but rather belong to libraries (e. g. hybrid globalization). It should be ensured that the runtime (unmanaged code and
As with the above, (C#) code supporting it should live separately, in, e. g., a new (That said, I am not familiar with how it is implemented currently)
Is a bit of a hard problem. Last time I looked, Mono code looked it up by name emitted by Roslyn. That is a hack. Perhaps we could consider asking Roslyn to somehow mark it... |
In Mono, dotnet.js is build during runtime build and than during app build it's just picked up from runtime pack + emscripten js with build different settings. Our golas is to support the same JS API as we have for Mono (you can see it here https://github.com/maraf/MinimalDotNetWasmNativeAOT/blob/dotnetjs/DotnetJsHack/main.js. The
I don't details yet. AFAIK hybrid globalization is done by icalls from Globalization* into JavaScript
The C# code already lives in a separate library
I don't have a solution for that yet. We also need to invoke JS marshaling to get the Task correctly back to JS as a promise |
For Javascript support in general. Jco (https://github.com/bytecodealliance/jco) are adding async/promise support to their wit bindgen, I wonder is there a route there, i.e. make the Javascript a component and consume it that way. Down the road a bit as we don't have full wit support yet. (Edited as not related to Main, just an observation on work in related spaces) |
What does the Separate question: is the JS minified by default (at runtime build)? In NAOT, we have so far been following the strategy that Separate question: how many of JS APIs depend on things like dynamic assembly loading and dynamic code execution (interpreter)? These would not work on NAOT (naturally).
How does the marshalling of tasks to promises work, in terms of native signatures and data flow? For example, how would the underlying |
We let emscripten generate ES6 module and link in mono functions. Then consume the emscripten module from our (two) modules. Running
Emscripten JavaScript is unminified, our API generated from Typescript is minified in Release mode, but we have source maps pointing to github.
I going to say "none", at least in the core paths. We have an API for lazy assembly loading, but it's not used by default. We lookup some C# functions with mono reflection, but I was able to bypass that with wasm exports for far.
An example of Roslyn generated wrapper for [global::System.Diagnostics.DebuggerNonUserCode]
[global::System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute(EntryPoint = "_5B_BrowserConsoleApp_5D_Xyz_Interop_2F_MyClass_3A_GreetToJS")]
internal static unsafe void __Wrapper_GreetToJS(global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument* __arguments_buffer)
{
string name;
ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __arg_exception = ref __arguments_buffer[0];
ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __arg_return = ref __arguments_buffer[1];
global::System.Threading.Tasks.Task<string> __retVal = default;
// Setup - Perform required setup.
ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __name_native__js_arg = ref __arguments_buffer[2];
// Unmarshal - Convert native data to managed data.
__name_native__js_arg.ToManaged(out name);
try
{
__retVal = Xyz.Interop.MyClass.GreetToJS(name);
__arg_return.ToJS(__retVal, static (ref global::System.Runtime.InteropServices.JavaScript.JSMarshalerArgument __task_result_arg, string __task_result) =>
{
__task_result_arg.ToJS(__task_result);
});
}
catch (global::System.Exception ex)
{
__arg_exception.ToJS(ex);
}
} |
Thanks you, I think I am seeing the bigger picture now. Some more questions. It is clear how the support for scenarios where JS is the root of execution work. What about cases where WASM is the root of execution, i. e. (Currently, NAOT-LLVM depends on incredibly little JS, basically one For the async main, I see it will root a lot of task infrastructure, so definitely not something to be done always (i. e. only when the user requests such by writing it). Is the user required to attribute async main with |
Sorry, I probably don't follow. Do you mean the WASI scenario? All this effort is meant for browser/nodejs/v8 target.
Definitely. If there isn't an async main, nothing should be rooted.
Yeah, adding |
The "shared library" scenario is like in https://devblogs.microsoft.com/dotnet/use-net-7-from-any-javascript-app-in-net-7/. More interesting is the "static library" case. As you know, one can build static WASM libraries and distribute them to be linked with something later. With
Pretty sure source generators will see the original source code for top-level statements. I am not sure how to make that work, even - you cannot call an unspeakable |
I see, thanks! With dotnet.js you would need to (currently) distribute 3 By linking with something later, do you mean at WebAssembly level or at JavaScript level? If later is the case, you would also want to hide the nature that your library is implemented with dotnet under the hood and would probably want to wrap the API surface. I had a demo of that last year with integration to react https://github.com/maraf/dotnet-wasm-react. |
Right, linking it statically, as in Connected to this is the question of whether the JS is set up to work correctly if it is not the root of execution, i. e. if the first thing under our control called is a UCO method from |
I have never tried that. How does it work with plain emscripten? Who is responsible for downloading and instantiating the wasm module? |
Static linking merges all of the input files together. You can see it in action if you look at some binlogs from the runtime (Mono or NAOT-LLVM) build, there are a bunch of Emscripten then has a bespoke system for making JS "look like C" in this process: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#implement-a-c-api-in-javascript. |
I see. So you can link together multiple emscripten-based libraries and pass |
Yes.
I see; that is more or less what I expected. To tie things a bit to the earlier points, there are three different subsystems in a runtime setup:
What is necessary for the library scenario is a very clear separation between |
Just to chime in on
Apart from exposing the async Main, I think we would need some additional tweaks (most probably in ILC codegen) in order to properly execute the startup sequence. Initially NativeAOT supported two startup scenarios:
The 3. new scenario was added for integration with Xamarin and support for iOS platforms where we had to introduce a new mode
Based on your discussion and proposed requirements around The startup sequence is generated at: https://github.com/dotnet/runtime/blob/cbc501ca196371572c38f8d12a66969864d99c08/src/coreclr/tools/aot/ILCompiler.Compiler/IL/Stubs/StartupCode/StartupCodeMainMethod.cs#L64 |
If we generate interop wrapper with UCO for the user defined Main method (either by explicitly marking it as |
Yes. These are already separated in Mono case
What we never actualy tried is using |
It is true if you create a There are two ways to think about this: either we have a library with a known entrypoint, or we have an executable with an unusual main. What design does the latter view lead toAsync main scenario is different from the sync main one in only two ways:
So, in the usual scenario we have:
In the async main scenario:
For simplicity,
For the the former, it requires a bit more thinking if it can be implemented transparently, given that we don't know if the main will be async or sync when invoking ILC. Export the UCO stub as |
This is roughly what WasmNativeFile does, it works in both AOT and iterpreted mode and like AOT even requires the workload so that we have the wasm-ld executable etc. It is also the machinery we use to link in/out optional runtime features as part of WasmBuildNative and how the bindings for things like skiasharp and sqlite work. This is easier to see in the wasi build than the browser target which is more complicated due to some product requirements. We currently expect to drive that build to the final link modulo wasm imports and exports but that could be altered. |
The library mode work that was done in the mono aot compiler for mono's library mode (what @ivanpovazan referred to) is available for us to use in AOT mode which includes the startup stub to init the runtime enough to allow calling into ICO entry points without calling into managed main and I think it will largely work out of the box if we add support to the bundler to handle it. We would need a little more work to generate stubs and/or fix the fallback implementation for the interpreter without the using AOT compiler because that IL is generated from unmanaged code right now. |
I tried to point out that there is already
So if we would just try to annotate the user's async Main with |
@maraf hi! I am interested in trying NativeAOT LLVM with Avalonia, and was wondering if you have any minimal standalone project or doc somewhere, from which I can start? Asking because this "compiling.md" page doesn't seem to include anything about I found https://github.com/maraf/MinimalDotNetWasmNativeAOT/tree/dotnetjs/DotnetJsHack repo. Are these hacks with copying runtime files still required? From this list of WIP features, I don't see any as a blocker for us. |
Hey! The If you want to target 9.0.0-* of NativeAOT-LLVM packages you need some .NET 9 SDK preview. Having later one is generally better |
@maraf it seems ReleaseJSOwnedObjectByGCHandle isn't yet supported.
|
browser-wasm
Interop.Runtime.cs
as wasm imports (instead of icalls)__Register_
as wasm exportmanaged_main
is also responsible for storingargs
andtearingdown
the runtimeEnvironment.GetCommandLineArgs()
/aotsdk
instead of/framework
Interop.Runtime.NativeAOT.cs
and other switches in libs (libs can't use switch based on runtime flavor)Smoke test with currently supported feature set
https://github.com/dotnet/runtimelab/blob/feature/NativeAOT-LLVM/src/tests/nativeaot/SmokeTests/DotnetJs
Original prototype
https://github.com/maraf/MinimalDotNetWasmNativeAOT/tree/dotnetjs/DotnetJsHack
The text was updated successfully, but these errors were encountered: