dotnet / runtimelab

This repo is for experimentation and exploring new ideas that may or may not make it into the main dotnet/runtime repo.
MIT License
1.44k stars 201 forks source link

[NativeAOT-LLVM] DotnetJS JSExport fails, when function was defined in a Browser class library #2626

Open maxkatz6 opened 5 months ago

maxkatz6 commented 5 months ago

Repro:

  1. Create browser class library, that is intended to be reused between Mono and NativeAOT-LLVM. Either .NET 8 or .NET 9.
  2. (Optional) Distribute it on nuget.
  3. Reference this library in NativeAOT-LLVM browser project.
  4. Use getAssemblyExports to execute exported method from JS.

Error message

MONO_WASM: Assert failed: Missing wasm export '_ClassLibrary__GeneratedInitializer__Register_' (for JSExport registration function in assembly 'ClassLibrary.dll') Error: Assert failed: Missing wasm export '_ClassLibrary__GeneratedInitializer__Register_' (for JSExport registration function in assembly 'ClassLibrary.dll')

Repro project: ClassLibrary-2.zip

^ Note, ClassLibrary doesn't use the same runtime nugets, and is compiled with standard JSExport source generator.

maxkatz6 commented 5 months ago

Looks like NativeAOT-LLVM implementation relies on source gen to output [UnmanagedCallersOnly] wrapper. And, I suppose, special msbuild targets to make emscripten see these register-methods.

Is it planned to sync this part with upstream? Or maybe add reflection fallback in NativeAOT-LLVM solution. Otherwise, it doesn't seem possible to reuse class libraries with interop between two browser runtimes.

maxkatz6 commented 5 months ago

Actually, the problem is a bit deeper that I thought at first. Even when class library is compiled with latest NativeAOT-LLVM interop source generators (and I can confirm that ClassLibraryGeneratedInitializerRegister_ is defined), executable project still doesn't see that function in runtime. Could it be some issue with emscripten msbuild targets?

ClassLibrary.zip

maxkatz6 commented 5 months ago

Might be related https://github.com/dotnet/runtimelab/issues/2045

SingleAccretion commented 5 months ago

The native compiler works by selecting a set of "root" entities as the starting point: the entry point, and UCO methods with an EntryPoint from assemblies marked UnmanagedEntryPointsAssembly. So, the fix here is to add:

<ItemGroup>
  <UnmanagedEntryPointsAssembly Include="YourClassLibraryAssemblyName" />
</ItemGroup>

Either in the application, or, for NuGets, in the package .props. This is documented here.

Ideally, this would of course be unnecessary, but it is not clear to me yet what the solution would look like.