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

[wasm] Marshaling bool within struct with [MarshalAs(UnmanagedType.U1)] throws System.InvalidProgramException #107546

Closed
drasticactions opened this issue Sep 9, 2024 · 6 comments
Assignees
Labels
arch-wasm WebAssembly architecture area-Interop-mono os-browser Browser variant of arch-wasm
Milestone

Comments

@drasticactions
Copy link

Description

I am still determining if this is a runtime bug, but since this code works everywhere except Blazor WASM, I have to ask.

I am working on a program with a native library. It includes the following struct:

https://github.com/drasticactions/DA.Whisper/blob/wasm-bug/src/DA.Whisper/NativeMethods.g.cs#L380C5-L391C6

 [StructLayout(LayoutKind.Sequential)]
  public unsafe partial struct whisper_context_params
  {
      [MarshalAs(UnmanagedType.U1)] public bool use_gpu;
      [MarshalAs(UnmanagedType.U1)] public bool flash_attn;
      public int gpu_device;
      [MarshalAs(UnmanagedType.U1)] public bool dtw_token_timestamps;
      public whisper_alignment_heads_preset dtw_aheads_preset;
      public int dtw_n_top;
      public whisper_aheads dtw_aheads;
      public nuint dtw_mem_size;
  }

This can be retrieved by calling NativeMethods.whisper_context_default_params(); to get the default values. I have built the native library on Windows, Mac, and Linux and invoked it in .NET (with and without NativeAOT enabled), and it has worked fine. Nothing is thrown, and I can manipulate the object and send it back though the NativeMethods.

However, when I built Whisper.cpp with Emscripten and ran it in web assembly, any time I called for any struct that included a bool, I would get a System.InvalidProgramException with a blank message.

image

I am unsure how to get better exceptions out of this, this is all I've been able to get. Other functions of accessing the library that don't involve these structs works as expected. Once I saw that it only through this message when I accessed the struct, I changed it to remove the Marshaling and set it to byte

https://github.com/drasticactions/DA.Whisper/blob/wasm-bug/src/DA.Whisper/NativeMethods.g.cs#L367-L378

[StructLayout(LayoutKind.Sequential)]
  public unsafe partial struct whisper_context1
  {
      public byte use_gpu;
      public byte flash_attn;
      public int gpu_device;
      public byte dtw_token_timestamps;
      public whisper_alignment_heads_preset dtw_aheads_preset;
      public int dtw_n_top;
      public whisper_aheads dtw_aheads;
      public nuint dtw_mem_size;
  }

Everything started working! I got the struct, manipulated it, and sent it back through NativeMethods and Whisper loaded it fine.

So I think that means there is a bug with [MarshalAs(UnmanagedType.U1)] and WASM. That should a byte (and indeed, it works on the other platforms I've tried, although maybe that's a mistake too?). I wish to keep my code the same to use the marshal value instead of byte (since it works everywhere else). Is this a bug, or did I make a mistake?

I checked with both net8.0 and net9.0-preview7 and both fail with the same exception.

Reproduction Steps

  • Check out DA.Whisper and use the wasm-bug branch
  • Build the WASM runtime with make wasm (or use my provided runtimes, replace the runtime folder with this
    runtime.zip)
  • Run src\DA.WhisperBlazor

There are two buttons, one which invokes the Marshaled byte struct, the other which does not. Clicking on the First button should go through right, clicking the second should throw an exception.

Expected behavior

I can access Bool values in Native code via [MarshalAs(UnmanagedType.U1)]

Actual behavior

System.InvalidProgramException thrown.

Regression?

No response

Known Workarounds

Don't use [MarshalAs(UnmanagedType.U1)] but use the literal type byte instead.

Configuration

.NET SDK:
 Version:           9.0.100-preview.7.24407.12
 Commit:            d672b8a045
 Workload version:  9.0.100-manifests.2aef0cee
 MSBuild version:   17.12.0-preview-24374-02+48e81c6f1

ランタイム環境:
 OS Name:     Windows
 OS Version:  10.0.26100
 OS Platform: Windows
 RID:         win-x64
 Base Path:   C:\Program Files\dotnet\sdk\9.0.100-preview.7.24407.12\

インストール済みの .NET ワークロード:
新しいマニフェストをインストールするときに loose manifests を使用するように構成されています。
 [android]
   インストール ソース: SDK 9.0.100-preview.7, VS 17.12.35209.166
   マニフェストのバージョン:    35.0.0-preview.7.41/9.0.100-preview.7
   マニフェスト パス:       C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.7\microsoft.net.sdk.android\35.0.0-preview.7.41\WorkloadManifest.json
   インストールの種類:              Msi

 [aspire]
   インストール ソース: SDK 9.0.100-preview.7, VS 17.12.35209.166
   マニフェストのバージョン:    8.2.0/8.0.100
   マニフェスト パス:       C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.aspire\8.2.0\WorkloadManifest.json
   インストールの種類:        FileBased

 [ios]
   インストール ソース: SDK 9.0.100-preview.7, VS 17.12.35209.166
   マニフェストのバージョン:    17.5.9231-net9-p7/9.0.100-preview.7
   マニフェスト パス:       C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.7\microsoft.net.sdk.ios\17.5.9231-net9-p7\WorkloadManifest.json
   インストールの種類:              Msi

 [maccatalyst]
   インストール ソース: SDK 9.0.100-preview.7, VS 17.12.35209.166
   マニフェストのバージョン:    17.5.9231-net9-p7/9.0.100-preview.7
   マニフェスト パス:       C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.7\microsoft.net.sdk.maccatalyst\17.5.9231-net9-p7\WorkloadManifest.json
   インストールの種類:              Msi

 [maui-windows]
   インストール ソース: SDK 9.0.100-preview.7, VS 17.12.35209.166
   マニフェストのバージョン:    9.0.0-preview.7.24407.4/9.0.100-preview.7
   マニフェスト パス:       C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.7\microsoft.net.sdk.maui\9.0.0-preview.7.24407.4\WorkloadManifest.json
   インストールの種類:              Msi

 [wasm-tools]
   インストール ソース: SDK 9.0.100-preview.7, VS 17.12.35209.166
   マニフェストのバージョン:    9.0.0-preview.7.24405.7/9.0.100-preview.7
   マニフェスト パス:       C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.7\microsoft.net.workload.mono.toolchain.current\9.0.0-preview.7.24405.7\WorkloadManifest.json
   インストールの種類:              Msi

 [wasm-tools-net8]
   インストール ソース: SDK 9.0.100-preview.7, VS 17.12.35209.166
   マニフェストのバージョン:    9.0.0-preview.7.24405.7/9.0.100-preview.7
   マニフェスト パス:       C:\Program Files\dotnet\sdk-manifests\9.0.100-preview.7\microsoft.net.workload.mono.toolchain.net8\9.0.0-preview.7.24405.7\WorkloadManifest.json
   インストールの種類:              Msi


Host:
  Version:      9.0.0-preview.7.24405.7
  Architecture: x64
  Commit:       static

.NET SDKs installed:
  9.0.100-preview.7.24407.12 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.App 6.0.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 8.0.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 9.0.0-preview.7.24406.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 6.0.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 8.0.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 9.0.0-preview.7.24405.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 6.0.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 8.0.7 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 9.0.0-preview.7.24405.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
  x86   [C:\Program Files (x86)\dotnet]
    registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
  Not set

global.json file:
  Not found

Learn more:
  https://aka.ms/dotnet/info

Download .NET:
  https://aka.ms/dotnet/download

Other information

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Sep 9, 2024
@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Sep 9, 2024
@lambdageek lambdageek added arch-wasm WebAssembly architecture area-Interop-mono and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Sep 9, 2024
Copy link
Contributor

Tagging subscribers to 'arch-wasm': @lewing
See info in area-owners.md if you want to be subscribed.

@lambdageek
Copy link
Member

/cc @pavelsavara

@pavelsavara
Copy link
Member

cc @kg

@kg
Copy link
Member

kg commented Sep 9, 2024

I believe the root cause here is that bool in .NET is sometimes 4 bytes in size, not 1 byte, so by doing MarshalAs you're requiring pinvoke to make a temporary copy of your struct with a different size and copy all the values into it, performing truncation on the bools as needed. We generally don't support this kind of struct marshaling on WASM, at least not yet.

Also note that bool is not a blittable type according to https://learn.microsoft.com/en-us/dotnet/framework/interop/blittable-and-non-blittable-types. Right now you should only expect blittable structs to marshal properly on the wasm target. We may be able to relax these restrictions in the future. These limitations are due to the extreme complexity of p/invoke and the existing implementations not being directly portable to the WASM target.

As a workaround, consider wrapping the bytes with bool properties. That would maintain a blittable struct (with private byte fields) that has a public interface with bools in it.

@lambdageek
Copy link
Member

lambdageek commented Sep 9, 2024

you might be able to set the DisableRuntimeMarshalingAttribute on the assembly to make a struct containing a bool blittable (the MarshalAsAttribute must not be used then - it will use the underlying type (ie byte) as the marshaling type) #60639 this is assuming all your pinvokes in the assembly are using the source generator [LibraryImport] mechanism instead of the runtime built-in marshaling

@pavelsavara pavelsavara added the os-browser Browser variant of arch-wasm label Sep 10, 2024
@lewing lewing added this to the 10.0.0 milestone Sep 10, 2024
@lewing lewing removed the untriaged New issue has not been triaged by the area owner label Sep 10, 2024
@agocke agocke added this to AppModel Sep 11, 2024
@lewing lewing assigned ilonatommy and unassigned mkhamoyan Sep 26, 2024
@pavelsavara
Copy link
Member

this is by design, right ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
arch-wasm WebAssembly architecture area-Interop-mono os-browser Browser variant of arch-wasm
Projects
Status: No status
Development

No branches or pull requests

7 participants