From 5d64c96f95c3be168cee945e1be0e919d1dc7a25 Mon Sep 17 00:00:00 2001 From: kpreisser Date: Sat, 15 Oct 2022 15:20:09 +0200 Subject: [PATCH 1/5] Add new Int64-based Memory APIs to allow to access memories with more than 32767 pages. Fixes #165 --- src/Memory.cs | 424 ++++++++++++++++++++++++++++++--- tests/MemoryAccessTests.cs | 99 ++++++++ tests/Modules/MemoryAccess.wat | 15 ++ tests/Wasmtime.Tests.csproj | 6 + 4 files changed, 505 insertions(+), 39 deletions(-) create mode 100644 tests/MemoryAccessTests.cs create mode 100644 tests/Modules/MemoryAccess.wat diff --git a/src/Memory.cs b/src/Memory.cs index eb7e322a..a2a7dd0c 100644 --- a/src/Memory.cs +++ b/src/Memory.cs @@ -72,10 +72,40 @@ public uint GetSize() return Native.wasmtime_memory_size(store.Context.handle, this.memory); } + /// + /// Gets the current length of the memory, in bytes. + /// + /// Returns the current length of the memory, in bytes. + public long GetLength() + { + return checked((long)(nuint)Native.wasmtime_memory_data_size(store.Context.handle, this.memory)); + } + + /// + /// Returns a pointer to the start of the memory. The length for which the pointer + /// is valid can be retrieved with . + /// + /// + /// The pointer 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 pointer should not be used after calling the grow method or + /// after calling into WebAssembly code. + /// + /// + public unsafe IntPtr GetPointer() + { + var data = Native.wasmtime_memory_data(store.Context.handle, this.memory); + return (nint)data; + } + /// /// Gets the span of the memory. /// /// Returns the span of the memory. + /// The memory has more than 32767 pages. /// /// The span may become invalid if the memory grows. /// @@ -85,12 +115,31 @@ public uint GetSize() /// Therefore, the returned span should not be used after calling the grow method or /// after calling into WebAssembly code. /// - public unsafe Span GetSpan() + [Obsolete("This method will throw an OverflowException if the memory has more than 32767 pages. " + + "Use the " + nameof(GetSpan) + " overload taking an address and a length.")] + public Span GetSpan() { - var context = store.Context; - var data = Native.wasmtime_memory_data(context.handle, this.memory); - var size = Convert.ToInt32(Native.wasmtime_memory_data_size(context.handle, this.memory).ToUInt32()); - return new Span(data, size); + return GetSpan(0, checked((int)GetLength())); + } + + /// + /// Gets a span of a section of the memory. + /// + /// Returns the span of a section of the memory. + /// The zero-based address of the start of the span. + /// The length of the span. + /// + /// 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. + /// + public Span GetSpan(long address, int length) + { + return GetSpan(address, length); } /// @@ -98,6 +147,8 @@ public unsafe Span GetSpan() /// /// The zero-based address of the start of the span. /// Returns the span of the memory. + /// The memory exceeds the byte length that can be + /// represented by a . /// /// The span may become invalid if the memory grows. /// @@ -106,11 +157,58 @@ public unsafe Span GetSpan() /// /// 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 unsafe Span GetSpan(int address) where T : unmanaged { - return MemoryMarshal.Cast(GetSpan()[address..]); + return GetSpan(address, checked((int)((GetLength() - address) / sizeof(T)))); + } + + /// + /// Gets a span of a section of the memory. + /// + /// Returns the span of a section of the memory. + /// The zero-based address of the start of the span. + /// The length of the span. + /// + /// 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 unsafe Span GetSpan(long address, int length) + where T : unmanaged + { + if (address < 0) + { + throw new ArgumentOutOfRangeException(nameof(address)); + } + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + var context = store.Context; + var data = Native.wasmtime_memory_data(context.handle, this.memory); + var memoryLength = this.GetLength(); + + // Note: A Span can span more than 2 GiB bytes if sizeof(T) > 1. + long byteLength = (long)length * sizeof(T); + + if (address > memoryLength - byteLength) + throw new ArgumentException("The specified address and length exceed the Memory's bounds."); + + return new Span((T*)(data + address), length); } /// @@ -119,10 +217,30 @@ public unsafe Span GetSpan(int address) /// Type of the struct to read. Ensure layout in C# is identical to layout in WASM. /// The zero-based address to read from. /// Returns the struct read from memory. + /// + /// 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 T Read(int address) where T : unmanaged { - return GetSpan(address)[0]; + return GetSpan(address, 1)[0]; + } + + /// + /// Read a struct from memory. + /// + /// Type of the struct to read. Ensure layout in C# is identical to layout in WASM. + /// The zero-based address to read from. + /// Returns the struct read from memory. + /// + /// 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 T Read(long address) + where T : unmanaged + { + return GetSpan(address, 1)[0]; } /// @@ -131,10 +249,30 @@ public T Read(int address) /// /// The zero-based address to read from. /// The struct to write. + /// + /// 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 void Write(int address, T value) where T : unmanaged { - GetSpan(address)[0] = value; + GetSpan(address, 1)[0] = value; + } + + /// + /// Write a struct to memory. + /// + /// + /// The zero-based address to read from. + /// The struct to write. + /// + /// 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 void Write(long address, T value) + where T : unmanaged + { + GetSpan(address, 1)[0] = value; } /// @@ -151,7 +289,24 @@ public string ReadString(int address, int length, Encoding? encoding = null) encoding = Encoding.UTF8; } - return encoding.GetString(GetSpan().Slice(address, length)); + return encoding.GetString(GetSpan(address, length)); + } + + /// + /// Reads a string from memory with the specified encoding. + /// + /// The zero-based address to read from. + /// The length of bytes to read. + /// The encoding to use when reading the string; if null, UTF-8 encoding is used. + /// Returns the string read from memory. + public string ReadString(long address, int length, Encoding? encoding = null) + { + if (encoding is null) + { + encoding = Encoding.UTF8; + } + + return encoding.GetString(GetSpan(address, length)); } /// @@ -161,7 +316,36 @@ public string ReadString(int address, int length, Encoding? encoding = null) /// Returns the string read from memory. public string ReadNullTerminatedString(int address) { - var slice = GetSpan().Slice(address); + if (address < 0) + { + throw new ArgumentOutOfRangeException(nameof(address)); + } + + // We can only read a maximum of 2 GiB. + var slice = GetSpan(address, (int)Math.Min(int.MaxValue, GetLength() - address)); + var terminator = slice.IndexOf((byte)0); + if (terminator == -1) + { + throw new InvalidOperationException("string is not null terminated"); + } + + return Encoding.UTF8.GetString(slice.Slice(0, terminator)); + } + + /// + /// Reads a null-terminated UTF-8 string from memory. + /// + /// The zero-based address to read from. + /// Returns the string read from memory. + public string ReadNullTerminatedString(long address) + { + if (address < 0) + { + throw new ArgumentOutOfRangeException(nameof(address)); + } + + // We can only read a maximum of 2 GiB. + var slice = GetSpan(address, (int)Math.Min(int.MaxValue, GetLength() - address)); var terminator = slice.IndexOf((byte)0); if (terminator == -1) { @@ -180,12 +364,37 @@ public string ReadNullTerminatedString(int address) /// Returns the number of bytes written. public int WriteString(int address, string value, Encoding? encoding = null) { + if (address < 0) + { + throw new ArgumentOutOfRangeException(nameof(address)); + } + if (encoding is null) { encoding = Encoding.UTF8; } + return encoding.GetBytes(value, GetSpan(address, (int)Math.Min(int.MaxValue, GetLength() - address))); + } + + /// + /// Writes a string at the given address with the given encoding. + /// + /// The zero-based address to write to. + /// The string to write. + /// The encoding to use when writing the string; if null, UTF-8 encoding is used. + /// Returns the number of bytes written. + public int WriteString(long address, string value, Encoding? encoding = null) + { + if (address < 0) + { + throw new ArgumentOutOfRangeException(nameof(address)); + } - return encoding.GetBytes(value, GetSpan().Slice(address)); + if (encoding is null) + { + encoding = Encoding.UTF8; + } + return encoding.GetBytes(value, GetSpan(address, (int)Math.Min(int.MaxValue, GetLength() - address))); } /// @@ -195,7 +404,17 @@ public int WriteString(int address, string value, Encoding? encoding = null) /// Returns the byte read from memory. public byte ReadByte(int address) { - return GetSpan()[address]; + return GetSpan(address, sizeof(byte))[0]; + } + + /// + /// Reads a byte from memory. + /// + /// The zero-based address to read from. + /// Returns the byte read from memory. + public byte ReadByte(long address) + { + return GetSpan(address, sizeof(byte))[0]; } /// @@ -205,7 +424,17 @@ public byte ReadByte(int address) /// The byte to write. public void WriteByte(int address, byte value) { - GetSpan()[address] = value; + GetSpan(address, sizeof(byte))[0] = value; + } + + /// + /// Writes a byte to memory. + /// + /// The zero-based address to write to. + /// The byte to write. + public void WriteByte(long address, byte value) + { + GetSpan(address, sizeof(byte))[0] = value; } /// @@ -215,7 +444,17 @@ public void WriteByte(int address, byte value) /// Returns the short read from memory. public short ReadInt16(int address) { - return BinaryPrimitives.ReadInt16LittleEndian(GetSpan().Slice(address, 2)); + return BinaryPrimitives.ReadInt16LittleEndian(GetSpan(address, sizeof(short))); + } + + /// + /// Reads a short from memory. + /// + /// The zero-based address to read from. + /// Returns the short read from memory. + public short ReadInt16(long address) + { + return BinaryPrimitives.ReadInt16LittleEndian(GetSpan(address, sizeof(short))); } /// @@ -225,7 +464,17 @@ public short ReadInt16(int address) /// The short to write. public void WriteInt16(int address, short value) { - BinaryPrimitives.WriteInt16LittleEndian(GetSpan().Slice(address, 2), value); + BinaryPrimitives.WriteInt16LittleEndian(GetSpan(address, sizeof(short)), value); + } + + /// + /// Writes a short to memory. + /// + /// The zero-based address to write to. + /// The short to write. + public void WriteInt16(long address, short value) + { + BinaryPrimitives.WriteInt16LittleEndian(GetSpan(address, sizeof(short)), value); } /// @@ -235,7 +484,17 @@ public void WriteInt16(int address, short value) /// Returns the int read from memory. public int ReadInt32(int address) { - return BinaryPrimitives.ReadInt32LittleEndian(GetSpan().Slice(address, 4)); + return BinaryPrimitives.ReadInt32LittleEndian(GetSpan(address, sizeof(int))); + } + + /// + /// Reads an int from memory. + /// + /// The zero-based address to read from. + /// Returns the int read from memory. + public int ReadInt32(long address) + { + return BinaryPrimitives.ReadInt32LittleEndian(GetSpan(address, sizeof(int))); } /// @@ -245,7 +504,17 @@ public int ReadInt32(int address) /// The int to write. public void WriteInt32(int address, int value) { - BinaryPrimitives.WriteInt32LittleEndian(GetSpan().Slice(address, 4), value); + BinaryPrimitives.WriteInt32LittleEndian(GetSpan(address, sizeof(int)), value); + } + + /// + /// Writes an int to memory. + /// + /// The zero-based address to write to. + /// The int to write. + public void WriteInt32(long address, int value) + { + BinaryPrimitives.WriteInt32LittleEndian(GetSpan(address, sizeof(int)), value); } /// @@ -255,7 +524,17 @@ public void WriteInt32(int address, int value) /// Returns the long read from memory. public long ReadInt64(int address) { - return BinaryPrimitives.ReadInt64LittleEndian(GetSpan().Slice(address, 8)); + return BinaryPrimitives.ReadInt64LittleEndian(GetSpan(address, sizeof(long))); + } + + /// + /// Reads a long from memory. + /// + /// The zero-based address to read from. + /// Returns the long read from memory. + public long ReadInt64(long address) + { + return BinaryPrimitives.ReadInt64LittleEndian(GetSpan(address, sizeof(long))); } /// @@ -265,7 +544,17 @@ public long ReadInt64(int address) /// The long to write. public void WriteInt64(int address, long value) { - BinaryPrimitives.WriteInt64LittleEndian(GetSpan().Slice(address, 8), value); + BinaryPrimitives.WriteInt64LittleEndian(GetSpan(address, sizeof(long)), value); + } + + /// + /// Writes a long to memory. + /// + /// The zero-based address to write to. + /// The long to write. + public void WriteInt64(long address, long value) + { + BinaryPrimitives.WriteInt64LittleEndian(GetSpan(address, sizeof(long)), value); } /// @@ -282,6 +571,20 @@ public IntPtr ReadIntPtr(int address) return (IntPtr)ReadInt64(address); } + /// + /// Reads an IntPtr from memory. + /// + /// The zero-based address to read from. + /// Returns the IntPtr read from memory. + public IntPtr ReadIntPtr(long address) + { + if (IntPtr.Size == 4) + { + return (IntPtr)ReadInt32(address); + } + return (IntPtr)ReadInt64(address); + } + /// /// Writes an IntPtr to memory. /// @@ -291,11 +594,28 @@ public void WriteIntPtr(int address, IntPtr value) { if (IntPtr.Size == 4) { - WriteInt32(address, value.ToInt32()); + WriteInt32(address, (int)value); } else { - WriteInt64(address, value.ToInt64()); + WriteInt64(address, (long)value); + } + } + + /// + /// Writes an IntPtr to memory. + /// + /// The zero-based address to write to. + /// The IntPtr to write. + public void WriteIntPtr(long address, IntPtr value) + { + if (IntPtr.Size == 4) + { + WriteInt32(address, (int)value); + } + else + { + WriteInt64(address, (long)value); } } @@ -306,11 +626,17 @@ public void WriteIntPtr(int address, IntPtr value) /// Returns the single read from memory. public float ReadSingle(int address) { - unsafe - { - var i = ReadInt32(address); - return *((float*)&i); - } + return BitConverter.Int32BitsToSingle(ReadInt32(address)); + } + + /// + /// Reads a single from memory. + /// + /// The zero-based address to read from. + /// Returns the single read from memory. + public float ReadSingle(long address) + { + return BitConverter.Int32BitsToSingle(ReadInt32(address)); } /// @@ -320,10 +646,17 @@ public float ReadSingle(int address) /// The single to write. public void WriteSingle(int address, float value) { - unsafe - { - WriteInt32(address, *(int*)&value); - } + WriteInt32(address, BitConverter.SingleToInt32Bits(value)); + } + + /// + /// Writes a single to memory. + /// + /// The zero-based address to write to. + /// The single to write. + public void WriteSingle(long address, float value) + { + WriteInt32(address, BitConverter.SingleToInt32Bits(value)); } /// @@ -333,11 +666,17 @@ public void WriteSingle(int address, float value) /// Returns the double read from memory. public double ReadDouble(int address) { - unsafe - { - var i = ReadInt64(address); - return *((double*)&i); - } + return BitConverter.Int64BitsToDouble(ReadInt64(address)); + } + + /// + /// Reads a double from memory. + /// + /// The zero-based address to read from. + /// Returns the double read from memory. + public double ReadDouble(long address) + { + return BitConverter.Int64BitsToDouble(ReadInt64(address)); } /// @@ -347,10 +686,17 @@ public double ReadDouble(int address) /// The double to write. public void WriteDouble(int address, double value) { - unsafe - { - WriteInt64(address, *(long*)&value); - } + WriteInt64(address, BitConverter.DoubleToInt64Bits(value)); + } + + /// + /// Writes a double to memory. + /// + /// The zero-based address to write to. + /// The double to write. + public void WriteDouble(long address, double value) + { + WriteInt64(address, BitConverter.DoubleToInt64Bits(value)); } /// diff --git a/tests/MemoryAccessTests.cs b/tests/MemoryAccessTests.cs new file mode 100644 index 00000000..d1afa5d4 --- /dev/null +++ b/tests/MemoryAccessTests.cs @@ -0,0 +1,99 @@ +using System; +using System.Linq; +using FluentAssertions; +using Xunit; + +namespace Wasmtime.Tests +{ + public class MemoryAccessFixture : ModuleFixture + { + protected override string ModuleFileName => "MemoryAccess.wat"; + } + + public class MemoryAccessTests : IClassFixture, IDisposable + { + private MemoryAccessFixture Fixture { get; set; } + + private Store Store { get; set; } + + private Linker Linker { get; set; } + + public MemoryAccessTests(MemoryAccessFixture fixture) + { + Fixture = fixture; + Store = new Store(Fixture.Engine); + Linker = new Linker(Fixture.Engine); + } + + [Fact] + public unsafe void ItCanAccessMemoryWith65536Pages() + { + var instance = Linker.Instantiate(Store, Fixture.Module); + var memory = instance.GetMemory("mem"); + + memory.GetLength().Should().Be(uint.MaxValue + 1L); + + memory.ReadInt32(0).Should().Be(0); + memory.ReadInt32(0L).Should().Be(0); + + memory.WriteInt64(100, 1234); + memory.ReadInt64(100L).Should().Be(1234); + + memory.ReadByte(uint.MaxValue).Should().Be(0x63); + memory.ReadInt16(uint.MaxValue - 1).Should().Be(0x6364); + memory.ReadInt32(uint.MaxValue - 3).Should().Be(0x63646500); + + memory.ReadSingle(uint.MaxValue - 3).Should().Be(4.2131355E+21F); + + var span = memory.GetSpan(uint.MaxValue - 1, 2); + span.SequenceEqual(new byte[] { 0x64, 0x63 }).Should().BeTrue(); + + var int16Span = memory.GetSpan(0, int.MaxValue); + int16Span[int.MaxValue - 1].Should().Be(0x6500); + + int16Span = memory.GetSpan(2); + int16Span[int.MaxValue - 1].Should().Be(0x6364); + + byte* ptr = (byte*)memory.GetPointer(); + ptr += uint.MaxValue; + (*ptr).Should().Be(0x63); + + string str1 = "Hello World"; + memory.WriteString(uint.MaxValue - str1.Length, str1); + memory.ReadString(uint.MaxValue - str1.Length, str1.Length).Should().Be(str1); + } + + [Fact] + public void ItThrowsForOutOfBoundsAccess() + { + var instance = Linker.Instantiate(Store, Fixture.Module); + var memory = instance.GetMemory("mem"); + +#pragma warning disable CS0618 // Type or member is obsolete + Action action = () => memory.GetSpan(); +#pragma warning restore CS0618 // Type or member is obsolete + action.Should().Throw(); + + action = () => memory.GetSpan(0); + action.Should().Throw(); + + action = () => memory.GetSpan(-1L, 0); + action.Should().Throw(); + + action = () => memory.GetSpan(0L, -1); + action.Should().Throw(); + + action = () => memory.ReadInt16(uint.MaxValue); + action.Should().Throw(); + + action = () => memory.GetSpan(uint.MaxValue, 1); + action.Should().Throw(); + } + + public void Dispose() + { + Store.Dispose(); + Linker.Dispose(); + } + } +} diff --git a/tests/Modules/MemoryAccess.wat b/tests/Modules/MemoryAccess.wat new file mode 100644 index 00000000..f64b97a4 --- /dev/null +++ b/tests/Modules/MemoryAccess.wat @@ -0,0 +1,15 @@ +(module + (memory (export "mem") 65536) + (func $start + i32.const 4294967295 + i32.const 99 + i32.store8 + i32.const 4294967294 + i32.const 100 + i32.store8 + i32.const 4294967293 + i32.const 101 + i32.store8 + ) + (start $start) +) diff --git a/tests/Wasmtime.Tests.csproj b/tests/Wasmtime.Tests.csproj index ffc535e2..f39b32f7 100644 --- a/tests/Wasmtime.Tests.csproj +++ b/tests/Wasmtime.Tests.csproj @@ -26,6 +26,12 @@ + + + PreserveNewest + + + true From 713ff04a77e3e90809567fcad4335fe34a31e85f Mon Sep 17 00:00:00 2001 From: kpreisser Date: Sat, 15 Oct 2022 15:37:02 +0200 Subject: [PATCH 2/5] Follow-Up: Fix superfluous entry. --- tests/Wasmtime.Tests.csproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/Wasmtime.Tests.csproj b/tests/Wasmtime.Tests.csproj index f39b32f7..ffc535e2 100644 --- a/tests/Wasmtime.Tests.csproj +++ b/tests/Wasmtime.Tests.csproj @@ -26,12 +26,6 @@ - - - PreserveNewest - - - true From 9b10e55fa18e18088ddfbf44dd6e431683ce7a04 Mon Sep 17 00:00:00 2001 From: kpreisser Date: Fri, 21 Oct 2022 10:28:01 +0200 Subject: [PATCH 3/5] PR feedback: Remove the int-based overloads which have been replaced with long-based ones. --- src/Memory.cs | 244 -------------------------------------------------- 1 file changed, 244 deletions(-) diff --git a/src/Memory.cs b/src/Memory.cs index a2a7dd0c..7777639b 100644 --- a/src/Memory.cs +++ b/src/Memory.cs @@ -211,22 +211,6 @@ public unsafe Span GetSpan(long address, int length) return new Span((T*)(data + address), length); } - /// - /// Read a struct from memory. - /// - /// Type of the struct to read. Ensure layout in C# is identical to layout in WASM. - /// The zero-based address to read from. - /// Returns the struct read from memory. - /// - /// 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 T Read(int address) - where T : unmanaged - { - return GetSpan(address, 1)[0]; - } - /// /// Read a struct from memory. /// @@ -243,22 +227,6 @@ public T Read(long address) return GetSpan(address, 1)[0]; } - /// - /// Write a struct to memory. - /// - /// - /// The zero-based address to read from. - /// The struct to write. - /// - /// 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 void Write(int address, T value) - where T : unmanaged - { - GetSpan(address, 1)[0] = value; - } - /// /// Write a struct to memory. /// @@ -275,23 +243,6 @@ public void Write(long address, T value) GetSpan(address, 1)[0] = value; } - /// - /// Reads a string from memory with the specified encoding. - /// - /// The zero-based address to read from. - /// The length of bytes to read. - /// The encoding to use when reading the string; if null, UTF-8 encoding is used. - /// Returns the string read from memory. - public string ReadString(int address, int length, Encoding? encoding = null) - { - if (encoding is null) - { - encoding = Encoding.UTF8; - } - - return encoding.GetString(GetSpan(address, length)); - } - /// /// Reads a string from memory with the specified encoding. /// @@ -309,29 +260,6 @@ public string ReadString(long address, int length, Encoding? encoding = null) return encoding.GetString(GetSpan(address, length)); } - /// - /// Reads a null-terminated UTF-8 string from memory. - /// - /// The zero-based address to read from. - /// Returns the string read from memory. - public string ReadNullTerminatedString(int address) - { - if (address < 0) - { - throw new ArgumentOutOfRangeException(nameof(address)); - } - - // We can only read a maximum of 2 GiB. - var slice = GetSpan(address, (int)Math.Min(int.MaxValue, GetLength() - address)); - var terminator = slice.IndexOf((byte)0); - if (terminator == -1) - { - throw new InvalidOperationException("string is not null terminated"); - } - - return Encoding.UTF8.GetString(slice.Slice(0, terminator)); - } - /// /// Reads a null-terminated UTF-8 string from memory. /// @@ -355,27 +283,6 @@ public string ReadNullTerminatedString(long address) return Encoding.UTF8.GetString(slice.Slice(0, terminator)); } - /// - /// Writes a string at the given address with the given encoding. - /// - /// The zero-based address to write to. - /// The string to write. - /// The encoding to use when writing the string; if null, UTF-8 encoding is used. - /// Returns the number of bytes written. - public int WriteString(int address, string value, Encoding? encoding = null) - { - if (address < 0) - { - throw new ArgumentOutOfRangeException(nameof(address)); - } - - if (encoding is null) - { - encoding = Encoding.UTF8; - } - return encoding.GetBytes(value, GetSpan(address, (int)Math.Min(int.MaxValue, GetLength() - address))); - } - /// /// Writes a string at the given address with the given encoding. /// @@ -397,16 +304,6 @@ public int WriteString(long address, string value, Encoding? encoding = null) return encoding.GetBytes(value, GetSpan(address, (int)Math.Min(int.MaxValue, GetLength() - address))); } - /// - /// Reads a byte from memory. - /// - /// The zero-based address to read from. - /// Returns the byte read from memory. - public byte ReadByte(int address) - { - return GetSpan(address, sizeof(byte))[0]; - } - /// /// Reads a byte from memory. /// @@ -417,16 +314,6 @@ public byte ReadByte(long address) return GetSpan(address, sizeof(byte))[0]; } - /// - /// Writes a byte to memory. - /// - /// The zero-based address to write to. - /// The byte to write. - public void WriteByte(int address, byte value) - { - GetSpan(address, sizeof(byte))[0] = value; - } - /// /// Writes a byte to memory. /// @@ -437,16 +324,6 @@ public void WriteByte(long address, byte value) GetSpan(address, sizeof(byte))[0] = value; } - /// - /// Reads a short from memory. - /// - /// The zero-based address to read from. - /// Returns the short read from memory. - public short ReadInt16(int address) - { - return BinaryPrimitives.ReadInt16LittleEndian(GetSpan(address, sizeof(short))); - } - /// /// Reads a short from memory. /// @@ -457,16 +334,6 @@ public short ReadInt16(long address) return BinaryPrimitives.ReadInt16LittleEndian(GetSpan(address, sizeof(short))); } - /// - /// Writes a short to memory. - /// - /// The zero-based address to write to. - /// The short to write. - public void WriteInt16(int address, short value) - { - BinaryPrimitives.WriteInt16LittleEndian(GetSpan(address, sizeof(short)), value); - } - /// /// Writes a short to memory. /// @@ -477,16 +344,6 @@ public void WriteInt16(long address, short value) BinaryPrimitives.WriteInt16LittleEndian(GetSpan(address, sizeof(short)), value); } - /// - /// Reads an int from memory. - /// - /// The zero-based address to read from. - /// Returns the int read from memory. - public int ReadInt32(int address) - { - return BinaryPrimitives.ReadInt32LittleEndian(GetSpan(address, sizeof(int))); - } - /// /// Reads an int from memory. /// @@ -497,16 +354,6 @@ public int ReadInt32(long address) return BinaryPrimitives.ReadInt32LittleEndian(GetSpan(address, sizeof(int))); } - /// - /// Writes an int to memory. - /// - /// The zero-based address to write to. - /// The int to write. - public void WriteInt32(int address, int value) - { - BinaryPrimitives.WriteInt32LittleEndian(GetSpan(address, sizeof(int)), value); - } - /// /// Writes an int to memory. /// @@ -517,16 +364,6 @@ public void WriteInt32(long address, int value) BinaryPrimitives.WriteInt32LittleEndian(GetSpan(address, sizeof(int)), value); } - /// - /// Reads a long from memory. - /// - /// The zero-based address to read from. - /// Returns the long read from memory. - public long ReadInt64(int address) - { - return BinaryPrimitives.ReadInt64LittleEndian(GetSpan(address, sizeof(long))); - } - /// /// Reads a long from memory. /// @@ -537,16 +374,6 @@ public long ReadInt64(long address) return BinaryPrimitives.ReadInt64LittleEndian(GetSpan(address, sizeof(long))); } - /// - /// Writes a long to memory. - /// - /// The zero-based address to write to. - /// The long to write. - public void WriteInt64(int address, long value) - { - BinaryPrimitives.WriteInt64LittleEndian(GetSpan(address, sizeof(long)), value); - } - /// /// Writes a long to memory. /// @@ -557,20 +384,6 @@ public void WriteInt64(long address, long value) BinaryPrimitives.WriteInt64LittleEndian(GetSpan(address, sizeof(long)), value); } - /// - /// Reads an IntPtr from memory. - /// - /// The zero-based address to read from. - /// Returns the IntPtr read from memory. - public IntPtr ReadIntPtr(int address) - { - if (IntPtr.Size == 4) - { - return (IntPtr)ReadInt32(address); - } - return (IntPtr)ReadInt64(address); - } - /// /// Reads an IntPtr from memory. /// @@ -585,23 +398,6 @@ public IntPtr ReadIntPtr(long address) return (IntPtr)ReadInt64(address); } - /// - /// Writes an IntPtr to memory. - /// - /// The zero-based address to write to. - /// The IntPtr to write. - public void WriteIntPtr(int address, IntPtr value) - { - if (IntPtr.Size == 4) - { - WriteInt32(address, (int)value); - } - else - { - WriteInt64(address, (long)value); - } - } - /// /// Writes an IntPtr to memory. /// @@ -619,16 +415,6 @@ public void WriteIntPtr(long address, IntPtr value) } } - /// - /// Reads a single from memory. - /// - /// The zero-based address to read from. - /// Returns the single read from memory. - public float ReadSingle(int address) - { - return BitConverter.Int32BitsToSingle(ReadInt32(address)); - } - /// /// Reads a single from memory. /// @@ -639,16 +425,6 @@ public float ReadSingle(long address) return BitConverter.Int32BitsToSingle(ReadInt32(address)); } - /// - /// Writes a single to memory. - /// - /// The zero-based address to write to. - /// The single to write. - public void WriteSingle(int address, float value) - { - WriteInt32(address, BitConverter.SingleToInt32Bits(value)); - } - /// /// Writes a single to memory. /// @@ -659,16 +435,6 @@ public void WriteSingle(long address, float value) WriteInt32(address, BitConverter.SingleToInt32Bits(value)); } - /// - /// Reads a double from memory. - /// - /// The zero-based address to read from. - /// Returns the double read from memory. - public double ReadDouble(int address) - { - return BitConverter.Int64BitsToDouble(ReadInt64(address)); - } - /// /// Reads a double from memory. /// @@ -679,16 +445,6 @@ public double ReadDouble(long address) return BitConverter.Int64BitsToDouble(ReadInt64(address)); } - /// - /// Writes a double to memory. - /// - /// The zero-based address to write to. - /// The double to write. - public void WriteDouble(int address, double value) - { - WriteInt64(address, BitConverter.DoubleToInt64Bits(value)); - } - /// /// Writes a double to memory. /// From 3f935d10d00335f743f82a8b71d5b94c940b5381 Mon Sep 17 00:00:00 2001 From: kpreisser Date: Fri, 21 Oct 2022 10:28:25 +0200 Subject: [PATCH 4/5] PR feedback: Fix style and missing comment. --- src/Memory.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Memory.cs b/src/Memory.cs index 7777639b..8c0fe646 100644 --- a/src/Memory.cs +++ b/src/Memory.cs @@ -85,6 +85,7 @@ public long GetLength() /// Returns a pointer to the start of the memory. The length for which the pointer /// is valid can be retrieved with . /// + /// Returns a pointer to the start of the memory. /// /// The pointer may become invalid if the memory grows. /// @@ -94,7 +95,6 @@ public long GetLength() /// Therefore, the returned pointer should not be used after calling the grow method or /// after calling into WebAssembly code. /// - /// public unsafe IntPtr GetPointer() { var data = Native.wasmtime_memory_data(store.Context.handle, this.memory); @@ -206,7 +206,9 @@ public unsafe Span GetSpan(long address, int length) long byteLength = (long)length * sizeof(T); if (address > memoryLength - byteLength) + { throw new ArgumentException("The specified address and length exceed the Memory's bounds."); + } return new Span((T*)(data + address), length); } @@ -301,6 +303,7 @@ public int WriteString(long address, string value, Encoding? encoding = null) { encoding = Encoding.UTF8; } + return encoding.GetBytes(value, GetSpan(address, (int)Math.Min(int.MaxValue, GetLength() - address))); } From c57a79fe731770c9f3ed9eb16a56dd240d5b0c6c Mon Sep 17 00:00:00 2001 From: kpreisser Date: Fri, 21 Oct 2022 10:28:44 +0200 Subject: [PATCH 5/5] Use in the element to better structure paragraphs. --- src/Memory.cs | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Memory.cs b/src/Memory.cs index 8c0fe646..89e0afa1 100644 --- a/src/Memory.cs +++ b/src/Memory.cs @@ -87,13 +87,16 @@ public long GetLength() /// /// Returns a pointer to the start of the memory. /// + /// /// The pointer 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 pointer should not be used after calling the grow method or /// after calling into WebAssembly code. + /// /// public unsafe IntPtr GetPointer() { @@ -107,13 +110,16 @@ public unsafe IntPtr GetPointer() /// Returns the span of the memory. /// The memory has more than 32767 pages. /// + /// /// 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. + /// /// [Obsolete("This method will throw an OverflowException if the memory has more than 32767 pages. " + "Use the " + nameof(GetSpan) + " overload taking an address and a length.")] @@ -129,13 +135,16 @@ public Span GetSpan() /// The zero-based address of the start of the span. /// The length of the span. /// + /// /// 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. + /// /// public Span GetSpan(long address, int length) { @@ -150,16 +159,20 @@ public Span GetSpan(long address, int length) /// The memory exceeds the byte length that can be /// represented by a . /// + /// /// 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 unsafe Span GetSpan(int address) where T : unmanaged @@ -174,16 +187,20 @@ public unsafe Span GetSpan(int address) /// The zero-based address of the start of the span. /// The length of the span. /// + /// /// 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 unsafe Span GetSpan(long address, int length) where T : unmanaged @@ -220,8 +237,10 @@ public unsafe Span GetSpan(long address, int length) /// The zero-based address to read from. /// Returns the struct read from memory. /// + /// /// 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 T Read(long address) where T : unmanaged @@ -236,8 +255,10 @@ public T Read(long address) /// The zero-based address to read from. /// The struct to write. /// + /// /// 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 void Write(long address, T value) where T : unmanaged