dotnet / dotnet/runtime

[API Proposal]: JavaScript marshalling support for Memory<>, ReadOnlyMemory<> and ReadOnlySpan<>

Open
#125,567 2 comments 0 reactions 1 assignee Claimed by @pavelsavara View on GitHub
api-suggestion arch-wasm area-System.Runtime.InteropServices.JavaScript os-browser
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Background and motivation

Currently the dotnet WebAssembly JavaScript interop (`JSImport`/`JSExport`) supports `ArraySegment` and `Span` as zero-copy views over buffers exchanged with JS. Of those two, `ArraySegment` references can be used beyond the immediate call stack. `Memory` is the newer, generally preferred API for this purpose, so this proposal adds `Memory` support as a more general replacement for what `ArraySegment` does today. The proposal also includes `ReadOnlyMemory` and `ReadOnlySpan` to let developers control whether exposed Wasm memory is writable.

The `Span` and `ArraySegment` part of this API currently supports: `byte`, `int`, `float` and `double`. `Memory`, `ReadOnlyMemory` and `ReadOnlySpan` should support the same element types.

See https://github.com/dotnet/runtime/discussions/97268 and https://github.com/dotnet/runtime/issues/97381.

### API Proposal

```diff
namespace System.Runtime.InteropServices.JavaScript;

[Versioning.SupportedOSPlatformAttribute("browser")]
[CLSCompliant(false)]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public struct JSMarshalerArgument
{
....
public void ToManaged(out Span value) { throw null; }
public void ToJS(Span value) { throw null; }
+ public void ToManaged(out ReadOnlySpan value) { throw null; }
+ public void ToJS(ReadOnlySpan value) { throw null; }
public void ToManaged(out ArraySegment value) { throw null; }
public void ToJS(ArraySegment value) { throw null; }
+ public void ToManaged(out Memory value) { throw null; }
+ public void ToJS(Memory value) { throw null; }
+ public void ToManaged(out ReadOnlyMemory value) { throw null; }
+ public void ToJS(ReadOnlyMemory value) { throw null; }
....
public void ToManaged(out Span value) { throw null; }
public void ToJS(Span value) { throw null; }
+ public void ToManaged(out ReadOnlySpan value) { throw null; }
+ public void ToJS(ReadOnlySpan value) { throw null; }
public void ToManaged(out ArraySegment value) { throw null; }
public void ToJS(ArraySegment value) { throw null; }
+ public void ToManaged(out Memory value) { throw null; }
+ public void ToJS(Memory value) { throw null; }
+ public void ToManaged(out ReadOnlyMemory value) { throw null; }
+ public void ToJS(ReadOnlyMemory value) { throw null; }
....
public void ToManaged(out Span value) { throw null; }
public void ToJS(Span value) { throw null; }
+ public void ToManaged(out ReadOnlySpan value) { throw null; }
+ public void ToJS(ReadOnlySpan value) { throw null; }
public void ToManaged(out ArraySegment value) { throw null; }
public void ToJS(ArraySegment value) { throw null; }
+ public void ToManaged(out Memory value) { throw null; }
+ public void ToJS(Memory value) { throw null; }
+ public void ToManaged(out ReadOnlyMemory value) { throw null; }
+ public void ToJS(ReadOnlyMemory value) { throw null; }
....
public void ToManaged(out Span value) { throw null; }
public void ToJS(Span value) { throw null; }
+ public void ToManaged(out ReadOnlySpan value) { throw null; }
+ public void ToJS(ReadOnlySpan value) { throw null; }
public void ToManaged(out ArraySegment value) { throw null; }
public void ToJS(ArraySegment value) { throw null; }
+ public void ToManaged(out Memory value) { throw null; }
+ public void ToJS(Memory value) { throw null; }
+ public void ToManaged(out ReadOnlyMemory value) { throw null; }
+ public void ToJS(ReadOnlyMemory value) { throw null; }
....
}
```

### API Usage

This is **API for code generated by Roslyn** analyzer when users use `[JSImport]` or `[JSExport]`.
It's not human facing API, but it needs to be public API because Roslyn generated code is consuming only visible APIs.

The generated code looks similar to this, just with `float` data type.
https://github.com/dotnet/runtime/blob/cf05e728871742f5c5483db2f3efea537ff17328/src/libraries/System.Runtime.InteropServices.JavaScript/tests/JSImportGenerator.UnitTest/Compiles.cs#L210-L227

### User facing API

This doesn't change because this is all user code. The new types just become available to be used.

```csharp
[JSImport("echo", "JavaScriptTestHelper")]
static partial Memory Echo(Memory value);

[JSExport]
internal static Memory Round(Memory value)
{
return value;
}

[JSImport("echo3", "JavaScriptTestHelper")]
static partial ReadOnlyMemory Echo3(ReadOnlyMemory value);

[JSExport]
static ReadOnlySpan EchoSpan(ReadOnlySpan value)
{
return value;
}

```

### Alternative Designs

_No response_

### Risks

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.