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

Caller.TryGetMemorySpan #219

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions src/Caller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ namespace Wasmtime
/// </summary>
public readonly ref struct Caller
{
private const int StackallocThreshold = 256;

internal Caller(IntPtr handle)
{
if (handle == IntPtr.Zero)
Expand All @@ -21,6 +23,61 @@ internal Caller(IntPtr handle)
this.store = context.Store;
}

/// <summary>
/// Gets a Span from an exported memory of the caller by the given name.
/// </summary>
/// <param name="name">The name of the exported memory.</param>
/// <param name="address">The zero-based address of the start of the span.</param>
/// <param name="length">The length of the span.</param>
/// <param name="result">The Span of memory (if the function returns true)</param>
/// <returns>Returns true if the exported memory is found or false if a memory of the requested name is not exported.</returns>
/// <remarks>
/// <para>
/// The span may become invalid if the memory grows.
///
/// This may happen if the memory is explicitly requested to grow or
/// grows as a result of WebAssembly execution.
/// </para>
/// <para>
/// Therefore, the returned span should not be used after calling the grow method or
/// after calling into WebAssembly code.
/// </para>
/// <para>
/// Note that WebAssembly always uses little endian as byte order. On platforms
/// that use big endian, you will need to convert numeric values accordingly.
/// </para>
/// </remarks>
public bool TryGetMemorySpan<T>(string name, long address, int length, out Span<T> result)
where T : unmanaged
{
var nameLength = Encoding.UTF8.GetMaxByteCount(name.Length);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems you reverted back to the previous approach of using GetMaxByteCount when doing the force-push, was this deliberate? (Sorry for not catching this earlier).
Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it wasn't! I'm not sure quite how I managed that, I'll put in a PR to fix it now. Thanks for catching it 👍

var nameBytes = nameLength < StackallocThreshold ? stackalloc byte[nameLength] : new byte[nameLength];
nameLength = Encoding.UTF8.GetBytes(name, nameBytes);
nameBytes = nameBytes[..nameLength];

unsafe
{
fixed (byte* ptr = nameBytes)
{
if (!Native.wasmtime_caller_export_get(handle, ptr, (UIntPtr)nameBytes.Length, out var item))
{
result = default;
return false;
}

if (item.kind != ExternKind.Memory)
{
item.Dispose();
result = default;
return false;
}

result = Memory.GetSpan<T>(context, item.of.memory, address, length);
return true;
}
}
}

/// <summary>
/// Gets an exported memory of the caller by the given name.
/// </summary>
Expand Down
14 changes: 10 additions & 4 deletions src/Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,14 @@ public unsafe Span<T> GetSpan<T>(int address)
/// </remarks>
public unsafe Span<T> GetSpan<T>(long address, int length)
where T : unmanaged
{
var span = GetSpan<T>(store.Context, memory, address, length);
GC.KeepAlive(store);
return span;
}

internal static unsafe Span<T> GetSpan<T>(StoreContext context, ExternMemory memory, long address, int length)
where T : unmanaged
{
if (address < 0)
{
Expand All @@ -242,11 +250,9 @@ public unsafe Span<T> GetSpan<T>(long address, int length)
throw new ArgumentOutOfRangeException(nameof(length));
}

var context = store.Context;
var data = Native.wasmtime_memory_data(context.handle, this.memory);
GC.KeepAlive(store);
var data = Native.wasmtime_memory_data(context.handle, memory);

var memoryLength = this.GetLength();
var memoryLength = checked((long)Native.wasmtime_memory_data_size(context.handle, memory));

// Note: A Span<T> can span more than 2 GiB bytes if sizeof(T) > 1.
long byteLength = (long)length * sizeof(T);
Expand Down
28 changes: 27 additions & 1 deletion tests/CallerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,33 @@ public void ItCanReadAndWriteMemoryFromCaller()
var callback = instance.GetFunction("call_callback")!;

// Write a value into memory
var memory = instance.GetMemory("memory");
var memory = instance.GetMemory("memory")!;
memory.WriteByte(10, 20);

// Callback checks that value and writes another
callback.Invoke();

// Read value back from memory, checking it has been modified
memory.ReadByte(10).Should().Be(21);
}

[Fact]
public void ItCanReadAndWriteMemorySpanFromCaller()
{
Linker.DefineFunction("env", "callback", (Caller c) =>
{
c.TryGetMemorySpan<byte>("memory", 10, 1, out var span).Should().BeTrue();

span.Length.Should().Be(1);
span[0].Should().Be(20);
span[0] = 21;
});

var instance = Linker.Instantiate(Store, Fixture.Module);
var callback = instance.GetFunction("call_callback")!;

// Write a value into memory
var memory = instance.GetMemory("memory")!;
memory.WriteByte(10, 20);

// Callback checks that value and writes another
Expand Down