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.42k stars 198 forks source link

Memory access out of bounds when running the Web Assembly native library sample #2258

Closed marc-mueller closed 1 year ago

marc-mueller commented 1 year ago

Hi

I tried to compile and run the sample for Web Assembly native library mentioned here.

namespace SampleLibrary;
public class SampleLib
{
    [System.Runtime.InteropServices.UnmanagedCallersOnly(EntryPoint = "Answer")]
    public static int Answer()
    {
        return 42;
    }
}

I'm compile the library with the following command:

dotnet publish /p:NativeLib=Static /p:SelfContained=true -r browser-wasm -c Debug /p:TargetArchitecture=wasm /p:PlatformTarget=AnyCPU /p:MSBuildEnableWorkloadResolver=false /p:EmitLegacyAssetsFileItems=true /p:EmccExtraArgs="-s EXPORTED_FUNCTIONS=_Answer -s EXPORTED_RUNTIME_METHODS=cwrap" --self-contained

When I run the wasm in the browser (by opening the provided emscripten html file), I get the following error when I try to call the Answer function:

Module._Answer()
SampleLibrary.wasm:0xc54 Uncaught RuntimeError: memory access out of bounds
    at ReaderWriterLock::WriteHolder::WriteHolder(ReaderWriterLock*, bool) (SampleLibrary.wasm:0xc54)
    at ThreadStore::AttachCurrentThread(bool) (SampleLibrary.wasm:0x1bc8)
    at ThreadStore::AttachCurrentThread() (SampleLibrary.wasm:0x1bff)
    at Thread::ReversePInvokeAttachOrTrapThread(ReversePInvokeFrame*) (SampleLibrary.wasm:0x1048)
    at RhpReversePInvokeAttachOrTrapThread2 (SampleLibrary.wasm:0x10d2)
    at RhpReversePInvoke (SampleLibrary.wasm:0x1143)
    at SampleLibrary_SampleLibrary_SampleLib__Answer (SampleLibrary.wasm:0x1d35)
    at Object._Answer (SampleLibrary.js:719:22)
    at <anonymous>:1:8
$ReaderWriterLock::WriteHolder::WriteHolder(ReaderWriterLock*, bool) @ SampleLibrary.wasm:0xc54
$ThreadStore::AttachCurrentThread(bool) @ SampleLibrary.wasm:0x1bc8
$ThreadStore::AttachCurrentThread() @ SampleLibrary.wasm:0x1bff
$Thread::ReversePInvokeAttachOrTrapThread(ReversePInvokeFrame*) @ SampleLibrary.wasm:0x1048
$RhpReversePInvokeAttachOrTrapThread2 @ SampleLibrary.wasm:0x10d2
$RhpReversePInvoke @ SampleLibrary.wasm:0x1143
$SampleLibrary_SampleLibrary_SampleLib__Answer @ SampleLibrary.wasm:0x1d35
(anonymous) @ SampleLibrary.js:719
(anonymous) @ VM57:1
Module.asm.Answer()
SampleLibrary.wasm:0xc54 Uncaught RuntimeError: memory access out of bounds
    at ReaderWriterLock::WriteHolder::WriteHolder(ReaderWriterLock*, bool) (SampleLibrary.wasm:0xc54)
    at ThreadStore::AttachCurrentThread(bool) (SampleLibrary.wasm:0x1bc8)
    at ThreadStore::AttachCurrentThread() (SampleLibrary.wasm:0x1bff)
    at Thread::ReversePInvokeAttachOrTrapThread(ReversePInvokeFrame*) (SampleLibrary.wasm:0x1048)
    at RhpReversePInvokeAttachOrTrapThread2 (SampleLibrary.wasm:0x10d2)
    at RhpReversePInvoke (SampleLibrary.wasm:0x1143)
    at SampleLibrary_SampleLibrary_SampleLib__Answer (SampleLibrary.wasm:0x1d35)
    at <anonymous>:1:12

What am I doing wrong? Thanks for your feedback!

yowl commented 1 year ago

Hi Marc,

Do you have a call to

const corertInit = Module.cwrap('NativeAOT_StaticInitialization', 'number', []);
corertInit();

before the call to your function?

marc-mueller commented 1 year ago

No I don't have this call. I was assuming that the js file generated by Emscripten does it. As far as I can see, NativeAOT_StaticInitialization is not exported and cannot be called therefore. Did I miss something in the project setup or compiler configuration?

yowl commented 1 year ago

Right, you need something like -s EXPORTED_FUNCTIONS=_Answer%2C_NativeAOT_StaticInitialization

yowl commented 1 year ago

This is old, but might still be correct: https://stackoverflow.com/questions/70474778/compiling-c-sharp-project-to-webassembly

marc-mueller commented 1 year ago

@yowl Thank you very much for your fast responses. That did the trick and it is now working as expected. The docs did not state that and I wasn't able to figure it out quickly with going through the docs. Maybe we should place a hint there.

I'll now try to compile a complex library with this approach.

yowl commented 1 year ago

Yes, thanks we will update the docs and the build integration so that is automatic.

marc-mueller commented 1 year ago

@yowl : Do you know if there are sample projects somewhere available which goes beyond a simple hello world? I'm currently trying to implement a proof of concept to share business logic by wasm modules from many different languages / technologies. C++ and emscripten worked really well but the same library in C# with NativeAOT-LLVM gives me headaches currently. It would be nice to see some guidance and samples.

I really like the approach of NativeAOT-LLVM and the possibility to compile wasm modules. I think this has a huge potential. At least for now it is just hard to get a starting point. Thanks!

yowl commented 1 year ago

HI, for joining wasm modules together, you really want the Web assembly Component ABI (https://github.com/WebAssembly/component-model/blob/main/design/high-level/Goals.md) Unfortunately it seems to be taking a long time for the standard to be completed. Plus for it to work well with c# we'll need a wit-bindgen backend for c# (or .Net in general) I understand that another group have one of those, and it may be open sourced soon, let's hope so, and we can adapt if for NAOT-LLVM if necessary. That's definitely something I'd like to do, or see done.

Right now you have to write your own plumbing code, although you can borrow ideas from Blazor or Uno, but its not a 5 minute job. Emscripten bindings for C++ don't translate to c#, at least for classes or object lifetime management. Simple functions can be exported with UnmanagedCallersOnly, but you probably already know that.

For compiler warnings, they are designed to be readable, but obviously they can seem cryptic at time, feel free to post questions about warning that you don't understand. For runtime issues, the Chrome debugger (or Edge), works quite well, you need the C++ Dwarf extension, then you can single step the c# (more or less) and see variables, although not all types will render well, strings for example.