diff --git a/src/Caller.cs b/src/Caller.cs index 202c066..4d49c34 100644 --- a/src/Caller.cs +++ b/src/Caller.cs @@ -9,6 +9,8 @@ namespace Wasmtime /// public readonly ref struct Caller { + private const int StackallocThreshold = 256; + internal Caller(IntPtr handle) { if (handle == IntPtr.Zero) @@ -21,6 +23,61 @@ internal Caller(IntPtr handle) this.store = context.Store; } + /// + /// Gets a Span from an exported memory of the caller by the given name. + /// + /// The name of the exported memory. + /// The zero-based address of the start of the span. + /// The length of the span. + /// The Span of memory (if the function returns true) + /// Returns true if the exported memory is found or false if a memory of the requested name is not exported. + /// + /// + /// 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. + /// + /// + /// Therefore, the returned span should not be used after calling the grow method or + /// after calling into WebAssembly code. + /// + /// + /// Note that WebAssembly always uses little endian as byte order. On platforms + /// that use big endian, you will need to convert numeric values accordingly. + /// + /// + public bool TryGetMemorySpan(string name, long address, int length, out Span result) + where T : unmanaged + { + var nameLength = Encoding.UTF8.GetMaxByteCount(name.Length); + 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(context, item.of.memory, address, length); + return true; + } + } + } + /// /// Gets an exported memory of the caller by the given name. /// diff --git a/src/Memory.cs b/src/Memory.cs index c23816c..9a85fb3 100644 --- a/src/Memory.cs +++ b/src/Memory.cs @@ -231,6 +231,14 @@ public unsafe Span GetSpan(int address) /// public unsafe Span GetSpan(long address, int length) where T : unmanaged + { + var span = GetSpan(store.Context, memory, address, length); + GC.KeepAlive(store); + return span; + } + + internal static unsafe Span GetSpan(StoreContext context, ExternMemory memory, long address, int length) + where T : unmanaged { if (address < 0) { @@ -242,11 +250,9 @@ public unsafe Span GetSpan(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 can span more than 2 GiB bytes if sizeof(T) > 1. long byteLength = (long)length * sizeof(T); diff --git a/tests/CallerTests.cs b/tests/CallerTests.cs index 2d2465e..f1a8648 100644 --- a/tests/CallerTests.cs +++ b/tests/CallerTests.cs @@ -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("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