-
Notifications
You must be signed in to change notification settings - Fork 199
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
c#: Use span and memory apis for primitive type parameters on imports #1138
c#: Use span and memory apis for primitive type parameters on imports #1138
Conversation
45b1902
to
f1b9b92
Compare
Signed-off-by: James Sturtevant <[email protected]>
f1b9b92
to
aa25aad
Compare
Streams also support
Maybe for some things we could generate both ? |
This is for the synchronous api though? I was thinking since we don't really support synchronous API's and we have p3 async coming down the line we might now be able to use
The issue I ran into was when list is used in a variant. I was initially going to generate both but then this seemed like an option. Reading up on Memory it sounds like it makes small heap based allocations for every memory held so this might not be the right option for a default. I will look to see if I can only generate this in the case of parameters and not for Variant based versions. I tried once but have a much better understanding of how it all comes together now. |
I've pushed a version that users Spans for any lists of primitive types and uses I think we will want/need to generate a second function that takes memory as well. The write use case is in the Stream API's WriteAsync method that takes |
bd38027
to
1bf7c22
Compare
1bf7c22
to
26b9360
Compare
Signed-off-by: James Sturtevant <[email protected]>
26b9360
to
2241667
Compare
Signed-off-by: James Sturtevant <[email protected]>
Signed-off-by: James Sturtevant <[email protected]>
You are right, sorry. We can't have sync/blocking stream APIs until we have multi-threading. (Unless we are happy to block on the stream operation, blocking also other async tasks in the ST thread-pool. And I have strong opinion against that.) |
I've added additional functions so we can support Span and memory for arrays of primitive types like u8, etc. |
Signed-off-by: James Sturtevant <[email protected]>
We still generate public static unsafe void EmptyListParam(byte[] a)
{
var cleanups = new List<Action>();
var gcHandle = GCHandle.Alloc(a, GCHandleType.Pinned);
var listPtr = gcHandle.AddrOfPinnedObject();
cleanups.Add(()=> gcHandle.Free());
EmptyListParamWasmInterop.wasmImportEmptyListParam((nint)listPtr, (a).Length);
foreach (var cleanup in cleanups)
{
cleanup();
}
} But we could use public static unsafe void EmptyListParam(byte[] a)
{
fixed (byte* aPtr = a)
{
EmptyListParamWasmInterop.wasmImportEmptyListParam((nint)aPtr, (a).Length);
}
} |
I also looked at public static unsafe byte[] EmptyListResult()
{
var retArea = stackalloc uint[2+1];
var ptr = ((int)retArea) + (4 - 1) & -4;
EmptyListResultWasmInterop.wasmImportEmptyListResult(ptr);
var array = new byte[BitConverter.ToInt32(new Span<byte>((void*)(ptr + 4), 4))];
new Span<byte>((void*)(BitConverter.ToInt32(new Span<byte>((void*)(ptr + 0), 4))), BitConverter.ToInt32(new Span<byte>((void*)(ptr + 4), 4))).CopyTo(new Span<byte>(array));
return array;
} And I think it could be slightly easier to read like this public static unsafe byte[] EmptyListResult()
{
uint* retArea = stackalloc uint[2 + 1];
var alignedPtr = ((int)retArea) + (4 - 1) & -4;
EmptyListResultWasmInterop.wasmImportEmptyListResult(alignedPtr);
var start = (void*)Marshal.ReadIntPtr(alignedPtr);
var count = Marshal.ReadInt32(alignedPtr + 4);
var array = new byte[count];
Unsafe.Copy(ref array, start);
return array;
} I have not tested this, tho |
For public static unsafe void ListParam2(string a)
{
var cleanups = new List<Action>();
var utf8Bytes = Encoding.UTF8.GetBytes(a);
var length = utf8Bytes.Length;
var gcHandle = GCHandle.Alloc(utf8Bytes, GCHandleType.Pinned);
var interopString = gcHandle.AddrOfPinnedObject();
cleanups.Add(() => gcHandle.Free());
ListParam2WasmInterop.wasmImportListParam2(interopString.ToInt32(), length);
foreach (var cleanup in cleanups)
{
cleanup();
}
} Ideally, we could tell the host that we use UTF16 and let the host to handle the re-encoding if necessary. Note that public static unsafe void ListParam2(string a)
{
fixed(char* aPtr = a)
{
Program.wasmImportListParam2((nint)aPtr, a.Length);
}
} if we need to stick to UTF8 public static unsafe void ListParam2(string a)
{
var bytes = Encoding.UTF8.GetBytes(a);
fixed (byte* aPtr = bytes)
{
Program.wasmImportListParam2((nint)aPtr, bytes.Length);
}
} |
public static unsafe nint wasmExportEmptyStringResult() {
string ret;
ret = TestImpl.EmptyStringResult();
var ptr = InteropReturnArea.returnArea.AddressOfReturnArea();
var utf8Bytes = Encoding.UTF8.GetBytes(ret);
var length = utf8Bytes.Length;
var gcHandle = GCHandle.Alloc(utf8Bytes, GCHandleType.Pinned);
var interopString = gcHandle.AddrOfPinnedObject();
BitConverter.TryWriteBytes(new Span<byte>((void*)(ptr + 4), 4), length);
BitConverter.TryWriteBytes(new Span<byte>((void*)(ptr + 0), 4), interopString.ToInt32());
return ptr;
} could become public static unsafe nint wasmExportEmptyStringResult()
{
string ret;
ret = TestImpl.EmptyStringResult();
IntPtr ptr = InteropReturnArea.returnArea.AddressOfReturnArea();
var len = Encoding.UTF8.GetByteCount(ret);
var buffer = Marshal.AllocHGlobal(len);
var span = new Span<byte>((void*)buffer, len);
Encoding.UTF8.GetBytes(ret, span);
Marshal.WriteIntPtr(ptr, buffer);
Marshal.WriteInt32(ptr+4, len);
return ptr;
} |
In the exports, we generate public interface ITest {
static abstract void EmptyListParam(byte[] a);
} but we probably generate public interface ITest {
static abstract void EmptyListParam(ReadOnlySpan<byte> a)
} because we don't expect the callee to modify that memory space. This would allow us to pass the host buffer, instead of making a managed copy on the heap (and GC it later). Note they could always make copy themself. public static void EmptyListParam(ReadOnlySpan<byte> a) {
byte[] copy = a.ToArray();
} |
Add it to places to clean up in #1150
Added to clean up in #1150
We can see if we can clean this up a bit, particularly with naming of variables, but some of the structure of the code comes from the way the base library we in the generator calls our generation code.
This pr was focused on Imports (I've updated the PR to include this in the title), but lets create an issue. #1151 |
Thanks for all the feedback! I'm learning a bunch. I've opened issues to track most of it. If I missed something lets get it in an issue. For this PR, does the general direction of generating additional |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all we got so far in this PR is in the right direction
Fixes: #1080 and follow up to #1122.
Putting in draft for now
since it has parts of #1137.This generates both Span and Memory function signatures for and function that has a canonical list type.
Note that no tests have to change since the memory/span API' provide implicit operators that automatically convert Byte Arrays to
Memory
. But I added an additional check to the test.