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

[NativeAOT LLVM] Enable JS interop #2440

Merged
merged 32 commits into from
Dec 19, 2023
Merged

Conversation

maraf
Copy link
Member

@maraf maraf commented Nov 24, 2023

  • The goal is not to enable all JS import/export scenarios, but start with some simple ones
  • Updated smoke tests
  • The mono_wasm_bind_js_function and mono_wasm_bind_cs_function are duplicated for NativeAOT case
  • Due to DisableImplicitFrameworkReferences=true in tests, generators are added as explicit references

Contributes to #2434

@maraf maraf added the area-NativeAOT-LLVM LLVM generation for Native AOT compilation (including Web Assembly) label Nov 24, 2023
@maraf maraf self-assigned this Nov 24, 2023
@elringus
Copy link

elringus commented Dec 8, 2023

@maraf Sorry if I'm asking something irrelevant here, but do I understand correctly that there are plans to move WASM target from Mono to NativeAOT/LLVM? If that's the case, will the existing "high-level" JS interop API (the JSImport/Export stuff from System.Runtime.InteropServices.JavaScript) still work with the new backend or it'll be something different?

@maraf
Copy link
Member Author

maraf commented Dec 9, 2023

@maraf Sorry if I'm asking something irrelevant here, but do I understand correctly that there are plans to move WASM target from Mono to NativeAOT/LLVM?

There is no such plan at the moment. Anyway NativeAOT/LLVM has come a long way and we are looking at comparison

If that's the case, will the existing "high-level" JS interop API (the JSImport/Export stuff from System.Runtime.InteropServices.JavaScript) still work with the new backend or it'll be something different?

The goal of the #2434 is to have the same/close as possible API for both Mono and NativeAOT/LLVM in the context of the browser, so that is possible try to wasmbrowser based apps on NativeAOT/LLVM.

@maraf maraf changed the title [NativeAOT LLVM] JS Interop [NativeAOT LLVM] Enable JS interop Dec 18, 2023
@maraf maraf requested review from a team and pavelsavara December 18, 2023 14:23
@maraf maraf marked this pull request as ready for review December 18, 2023 14:24
@@ -22,7 +22,8 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'browser'">
<Compile Include="$(CommonPath)Interop\Browser\Interop.Runtime.cs" Link="System\Runtime\InteropServices\JavaScript\Interop\Interop.Runtime.cs" />
<Compile Include="$(CommonPath)Interop\Browser\Interop.Runtime.cs" Link="System\Runtime\InteropServices\JavaScript\Interop\Interop.Runtime.cs" Condition="'$(RuntimeFlavor)' != 'CoreCLR'" />
Copy link
Member

Choose a reason for hiding this comment

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

libraries build system is not setup to build different binaries per runtime flavor. We have all libraries (except CoreLib) 100% shared between all runtimes flavors.

This change is ok as a quick hack for runtimelab, but it would not be upstreamable. Add a comment about it?

Copy link
Member

Choose a reason for hiding this comment

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

(The proper way to do this would be to come up with a scheme that works for all runtime flavors and/or move any runtime specific parts into CoreLib.)

Choose a reason for hiding this comment

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

I would expect this can be solved similar to Linq.Expressions, with an internal feature switch (always off for Mono, always on for NativeAOT).

Comment on lines 26 to 34
internal static partial int Math(int a, int b, int c);

[JSExport]
internal static int Square(int x)
{
Console.WriteLine($"Computing square of {x}");
return x^2;
}

Choose a reason for hiding this comment

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

Could you write more tests? For JSExport, you can take inspiration (aka copy code) from the SharedLibrary test - the main items of interest are EH and GC exercising methods.

For both JSExport and JSImport, it would be good to have some number of methods that exercise the actual marshaling (strings, arrays, tasks, delegates, basically, challening cases from the table in https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/import-export-interop?view=aspnetcore-8.0). Perhaps these tests are already written somewhere and too could be copied...

Copy link
Member

Choose a reason for hiding this comment

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

Choose a reason for hiding this comment

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

Right. We don't need super-comprehensive testing here, but something that would fail if some basic aspect of the system gets broken.

Copy link
Member Author

Choose a reason for hiding this comment

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

I would rather use standard JS interop tests when we get there. I don't see value in duplicating them here. This is just a smoke test.

Also, a lot of JS interop scenarios don't work yet and are out of scope for this PR

Choose a reason for hiding this comment

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

Also, a lot of JS interop scenarios don't work yet and are out of scope for this PR

Right. I would expect that as they are enabled, they get added to this test. Could you add a few simple ones that already work then (like I would expect strings to work)?

One of the reasons why it is valuable to have this testing is that even when you enable the full libraries test suite, it will take > minute to compile, while this test will take much less. Libraries tests are not suitable for inner loop.

Copy link
Member Author

Choose a reason for hiding this comment

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

Strings for example don't work

Choose a reason for hiding this comment

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

Interesting that they don't. Well, let's leave this be until they do.

Copy link
Member Author

Choose a reason for hiding this comment

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

We need dotnet/runtime#95180 for strings

Copy link
Contributor

Choose a reason for hiding this comment

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

We need dotnet/runtime#95180 for strings

Not in time for this PR, but I will start the next merge soon.

Copy link
Member Author

Choose a reason for hiding this comment

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

It's ok, no rush on that one, I have other things to do :)

Comment on lines 37 to 41
BindJSFunction(function_name, function_name.Length, module_name, module_name.Length, signature, out bound_function_js_handle, out is_exception);
if (is_exception != 0)
result = "Runtime.BindJSFunction failed";
else
result = "";

Choose a reason for hiding this comment

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

The original Mono functions were internal calls and would run in cooperative GC mode (i. e. as if they were managed code). DllImport is PInvoke, which puts the code into preemptive mode. I wonder if this will cause problems.

(Code in preemptive mode moslty cannot touch managed state).

Copy link
Member

Choose a reason for hiding this comment

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

what's the plan for running GC for NAOT on browser ? What would trigger it and what would schedule finalizers ?

Choose a reason for hiding this comment

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

what's the plan for running GC for NAOT on browser ? What would trigger it and what would schedule finalizers ?

Same as on other platforms - you GC whenever GC decides its time to GC. Practically, this will happens on allocations, or explicit GC.Collects.

Finalization is a little bit NYI right now (#2240), but it could follow the same plan (nice because WASI would have to do that), or be hooked into the event loop.

Copy link
Member

Choose a reason for hiding this comment

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

I guess we need to make decision about yielding to browser event loop.
Big part of JS interop is moot without it (promises, network events, UI).

Choose a reason for hiding this comment

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

Well, I would expect the yielding to happen naturally in async code, as you hit awaits and such. GC shouldn't really play much into it? We would of course have the Browser-specific thread pool implementation.

src/mono/wasm/runtime/invoke-cs.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/invoke-cs.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/invoke-cs.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/invoke-cs.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/invoke-cs.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/invoke-cs.ts Outdated Show resolved Hide resolved
Copy link

@SingleAccretion SingleAccretion left a comment

Choose a reason for hiding this comment

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

Just a few comments, we are quite close. Thank you for collapsing back TS changes!

Copy link

@SingleAccretion SingleAccretion left a comment

Choose a reason for hiding this comment

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

LGTM modulo two questions and assuming green CI. Thank you!

I would also ask to add a note about the JSInterop library build workaround to #2434.

src/mono/wasm/runtime/dotnet.d.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/types/emscripten.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/invoke-cs.ts Outdated Show resolved Hide resolved
@@ -60,6 +61,15 @@ export function mono_wasm_new_root_buffer_from_pointer(offset: VoidPtr, capacity
* Releasing this root will not de-allocate the root space. You still need to call .release().
*/
export function mono_wasm_new_external_root<T extends MonoObject>(address: VoidPtr | MonoObjectRef): WasmRoot<T> {
if (NativeAOT) {
Copy link
Member

Choose a reason for hiding this comment

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

Since we don't need GC roots for MonoString* and we will delete legacy interop soon, I hope that we could figure out way how to get rid of all roots too (on main). That would make your life easier.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm looking forward to it

@maraf maraf merged commit 3d61458 into dotnet:feature/NativeAOT-LLVM Dec 19, 2023
11 checks passed
@maraf maraf deleted the jsinterop branch December 19, 2023 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-NativeAOT-LLVM LLVM generation for Native AOT compilation (including Web Assembly)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants