aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-11-02 21:32:44 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-11-02 21:32:44 -0400
commit59599b0f3e57d1881639d482bf1758c7f93d0dc2 (patch)
treeda14ad0c450542e37e2f597db22337343c68e2dc
parent9e3dd9be0f0ec7aaef1a719f09f96425e66369df (diff)
Unify memory api and add more tests
-rw-r--r--lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs19
-rw-r--r--lib/Net.Http/src/Core/HttpContext.cs2
-rw-r--r--lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs28
-rw-r--r--lib/Net.Http/src/IHttpMemoryPool.cs2
-rw-r--r--lib/Utils/src/Extensions/MemoryExtensions.cs60
-rw-r--r--lib/Utils/src/IO/VnMemoryStream.cs6
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.cs468
-rw-r--r--lib/Utils/src/Memory/MemoryUtilAlloc.cs81
-rw-r--r--lib/Utils/tests/Async/AsyncAccessSerializerTests.cs35
-rw-r--r--lib/Utils/tests/Memory/MemoryUtilTests.cs242
10 files changed, 849 insertions, 94 deletions
diff --git a/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs b/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs
index b23bc8f..bffc1c5 100644
--- a/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs
+++ b/lib/Net.Http/src/Core/Buffering/HttpBufferElement.cs
@@ -35,9 +35,15 @@ namespace VNLib.Net.Http.Core.Buffering
* as we are pinning the block. The block is pinned once for the lifetime
* of the connection, so we have access to the raw memory for faster
* span access.
+ *
+ * It is suggested to use an Unmanaged memory pool for zero-cost memory
+ * pinning
*/
internal abstract class HttpBufferElement : IHttpBuffer
{
+ private MemoryHandle Pinned;
+ private int _size;
+ protected Memory<byte> Buffer;
public virtual void FreeBuffer()
{
@@ -45,7 +51,7 @@ namespace VNLib.Net.Http.Core.Buffering
Pinned.Dispose();
Pinned = default;
Buffer = default;
- Size = 0;
+ _size = 0;
}
public virtual void SetBuffer(Memory<byte> buffer)
@@ -55,18 +61,15 @@ namespace VNLib.Net.Http.Core.Buffering
//Pin buffer and hold handle
Pinned = buffer.Pin();
//Set size to length of buffer
- Size = buffer.Length;
+ _size = buffer.Length;
}
///<inheritdoc/>
- public int Size { get; private set; }
-
- private MemoryHandle Pinned;
- protected Memory<byte> Buffer;
+ public int Size => _size;
///<inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public virtual Span<byte> GetBinSpan() => MemoryUtil.GetSpan<byte>(ref Pinned, Size);
+ public virtual Span<byte> GetBinSpan() => MemoryUtil.GetSpan<byte>(ref Pinned, _size);
///<inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -75,7 +78,7 @@ namespace VNLib.Net.Http.Core.Buffering
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual Span<byte> GetBinSpan(int maxSize)
{
- return maxSize > Size ? throw new ArgumentOutOfRangeException(nameof(maxSize)) : MemoryUtil.GetSpan<byte>(ref Pinned, maxSize);
+ return maxSize > _size ? throw new ArgumentOutOfRangeException(nameof(maxSize)) : MemoryUtil.GetSpan<byte>(ref Pinned, maxSize);
}
}
}
diff --git a/lib/Net.Http/src/Core/HttpContext.cs b/lib/Net.Http/src/Core/HttpContext.cs
index f0cd3f8..feb3df5 100644
--- a/lib/Net.Http/src/Core/HttpContext.cs
+++ b/lib/Net.Http/src/Core/HttpContext.cs
@@ -143,7 +143,7 @@ namespace VNLib.Net.Http.Core
{
_ctx = ctx;
- //Alloc buffers
+ //Alloc buffers during context init incase exception occurs in user-code
Buffers.AllocateBuffer(ParentServer.Config.MemoryPool);
//Init new connection
diff --git a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
index dd21707..8fef8dd 100644
--- a/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
+++ b/lib/Net.Http/src/Core/Request/HttpRequestExtensions.cs
@@ -157,6 +157,12 @@ namespace VNLib.Net.Http.Core
//Gets the max form data buffer size to help calculate the initial char buffer size
int maxBufferSize = context.ParentServer.Config.BufferConfig.FormDataBufferSize;
+ //Calculate a largest available buffer to read the entire stream or up to the maximum buffer size
+ int bufferSize = (int)Math.Min(request.InputStream.Length, maxBufferSize);
+
+ //Get the form data buffer (should be cost free)
+ Memory<byte> formBuffer = context.Buffers.GetFormDataBuffer();
+
switch (request.ContentType)
{
//CT not supported, dont read it
@@ -164,14 +170,8 @@ namespace VNLib.Net.Http.Core
break;
case ContentType.UrlEncoded:
{
- //Calculate a largest available buffer to read the entire stream or up to the maximum buffer size
- int bufferSize = (int)Math.Min(request.InputStream.Length, maxBufferSize);
-
//Alloc the form data character buffer, this will need to grow if the form data is larger than the buffer
- using MemoryHandle<char> urlbody = pool.AllocFormDataBuffer<char>(bufferSize);
-
- //Get a buffer for the form data
- Memory<byte> formBuffer = context.Buffers.GetFormDataBuffer();
+ using IResizeableMemoryHandle<char> urlbody = pool.AllocFormDataBuffer<char>(bufferSize);
//Load char buffer from stream
int chars = await BufferInputStream(request.InputStream, urlbody, formBuffer, info.Encoding);
@@ -190,14 +190,8 @@ namespace VNLib.Net.Http.Core
break;
}
- //Calculate a largest available buffer to read the entire stream or up to the maximum buffer size
- int bufferSize = (int)Math.Min(request.InputStream.Length, maxBufferSize);
-
//Alloc the form data buffer
- using MemoryHandle<char> formBody = pool.AllocFormDataBuffer<char>(bufferSize);
-
- //Get a buffer for the form data
- Memory<byte> formBuffer = context.Buffers.GetFormDataBuffer();
+ using IResizeableMemoryHandle<char> formBody = pool.AllocFormDataBuffer<char>(bufferSize);
//Load char buffer from stream
int chars = await BufferInputStream(request.InputStream, formBody, formBuffer, info.Encoding);
@@ -223,7 +217,7 @@ namespace VNLib.Net.Http.Core
* We assume the parsing method checked the size of the input stream so we can assume its safe to read
* all of it into memory.
*/
- private static async ValueTask<int> BufferInputStream(Stream stream, MemoryHandle<char> charBuffer, Memory<byte> binBuffer, Encoding encoding)
+ private static async ValueTask<int> BufferInputStream(Stream stream, IResizeableMemoryHandle<char> charBuffer, Memory<byte> binBuffer, Encoding encoding)
{
int length = 0;
do
@@ -361,11 +355,11 @@ namespace VNLib.Net.Http.Core
int bytes = info.Encoding.GetByteCount(data);
//get a buffer from the HTTP heap
- MemoryHandle<byte> buffHandle = pool.AllocFormDataBuffer<byte>(bytes);
+ IResizeableMemoryHandle<byte> buffHandle = pool.AllocFormDataBuffer<byte>(bytes);
try
{
//Convert back to binary
- bytes = info.Encoding.GetBytes(data, buffHandle);
+ bytes = info.Encoding.GetBytes(data, buffHandle.Span);
//Create a new memory stream encapsulating the file data
VnMemoryStream vms = VnMemoryStream.FromHandle(buffHandle, true, bytes, true);
diff --git a/lib/Net.Http/src/IHttpMemoryPool.cs b/lib/Net.Http/src/IHttpMemoryPool.cs
index f0a548e..d58ac5f 100644
--- a/lib/Net.Http/src/IHttpMemoryPool.cs
+++ b/lib/Net.Http/src/IHttpMemoryPool.cs
@@ -47,6 +47,6 @@ namespace VNLib.Net.Http
/// <typeparam name="T"></typeparam>
/// <param name="initialSize">The initial size of the buffer to allocate, which may be expanded as needed</param>
/// <returns>The allocated block of memory</returns>
- MemoryHandle<T> AllocFormDataBuffer<T>(int initialSize) where T: unmanaged;
+ IResizeableMemoryHandle<T> AllocFormDataBuffer<T>(int initialSize) where T: unmanaged;
}
}
diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs
index d21ceee..9553578 100644
--- a/lib/Utils/src/Extensions/MemoryExtensions.cs
+++ b/lib/Utils/src/Extensions/MemoryExtensions.cs
@@ -263,7 +263,7 @@ namespace VNLib.Utils.Extensions
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static ref byte GetByteOffsetRef<T>(this IMemoryHandle<T> block, nuint offset)
+ public static ref byte GetOffsetByteRef<T>(this IMemoryHandle<T> block, nuint offset)
{
_ = block ?? throw new ArgumentNullException(nameof(block));
@@ -361,16 +361,31 @@ namespace VNLib.Utils.Extensions
public static MemoryPool<T> ToPool<T>(this IUnmangedHeap heap, int maxBufferSize = int.MaxValue) where T : unmanaged => new PrivateBuffersMemoryPool<T>(heap, maxBufferSize);
/// <summary>
- /// Allocates a structure of the specified type on the current unmanged heap and zero's its memory
+ /// Allocates a structure of the specified type on the current unmanged heap and optionally zero's its memory
/// </summary>
/// <typeparam name="T">The structure type</typeparam>
/// <param name="heap"></param>
+ /// <param name="zero">A value that indicates if the structure memory should be zeroed before returning</param>
/// <returns>A pointer to the structure ready for use.</returns>
/// <remarks>Allocations must be freed with <see cref="StructFree{T}(IUnmangedHeap, T*)"/></remarks>
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe T* StructAlloc<T>(this IUnmangedHeap heap) where T : unmanaged => (T*)heap.Alloc(1, (nuint)sizeof(T), true);
+ public static unsafe T* StructAlloc<T>(this IUnmangedHeap heap, bool zero = true) where T : unmanaged => MemoryUtil.StructAlloc<T>(heap, zero);
+
+ /// <summary>
+ /// Allocates a structure of the specified type on the current unmanged heap and optionally zero's its memory
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="zero">A value that indicates if the structure memory should be zeroed before returning</param>
+ /// <returns>A reference/pointer to the structure ready for use.</returns>
+ /// <remarks>Allocations must be freed with <see cref="StructFreeRef{T}(IUnmangedHeap, ref T)"/></remarks>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T StructAllocRef<T>(this IUnmangedHeap heap, bool zero = true) where T : unmanaged => ref MemoryUtil.StructAllocRef<T>(heap, zero);
+
/// <summary>
/// Frees a structure at the specified address from the this heap.
@@ -378,17 +393,20 @@ namespace VNLib.Utils.Extensions
/// </summary>
/// <typeparam name="T">The structure type</typeparam>
/// <param name="heap"></param>
- /// <param name="structPtr">A pointer to the structure</param>
+ /// <param name="structPtr">A reference/pointer to the structure</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void StructFree<T>(this IUnmangedHeap heap, T* structPtr) where T : unmanaged
- {
- IntPtr block = new(structPtr);
- //Free block from heap
- heap.Free(ref block);
- //Clear ref
- *structPtr = default;
- }
-
+ public static unsafe void StructFree<T>(this IUnmangedHeap heap, T* structPtr) where T : unmanaged => MemoryUtil.StructFree(heap, structPtr);
+
+ /// <summary>
+ /// Frees a structure at the specified address from the this heap.
+ /// This must be the same heap the structure was allocated from
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="heap"></param>
+ /// <param name="structRef">A reference to the structure</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void StructFreeRef<T>(this IUnmangedHeap heap, ref T structRef) where T : unmanaged => MemoryUtil.StructFreeRef(heap, ref structRef);
+
/// <summary>
/// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type
/// </summary>
@@ -406,10 +424,8 @@ namespace VNLib.Utils.Extensions
_ = heap ?? throw new ArgumentNullException(nameof(heap));
//Minimum of one element
elements = Math.Max(elements, 1);
- //Get element size
- nuint elementSize = (nuint)sizeof(T);
//If zero flag is set then specify zeroing memory
- IntPtr block = heap.Alloc(elements, elementSize, zero);
+ IntPtr block = heap.Alloc(elements, (nuint)sizeof(T), zero);
//Return handle wrapper
return new MemoryHandle<T>(heap, block, elements, zero);
}
@@ -430,7 +446,7 @@ namespace VNLib.Utils.Extensions
{
return elements >= 0 ? Alloc<T>(heap, (nuint)elements, zero) : throw new ArgumentOutOfRangeException(nameof(elements));
}
-
+
/// <summary>
/// Allocates a buffer from the current heap and initialzies it by copying the initial data buffer
/// </summary>
@@ -441,13 +457,13 @@ namespace VNLib.Utils.Extensions
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlySpan<T> initialData) where T:unmanaged
+ public static MemoryHandle<T> AllocAndCopy<T>(this IUnmangedHeap heap, ReadOnlySpan<T> initialData) where T : unmanaged
{
//Aloc block
MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length);
-
+
//Copy initial data
- MemoryUtil.Copy(initialData, handle, 0);
+ MemoryUtil.Copy(initialData, 0, handle, 0, initialData.Length);
return handle;
}
@@ -468,7 +484,7 @@ namespace VNLib.Utils.Extensions
MemoryHandle<T> handle = heap.Alloc<T>(initialData.Length);
//Copy initial data
- MemoryUtil.Copy(initialData, handle, 0);
+ MemoryUtil.Copy(initialData, 0, handle, 0, initialData.Length);
return handle;
}
@@ -486,7 +502,7 @@ namespace VNLib.Utils.Extensions
public static void WriteAndResize<T>(this IResizeableMemoryHandle<T> handle, ReadOnlySpan<T> input) where T: unmanaged
{
handle.Resize(input.Length);
- MemoryUtil.Copy(input, handle, 0);
+ MemoryUtil.Copy(input, 0, handle, 0, input.Length);
}
/// <summary>
diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs
index 7ac56a6..45c4a55 100644
--- a/lib/Utils/src/IO/VnMemoryStream.cs
+++ b/lib/Utils/src/IO/VnMemoryStream.cs
@@ -368,7 +368,7 @@ namespace VNLib.Utils.IO
}
//get the value at the current position
- ref byte ptr = ref _buffer.GetByteOffsetRef((nuint)_position);
+ ref byte ptr = ref _buffer.GetOffsetByteRef((nuint)_position);
//Increment position
_position++;
@@ -488,7 +488,7 @@ namespace VNLib.Utils.IO
_length = newPos;
}
//Copy the input buffer to the internal buffer
- MemoryUtil.Copy(buffer, _buffer, (nuint)_position);
+ MemoryUtil.Copy(buffer, 0, _buffer, (nuint)_position, buffer.Length);
//Update the position
_position = newPos;
}
@@ -527,7 +527,7 @@ namespace VNLib.Utils.IO
byte[] data = new byte[_length];
//Copy the internal buffer to the new array
- MemoryUtil.Copy(_buffer, 0, data, 0, (nuint)_length);
+ MemoryUtil.CopyArray(_buffer, 0, data, 0, (nuint)_length);
return data;
}
diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs
index 0261bdf..a1ad0c1 100644
--- a/lib/Utils/src/Memory/MemoryUtil.cs
+++ b/lib/Utils/src/Memory/MemoryUtil.cs
@@ -294,6 +294,7 @@ namespace VNLib.Utils.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InitializeBlock<T>(Memory<T> block) where T : struct => UnsafeZeroMemory<T>(block);
+
/// <summary>
/// Zeroes a block of memory of the given unmanaged type
/// </summary>
@@ -301,15 +302,39 @@ namespace VNLib.Utils.Memory
/// <param name="block">A pointer to the block of memory to zero</param>
/// <param name="itemCount">The number of elements in the block to zero</param>
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
- public static void InitializeBlock<T>(T* block, int itemCount) where T : unmanaged
+ public static void InitializeBlock<T>(ref T block, int itemCount) where T : struct
{
- if (itemCount <= 0 || block == null)
+ if (Unsafe.IsNullRef(ref block))
+ {
+ throw new ArgumentNullException(nameof(block));
+ }
+
+ if (itemCount <= 0)
{
return;
}
+ //To bytereference
+ ref byte byteRef = ref Unsafe.As<T, byte>(ref block);
//Zero block
- Unsafe.InitBlock(block, 0, ByteCount<T>((uint)itemCount));
+ Unsafe.InitBlock(ref byteRef, 0, ByteCount<T>((uint)itemCount));
+ }
+
+ /// <summary>
+ /// Zeroes a block of memory of the given unmanaged type
+ /// </summary>
+ /// <typeparam name="T">The unmanaged type to zero</typeparam>
+ /// <param name="block">A pointer to the block of memory to zero</param>
+ /// <param name="itemCount">The number of elements in the block to zero</param>
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ public static void InitializeBlock<T>(T* block, int itemCount) where T : unmanaged
+ {
+ if (block == null)
+ {
+ throw new ArgumentNullException(nameof(block));
+ }
+
+ InitializeBlock(ref *block, itemCount);
}
/// <summary>
@@ -327,7 +352,7 @@ namespace VNLib.Utils.Memory
/// <typeparam name="T">The structure type</typeparam>
/// <param name="structPtr">The pointer to the allocated structure</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ZeroStruct<T>(void* structPtr) => Unsafe.InitBlock(structPtr, 0, (uint)Unsafe.SizeOf<T>());
+ public static void ZeroStruct<T>(void* structPtr) where T: unmanaged => InitializeBlock((T*)structPtr, Unsafe.SizeOf<T>());
/// <summary>
/// Zeroes a block of memory pointing to the structure
@@ -335,7 +360,7 @@ namespace VNLib.Utils.Memory
/// <typeparam name="T">The structure type</typeparam>
/// <param name="block">The pointer to the allocated structure</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ZeroStruct<T>(IntPtr block) => ZeroStruct<T>(block.ToPointer());
+ public static void ZeroStruct<T>(IntPtr block) where T : unmanaged => ZeroStruct<T>(block.ToPointer());
/// <summary>
/// Zeroes a block of memory pointing to the structure
@@ -345,6 +370,22 @@ namespace VNLib.Utils.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ZeroStruct<T>(T* structPtr) where T : unmanaged => ZeroStruct<T>((void*)structPtr);
+ /// <summary>
+ /// Zeroes a block of memory pointing to the structure
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="structRef">The reference to the allocated structure</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ZeroStruct<T>(ref T structRef) where T : unmanaged
+ {
+ if(Unsafe.IsNullRef(ref structRef))
+ {
+ throw new ArgumentNullException(nameof(structRef));
+ }
+
+ Unsafe.InitBlock(ref Unsafe.As<T, byte>(ref structRef), 0, (uint)sizeof(T));
+ }
+
#endregion
#region Copy
@@ -355,7 +396,275 @@ namespace VNLib.Utils.Memory
* guards are in place.
*/
private delegate void BigMemmove(ref byte dest, ref byte src, nuint len);
- private static readonly BigMemmove? _sysMemmove = ManagedLibrary.TryGetStaticMethod<BigMemmove>(typeof(Buffer), "Memmove", System.Reflection.BindingFlags.NonPublic);
+ private static readonly BigMemmove? _clrMemmove = ManagedLibrary.TryGetStaticMethod<BigMemmove>(typeof(Buffer), "Memmove", System.Reflection.BindingFlags.NonPublic);
+
+ /// <summary>
+ /// Copies structure data from a source byte reference that points to a sequence of
+ /// of data to the target structure reference.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A referrence to the first byte of source data to copy from</param>
+ /// <param name="target">An initialized target structure to copy data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void CopyStruct<T>(ref byte source, ref T target) where T : unmanaged
+ {
+ if (Unsafe.IsNullRef(ref target))
+ {
+ throw new ArgumentNullException(nameof(target));
+ }
+ if (Unsafe.IsNullRef(ref source))
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ //Recover byte reference of target struct
+ ref byte dst = ref Unsafe.As<T, byte>(ref target);
+
+ //Memmove
+ bool result = MemmoveByRef(ref source, 0, ref dst, 0, (nuint)sizeof(T));
+ Debug.Assert(result, "Memmove 32bit copy failed");
+ }
+
+ /// <summary>
+ /// Copies the memory of the structure pointed to by the source reference to the target
+ /// reference data sequence
+ /// </summary>
+ /// <remarks>
+ /// Warning: This is a low level api that cannot do bounds checking on the target sequence. It must
+ /// be large enough to hold the structure data.
+ /// </remarks>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A referrence to the first byte of source data to copy from</param>
+ /// <param name="target">A reference to the first byte of the memory location to copy the struct data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void CopyStruct<T>(ref T source, ref byte target) where T : unmanaged
+ {
+ if (Unsafe.IsNullRef(ref source))
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+ if (Unsafe.IsNullRef(ref target))
+ {
+ throw new ArgumentNullException(nameof(target));
+ }
+
+ //Recover byte reference to struct
+ ref byte src = ref Unsafe.As<T, byte>(ref source);
+
+ //Memmove
+ bool result = MemmoveByRef(ref src, 0, ref target, 0, (nuint)sizeof(T));
+ Debug.Assert(result, "Memmove 32bit copy failed");
+ }
+
+
+ /// <summary>
+ /// Copies structure data from a source byte reference that points to a sequence of
+ /// of data to the target structure reference.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A referrence to the first byte of source data to copy from</param>
+ /// <param name="target">A pointer to initialized target structure to copy data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(ref byte source, T* target) where T : unmanaged
+ {
+ if (target == null)
+ {
+ throw new ArgumentNullException(nameof(target));
+ }
+ CopyStruct(ref source, ref *target);
+ }
+
+ /// <summary>
+ /// Copies structure data from a source byte reference that points to a sequence of
+ /// of data to the target structure reference.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A referrence to the first byte of source data to copy from</param>
+ /// <param name="target">A pointer to initialized target structure to copy data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(ref byte source, void* target) where T : unmanaged => CopyStruct(ref source, (T*)target);
+
+ /// <summary>
+ /// Copies structure data from a source sequence of data to the target structure reference.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="sourceData">A referrence to the first byte of source data to copy from</param>
+ /// <param name="target">A pointer to initialized target structure to copy data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(ReadOnlySpan<byte> sourceData, ref T target) where T : unmanaged
+ {
+ if (sourceData.Length < sizeof(T))
+ {
+ throw new ArgumentException("Source data is smaller than the size of the structure");
+ }
+
+ CopyStruct(ref MemoryMarshal.GetReference(sourceData), ref target);
+ }
+
+ /// <summary>
+ /// Copies structure data from a source sequence of data to the target structure reference.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="sourceData">A referrence to the first byte of source data to copy from</param>
+ /// <param name="target">A pointer to initialized target structure to copy data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(ReadOnlySpan<byte> sourceData, T* target) where T : unmanaged => CopyStruct(sourceData, ref *target);
+
+ /// <summary>
+ /// Copies structure data from a source sequence of data to the target structure reference.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="sourceData">A referrence to the first byte of source data to copy from</param>
+ /// <param name="target">A pointer to initialized target structure to copy data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(ReadOnlySpan<byte> sourceData, void* target) where T: unmanaged => CopyStruct(sourceData, (T*)target);
+
+
+
+ /// <summary>
+ /// Copies the memory of the structure pointed to by the source pointer to the target
+ /// reference data sequence
+ /// </summary>
+ /// <remarks>
+ /// Warning: This is a low level api that cannot do bounds checking on the target sequence. It must
+ /// be large enough to hold the structure data.
+ /// </remarks>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A pointer to the first byte of source data to copy from</param>
+ /// <param name="target">A reference to the first byte of the memory location to copy the struct data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(T* source, ref byte target) where T : unmanaged
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ CopyStruct(ref *source, ref target);
+ }
+
+ /// <summary>
+ /// Copies the memory of the structure pointed to by the source pointer to the target
+ /// reference data sequence
+ /// </summary>
+ /// <remarks>
+ /// Warning: This is a low level api that cannot do bounds checking on the target sequence. It must
+ /// be large enough to hold the structure data.
+ /// </remarks>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A pointer to the first byte of source data to copy from</param>
+ /// <param name="target">A reference to the first byte of the memory location to copy the struct data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(void* source, ref byte target) where T : unmanaged => CopyStruct((T*)source, ref target);
+
+ /// <summary>
+ /// Copies the memory of the structure pointed to by the source pointer to the target
+ /// reference data sequence
+ /// </summary>
+ /// <remarks>
+ /// Warning: This is a low level api that cannot do bounds checking on the target sequence. It must
+ /// be large enough to hold the structure data.
+ /// </remarks>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A pointer to the first byte of source data to copy from</param>
+ /// <param name="target">A reference to the first byte of the memory location to copy the struct data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(ref T source, Span<byte> target) where T : unmanaged
+ {
+ //check that the span is large enough to hold the structure
+ if (target.Length < sizeof(T))
+ {
+ throw new ArgumentException("Target span is smaller than the size of the structure");
+ }
+
+ CopyStruct(ref source, ref MemoryMarshal.AsRef<byte>(target));
+ }
+
+ /// <summary>
+ /// Copies the memory of the structure pointed to by the source pointer to the target
+ /// reference data sequence
+ /// </summary>
+ /// <remarks>
+ /// Warning: This is a low level api that cannot do bounds checking on the target sequence. It must
+ /// be large enough to hold the structure data.
+ /// </remarks>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A pointer to the first byte of source data to copy from</param>
+ /// <param name="target">A reference to the first byte of the memory location to copy the struct data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(T* source, Span<byte> target) where T : unmanaged
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+ CopyStruct(ref *source, target);
+ }
+
+ /// <summary>
+ /// Copies the memory of the structure pointed to by the source pointer to the target
+ /// reference data sequence
+ /// </summary>
+ /// <remarks>
+ /// Warning: This is a low level api that cannot do bounds checking on the target sequence. It must
+ /// be large enough to hold the structure data.
+ /// </remarks>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A pointer to the first byte of source data to copy from</param>
+ /// <param name="target">A reference to the first byte of the memory location to copy the struct data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(void* source, Span<byte> target) where T : unmanaged => CopyStruct((T*)source, target);
+
+ /// <summary>
+ /// Copies the memory of the structure pointed to by the source pointer to the target
+ /// reference data sequence
+ /// </summary>
+ /// <remarks>
+ /// Warning: This is a low level api that cannot do bounds checking on the target sequence. It must
+ /// be large enough to hold the structure data.
+ /// </remarks>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A pointer to the first byte of source data to copy from</param>
+ /// <param name="target">A reference to the first byte of the memory location to copy the struct data to</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CopyStruct<T>(IntPtr source, ref byte target) where T : unmanaged => CopyStruct<T>(source.ToPointer(), ref target);
+
+
+ /// <summary>
+ /// Copies the memory of the structure pointed to by the source reference to the target
+ /// structure reference
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A reference to the source structure to copy from</param>
+ /// <param name="target">A reference to the target structure to copy to</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CloneStruct<T>(ref T source, ref T target) where T : struct => Memmove(ref source, 0, ref target, 0, 1);
+
+ /// <summary>
+ /// Copies the memory of the structure pointed to by the source pointer to the target
+ /// structure pointer
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">A pointer to the source structure to copy from</param>
+ /// <param name="target">A pointer to the target structure to copy to</param>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void CloneStruct<T>(T* source, T* target) where T : unmanaged => Unsafe.CopyBlockUnaligned(target, source, (uint)sizeof(T));
+
/// <summary>
/// Copies data from source memory to destination memory of an umanged data type
@@ -363,30 +672,33 @@ namespace VNLib.Utils.Memory
/// <typeparam name="T">Unmanged type</typeparam>
/// <param name="source">Source data <see cref="ReadOnlySpan{T}"/></param>
/// <param name="dest">Destination <see cref="MemoryHandle{T}"/></param>
+ /// <param name="count">Number of elements to copy</param>
+ /// <param name="sourceOffset">Source offset</param>
/// <param name="destOffset">Dest offset</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public static void Copy<T>(ReadOnlySpan<T> source, IMemoryHandle<T> dest, nuint destOffset) where T: struct
+ public static void Copy<T>(ReadOnlySpan<T> source, int sourceOffset, IMemoryHandle<T> dest, nuint destOffset, int count) where T: struct
{
if (dest is null)
{
throw new ArgumentNullException(nameof(dest));
}
- if (source.IsEmpty)
+ if (count == 0)
{
return;
}
- //Check memhandle bounds
- CheckBounds(dest, destOffset, (uint)source.Length);
+ //Check bounds
+ CheckBounds(source, sourceOffset, count);
+ CheckBounds(dest, destOffset, (uint)count);
//Get byte ref and byte count
- nuint byteCount = ByteCount<T>((uint)source.Length);
+ nuint byteCount = ByteCount<T>((uint)count);
ref T src = ref MemoryMarshal.GetReference(source);
ref T dst = ref dest.GetReference();
//Use memmove by ref
- bool success = MemmoveByRef(ref src, 0, ref dst, (uint)destOffset, byteCount);
+ bool success = MemmoveByRef(ref src, (uint)sourceOffset, ref dst, (uint)destOffset, byteCount);
Debug.Assert(success, "Memmove by ref call failed during a 32bit copy");
}
@@ -395,10 +707,14 @@ namespace VNLib.Utils.Memory
/// </summary>
/// <typeparam name="T">Unmanged type</typeparam>
/// <param name="source">Source data <see cref="ReadOnlyMemory{T}"/></param>
- /// <param name="dest">Destination <see cref="MemoryHandle{T}"/></param>
- /// <param name="destOffset">Dest offset</param>
+ /// <param name="sourceOffset">The element offset in the source memory</param>
+ /// <param name="dest">Destination <see cref="IMemoryHandle{T}"/></param>
+ /// <param name="destOffset">Dest element offset</param>
+ /// <param name="count">The number of elements to copy</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public static void Copy<T>(ReadOnlyMemory<T> source, IMemoryHandle<T> dest, nuint destOffset) where T : struct => Copy(source.Span, dest, destOffset);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Copy<T>(ReadOnlyMemory<T> source, int sourceOffset, IMemoryHandle<T> dest, nuint destOffset, int count) where T : struct
+ => Copy(source.Span, sourceOffset, dest, destOffset, count);
/// <summary>
/// Copies data from source memory to destination memory of an umanged data type
@@ -425,6 +741,7 @@ namespace VNLib.Utils.Memory
//Check source bounds
CheckBounds(source, (nuint)sourceOffset, (nuint)count);
+ CheckBounds(dest, destOffset, count);
//Get byte ref and byte count
nuint byteCount = ByteCount<T>((uint)count);
@@ -523,7 +840,7 @@ namespace VNLib.Utils.Memory
/// <param name="count">The number of elements to copy</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public static void Copy<T>(IMemoryHandle<T> source, nuint sourceOffset, T[] dest, nuint destOffset, nuint count) where T : unmanaged
+ public static void CopyArray<T>(IMemoryHandle<T> source, nuint sourceOffset, T[] dest, nuint destOffset, nuint count) where T : unmanaged
{
if (source is null)
{
@@ -566,7 +883,106 @@ namespace VNLib.Utils.Memory
Buffer.MemoryCopy(srcOffset, dstOffset, byteCount, byteCount);
}
}
-
+
+
+ /// <summary>
+ /// Preforms a fast referrence based copy on very large blocks of memory
+ /// using pinning and pointers only when the number of bytes to copy is
+ /// larger than <see cref="UInt32.MaxValue"/>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="source">The source memory handle to copy data from</param>
+ /// <param name="sourceOffset">The element offset to begin reading from</param>
+ /// <param name="dest">The destination array to write data to</param>
+ /// <param name="destOffset"></param>
+ /// <param name="count">The number of elements to copy</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ArgumentOutOfRangeException"></exception>
+ public static void CopyArray<T>(T[] source, nuint sourceOffset, IMemoryHandle<T> dest, nuint destOffset, nuint count) where T : unmanaged
+ {
+ if (source is null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ if (dest is null)
+ {
+ throw new ArgumentNullException(nameof(dest));
+ }
+
+ if (count == 0)
+ {
+ return;
+ }
+
+ //Check source bounds
+ CheckBounds(source, sourceOffset, count);
+
+ //Check dest bounds
+ CheckBounds(dest, destOffset, count);
+
+ //Get byte refs and byte count
+ nuint byteCount = ByteCount<T>(count);
+ ref T src = ref MemoryMarshal.GetArrayDataReference(source);
+ ref T dst = ref dest.GetReference();
+
+ //Try to memove by ref first, otherwise fallback to pinning
+ if (!MemmoveByRef(ref src, sourceOffset, ref dst, destOffset, byteCount))
+ {
+ //Copying block larger than 32bit must be done with pointers
+ using MemoryHandle srcH = PinArrayAndGetHandle(source, 0);
+ using MemoryHandle dstH = dest.Pin(0);
+
+ //Get pointers and add offsets
+ T* srcOffset = ((T*)srcH.Pointer) + sourceOffset;
+ T* dstOffset = ((T*)dstH.Pointer) + destOffset;
+
+ //Copy memory
+ Buffer.MemoryCopy(srcOffset, dstOffset, byteCount, byteCount);
+ }
+ }
+
+ /// <summary>
+ /// Low level api for copying data from source memory to destination memory of an
+ /// umanged data type.
+ /// </summary>
+ /// <remarks>
+ /// WARNING: It's not possible to do bounds checking when using references. Be sure you
+ /// know what you are doing!
+ /// </remarks>
+ /// <typeparam name="T">The unmanaged or structure type to copy</typeparam>
+ /// <param name="src">A reference to the source data to copy from</param>
+ /// <param name="srcOffset">The offset (in elements) from the reference to begin the copy from</param>
+ /// <param name="dst">The detination</param>
+ /// <param name="dstOffset"></param>
+ /// <param name="elementCount"></param>
+ /// <exception cref="ArgumentException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void Memmove<T>(ref T src, nuint srcOffset, ref T dst, nuint dstOffset, nuint elementCount) where T : struct
+ {
+ if(Unsafe.IsNullRef(ref src))
+ {
+ throw new ArgumentNullException(nameof(src));
+ }
+
+ if(Unsafe.IsNullRef(ref dst))
+ {
+ throw new ArgumentNullException(nameof(dst));
+ }
+
+ if(elementCount == 0)
+ {
+ return;
+ }
+
+ //compute the byte count from the element count
+ nuint byteCount = ByteCount<T>(elementCount);
+
+ if(!MemmoveByRef(ref src, srcOffset, ref dst, dstOffset, byteCount))
+ {
+ throw new ArgumentException("The number of bytes to copy was larger than Uint32.MaxValue and was unsupported on this platform", nameof(elementCount));
+ }
+ }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static bool MemmoveByRef<T>(ref T src, nuint srcOffset, ref T dst, nuint dstOffset, nuint byteCount) where T : struct
@@ -582,10 +998,10 @@ namespace VNLib.Utils.Memory
ref byte srcByte = ref Unsafe.As<T, byte>(ref srcOffsetPtr);
ref byte dstByte = ref Unsafe.As<T, byte>(ref dstOffsetPtr);
- if (_sysMemmove != null)
+ if (_clrMemmove != null)
{
//Call sysinternal memmove
- _sysMemmove(ref dstByte, ref srcByte, byteCount);
+ _clrMemmove(ref dstByte, ref srcByte, byteCount);
return true;
}
else if(byteCount < uint.MaxValue)
@@ -697,8 +1113,11 @@ namespace VNLib.Utils.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CheckBounds<T>(ReadOnlySpan<T> block, int offset, int count)
{
- //Call slice and discard to raise exception
- _ = block.Slice(offset, count);
+ //Check span bounds
+ if (offset < 0 || count < 0 || offset + count > block.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "Offset or count are beyond the range of the supplied memory handle");
+ }
}
/// <summary>
@@ -713,8 +1132,11 @@ namespace VNLib.Utils.Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CheckBounds<T>(Span<T> block, int offset, int count)
{
- //Call slice and discard to raise exception
- _ = block.Slice(offset, count);
+ //Check span bounds
+ if (offset < 0 || count < 0 || offset + count > block.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "Offset or count are beyond the range of the supplied memory handle");
+ }
}
/// <summary>
diff --git a/lib/Utils/src/Memory/MemoryUtilAlloc.cs b/lib/Utils/src/Memory/MemoryUtilAlloc.cs
index 9e305d0..bc0f35e 100644
--- a/lib/Utils/src/Memory/MemoryUtilAlloc.cs
+++ b/lib/Utils/src/Memory/MemoryUtilAlloc.cs
@@ -24,6 +24,7 @@
using System;
using System.Buffers;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
using VNLib.Utils.Extensions;
@@ -162,6 +163,86 @@ namespace VNLib.Utils.Memory
return SafeAlloc<T>((int)np, zero);
}
+ /// <summary>
+ /// Allocates a structure of the specified type on the specified
+ /// unmanged heap and optionally zero's it's memory
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="heap">The heap to allocate structure memory from</param>
+ /// <param name="zero">A value that indicates if the structure memory should be zeroed before returning</param>
+ /// <returns>A pointer to the structure ready for use.</returns>
+ /// <remarks>Allocations must be freed with <see cref="StructFree{T}(IUnmangedHeap, T*)"/></remarks>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T* StructAlloc<T>(IUnmangedHeap heap, bool zero) where T : unmanaged
+ {
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
+ return (T*)heap.Alloc(1, (nuint)sizeof(T), zero);
+ }
+
+ /// <summary>
+ /// Allocates a structure of the specified type on the specified
+ /// unmanged heap and optionally zero's it's memory, then returns
+ /// and reference to the heap allocated structure.
+ /// </summary>
+ /// <typeparam name="T">The structure type</typeparam>
+ /// <param name="heap">The heap to allocate structure memory from</param>
+ /// <param name="zero">A value that indicates if the structure memory should be zeroed before returning</param>
+ /// <returns>A reference to the heap allocated structure</returns>
+ /// <remarks>Allocations must be freed with <see cref="StructFreeRef{T}(IUnmangedHeap, ref T)"/></remarks>
+ /// <exception cref="OutOfMemoryException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref T StructAllocRef<T>(IUnmangedHeap heap, bool zero) where T : unmanaged
+ {
+ //Alloc structure
+ T* ptr = StructAlloc<T>(heap, zero);
+ //Get a reference and assign it
+ return ref Unsafe.AsRef<T>(ptr);
+ }
+
+ /// <summary>
+ /// Frees a structure allocated with <see cref="StructAlloc{T}(IUnmangedHeap, bool)"/>
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="heap">Heap the structure was allocated from to free it back to</param>
+ /// <param name="structPtr">A pointer to the unmanaged structure to free</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void StructFree<T>(IUnmangedHeap heap, T* structPtr) where T : unmanaged => StructFree(heap, (void*)structPtr);
+
+ /// <summary>
+ /// Frees a structure allocated with <see cref="StructAllocRef{T}(IUnmangedHeap, bool)"/>
+ /// by its reference.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <param name="heap">Heap the structure was allocated from to free it back to</param>
+ /// <param name="structRef">A reference to the unmanaged structure to free</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void StructFreeRef<T>(IUnmangedHeap heap, ref T structRef) where T : unmanaged => StructFree(heap, Unsafe.AsPointer(ref structRef));
+
+ /// <summary>
+ /// Frees a structure allocated with <see cref="StructAlloc{T}(IUnmangedHeap, bool)"/>
+ /// </summary>
+ /// <param name="heap">Heap the structure was allocated from to free it back to</param>
+ /// <param name="structPtr">A pointer to the unmanaged structure to free</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ public static void StructFree(IUnmangedHeap heap, void* structPtr)
+ {
+ _ = heap ?? throw new ArgumentNullException(nameof(heap));
+ if(structPtr == null)
+ {
+ throw new ArgumentNullException(nameof(structPtr));
+ }
+ //Get intpointer
+ IntPtr ptr = (IntPtr)structPtr;
+ //Free
+ bool isFree = heap.Free(ref ptr);
+ Debug.Assert(isFree, $"Structure free failed for heap {heap.GetHashCode()}, struct address {ptr:x}");
+ }
+
#endregion
#region ByteOptimimzations
diff --git a/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs b/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs
index 3c9bde7..82f9c3a 100644
--- a/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs
+++ b/lib/Utils/tests/Async/AsyncAccessSerializerTests.cs
@@ -147,13 +147,15 @@ namespace VNLib.Utils.Async.Tests
int maxCount = 128;
string serialized = "";
- using CancellationTokenSource cts = new(500);
+ using CancellationTokenSource cts = new(10000);
Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () =>
{
//Take a lock then random delay, then release
await serializer.WaitAsync(DEFAULT_KEY, cts.Token);
+ await Task.Delay(1);
+
//Increment count
serialized += "0";
@@ -171,9 +173,7 @@ namespace VNLib.Utils.Async.Tests
public void SimplePerformanceComparisonTest()
{
const string DEFAULT_KEY = "default";
-
- //Alloc serailzer base on string
- IAsyncAccessSerializer<string> serializer = new AsyncAccessSerializer<string>(100, 100, StringComparer.Ordinal);
+
const int maxCount = 128;
const int itterations = 20;
@@ -182,55 +182,60 @@ namespace VNLib.Utils.Async.Tests
using CancellationTokenSource cts = new(500);
+ using SemaphoreSlim slim = new(1,1);
+
for (int i = 0; i < itterations; i++)
{
test = "";
- timer.Restart();
+ timer.Restart();
Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () =>
{
//Take a lock then random delay, then release
- await serializer.WaitAsync(DEFAULT_KEY, cts.Token);
+ await slim.WaitAsync(cts.Token);
//Increment count
test += "0";
- serializer.Release(DEFAULT_KEY);
-
+ slim.Release();
})).ToArray();
Task.WaitAll(asyncArr);
timer.Stop();
- Trace.WriteLine($"Async serialzier test completed in {timer.ElapsedTicks / 10} microseconds");
+ Trace.WriteLine($"SemaphoreSlim test completed in {timer.ElapsedTicks / 10} microseconds");
+
Assert.AreEqual(maxCount, test.Length);
- }
- using SemaphoreSlim slim = new(1,1);
+ }
+
+ //Alloc serailzer base on string
+ IAsyncAccessSerializer<string> serializer = new AsyncAccessSerializer<string>(100, 100, StringComparer.Ordinal);
for (int i = 0; i < itterations; i++)
{
+
test = "";
timer.Restart();
Task[] asyncArr = new int[maxCount].Select(p => Task.Run(async () =>
{
//Take a lock then random delay, then release
- await slim.WaitAsync(cts.Token);
+ await serializer.WaitAsync(DEFAULT_KEY, cts.Token);
//Increment count
test += "0";
- slim.Release();
+ serializer.Release(DEFAULT_KEY);
+
})).ToArray();
Task.WaitAll(asyncArr);
timer.Stop();
- Trace.WriteLine($"SemaphoreSlim test completed in {timer.ElapsedTicks / 10} microseconds");
-
+ Trace.WriteLine($"Async serialzier test completed in {timer.ElapsedTicks / 10} microseconds");
Assert.AreEqual(maxCount, test.Length);
}
}
diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs
index 64f94ff..e63dc03 100644
--- a/lib/Utils/tests/Memory/MemoryUtilTests.cs
+++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Buffers;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
@@ -13,7 +14,7 @@ namespace VNLib.Utils.Memory.Tests
[TestClass()]
public class MemoryUtilTests
{
- const int ZERO_TEST_LOOP_ITERATIONS = 1000000;
+ const int ZERO_TEST_LOOP_ITERATIONS = 100000;
const int ZERO_TEST_MAX_BUFFER_SIZE = 10 * 1024;
[TestMethod()]
@@ -141,6 +142,7 @@ namespace VNLib.Utils.Memory.Tests
_ = handle.Span;
_ = handle.Length;
_ = handle.IntLength;
+ _ = handle.GetReference();
//Test span pointer against pinned handle
using (MemoryHandle pinned = handle.Pin(0))
@@ -151,6 +153,11 @@ namespace VNLib.Utils.Memory.Tests
}
}
+ //Test references are equal
+ ref byte spanRef = ref MemoryMarshal.GetReference(handle.Span);
+ ref byte handleRef = ref handle.GetReference();
+ Assert.IsTrue(Unsafe.AreSame(ref spanRef, ref handleRef));
+
//Test negative pin
Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.Pin(-1));
@@ -248,6 +255,7 @@ namespace VNLib.Utils.Memory.Tests
_ = handle.Span;
_ = handle.Length;
_ = handle.GetIntLength();
+ _ = handle.GetReference();
//Test span pointer against pinned handle
using (MemoryHandle pinned = handle.Pin(0))
@@ -258,6 +266,11 @@ namespace VNLib.Utils.Memory.Tests
}
}
+ //Test references are equal
+ ref byte spanRef = ref MemoryMarshal.GetReference(handle.Span);
+ ref byte handleRef = ref handle.GetReference();
+ Assert.IsTrue(Unsafe.AreSame(ref spanRef, ref handleRef));
+
//Test negative pin
Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.Pin(-1));
@@ -265,11 +278,9 @@ namespace VNLib.Utils.Memory.Tests
Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = handle.Pin(1024));
}
-
//Negative value
Assert.ThrowsException<ArgumentException>(() => _ = MemoryUtil.SafeAlloc<byte>(-1));
-
/*
* Alloc random sized blocks in a loop, confirm they are empty
* then fill the block with random data before freeing it back to
@@ -451,7 +462,6 @@ namespace VNLib.Utils.Memory.Tests
Assert.IsTrue(pageSize == Environment.SystemPageSize);
}
-
[TestMethod()]
public void AllocNearestPage()
{
@@ -534,5 +544,229 @@ namespace VNLib.Utils.Memory.Tests
Assert.IsTrue(byteSize == (nuint)(Environment.SystemPageSize * 2));
}
}
+
+ [TestMethod]
+ public unsafe void HeapAllocStructureRefTest()
+ {
+ //Alloc a structure as a reference
+ ref TestStruct str = ref MemoryUtil.StructAllocRef<TestStruct>(MemoryUtil.Shared, true);
+
+ str.X = 899;
+ str.Y = 458;
+
+ //recover a pointer from the reference
+ TestStruct* ptr = (TestStruct*)Unsafe.AsPointer(ref str);
+
+ //Confirm the values modified on the ref are reflected in the pointer deref
+ Assert.IsTrue(ptr->X == 899);
+ Assert.IsTrue(ptr->Y == 458);
+
+ //We can confirm the references are the same
+ Assert.IsTrue(Unsafe.AreSame(ref (*ptr), ref str));
+
+ //free the structure
+ MemoryUtil.StructFreeRef(MemoryUtil.Shared, ref str);
+ }
+
+ [TestMethod]
+ public unsafe void StructCopyTest()
+ {
+ //Alloc a structure as a reference
+ ref TestStruct test1 = ref MemoryUtil.Shared.StructAllocRef<TestStruct>(true);
+
+ //Confirm test 1 is zeroed
+ Assert.IsTrue(test1.X == 0);
+ Assert.IsTrue(test1.Y == 0);
+
+ test1.X = 899;
+ test1.Y = 458;
+
+ //Test 2 blongs on the stack
+ TestStruct test2 = default;
+ MemoryUtil.CloneStruct(ref test1, ref test2);
+
+ Assert.IsTrue(test1.X == test2.X);
+ Assert.IsTrue(test1.Y == test2.Y);
+
+ //Zero out test 1
+ MemoryUtil.ZeroStruct(ref test1);
+
+ //Confirm test 1 is zeroed
+ Assert.IsTrue(test1.X == 0);
+ Assert.IsTrue(test1.Y == 0);
+
+ //Confirm test 2 is unmodified
+ Assert.IsTrue(test2.X == 899);
+ Assert.IsTrue(test2.Y == 458);
+
+ //Alloc buffer to write struct data to
+ Span<byte> buffer = stackalloc byte[sizeof(TestStruct)];
+ MemoryUtil.CopyStruct(ref test2, buffer);
+
+ //Read the x value as the first integer within the buffer
+ int xCoord = MemoryMarshal.Read<int>(buffer);
+ Assert.IsTrue(xCoord == 899);
+
+ //Clone to test 3 (stack alloc struct)
+ TestStruct test3 = default;
+ MemoryUtil.CopyStruct(buffer, ref test3);
+
+ //Check that test2 and test3 are the same
+ Assert.IsTrue(test2.X == test3.X);
+ Assert.IsTrue(test2.Y == test3.Y);
+
+ //Copy back to test 1
+ MemoryUtil.CopyStruct(buffer, ref test1);
+
+ //Check that test1 and test2 are now the same
+ Assert.IsTrue(test1.X == test2.X);
+ Assert.IsTrue(test1.Y == test2.Y);
+
+ //free the structure
+ MemoryUtil.StructFreeRef(MemoryUtil.Shared, ref test1);
+ }
+
+ [TestMethod]
+ public void CopyTest()
+ {
+ /*
+ * Testing the MemoryUtil.Copy/CopyArray family of functions and their overloads
+ */
+
+ using IMemoryHandle<byte> testHandle = MemoryUtil.SafeAlloc<byte>(16, false);
+ using IMemoryHandle<byte> emptyHandle = new MemoryHandle<byte>();
+ byte[] testArray = new byte[16];
+
+ ReadOnlyMemory<byte> testMem = testArray;
+ Memory<byte> testMem2 = testArray;
+
+ /*
+ * Test null reference checks
+ */
+ Assert.ThrowsException<ArgumentNullException>(() => MemoryUtil.Copy((IMemoryHandle<byte>)null, 0, testHandle, 0, 1));
+ Assert.ThrowsException<ArgumentNullException>(() => MemoryUtil.Copy(testHandle, 0, (IMemoryHandle<byte>)null, 0, 1));
+
+ Assert.ThrowsException<ArgumentNullException>(() => MemoryUtil.Copy(ReadOnlyMemory<byte>.Empty, 0, null, 0, 1));
+ Assert.ThrowsException<ArgumentNullException>(() => MemoryUtil.Copy(ReadOnlySpan<byte>.Empty, 0, null, 0, 1));
+
+ Assert.ThrowsException<ArgumentNullException>(() => MemoryUtil.CopyArray(null, 0, testArray, 0, 1));
+ Assert.ThrowsException<ArgumentNullException>(() => MemoryUtil.CopyArray(testHandle, 0, null, 0, 1));
+
+
+ /*
+ * Test out of range values for empty blocks
+ */
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, emptyHandle, 0, 1));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(emptyHandle, 0, testHandle, 0, 1));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, Memory<byte>.Empty, 0, 1));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(ReadOnlyMemory<byte>.Empty, 0, testHandle, 0, 1));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, Span<byte>.Empty, 0, 1));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(ReadOnlySpan<byte>.Empty, 0, testHandle, 0, 1));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.CopyArray(Array.Empty<byte>(), 0, testHandle, 0, 1));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.CopyArray(testHandle, 0, Array.Empty<byte>(), 0, 1));
+
+
+ /*
+ * Check for out of range with valid handles
+ */
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testHandle, 0, 17));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testHandle, 1, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 1, testHandle, 0, 16));
+
+ //Test with real values using memory
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testMem2, 0, 17));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testMem2, 1, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 1, testMem2, 0, 16));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem, 0, testHandle, 0, 17));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem, 0, testHandle, 1, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem, 1, testHandle, 0, 16));
+
+ //Test with real values using span
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testMem2.Span, 0, 17));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testMem2.Span, 1, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 1, testMem2.Span, 0, 16));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem.Span, 0, testHandle, 0, 17));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem.Span, 0, testHandle, 1, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem.Span, 1, testHandle, 0, 16));
+
+ //Test with real values using array
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.CopyArray(testHandle, 0, testArray, 0, 17));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.CopyArray(testHandle, 0, testArray, 1, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.CopyArray(testHandle, 1, testArray, 0, 16));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.CopyArray(testArray, 0, testHandle, 0, 17));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.CopyArray(testArray, 0, testHandle, 1, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.CopyArray(testArray, 1, testHandle, 0, 16));
+
+ /*
+ * Test inbounds test values
+ */
+ MemoryUtil.Copy(testHandle, 0, testHandle, 0, 16);
+ MemoryUtil.Copy(testHandle, 0, testHandle, 1, 15);
+ MemoryUtil.Copy(testHandle, 1, testHandle, 0, 15);
+
+ //Memory-inbounds
+ MemoryUtil.Copy(testHandle, 0, testMem2, 0, 16);
+ MemoryUtil.Copy(testHandle, 0, testMem2, 1, 15);
+ MemoryUtil.Copy(testHandle, 1, testMem2, 0, 15);
+
+ MemoryUtil.Copy(testMem, 0, testHandle, 0, 16);
+ MemoryUtil.Copy(testMem, 0, testHandle, 1, 15);
+ MemoryUtil.Copy(testMem, 1, testHandle, 0, 15);
+
+ //Span in-bounds
+ MemoryUtil.Copy(testHandle, 0, testMem2.Span, 0, 16);
+ MemoryUtil.Copy(testHandle, 0, testMem2.Span, 1, 15);
+ MemoryUtil.Copy(testHandle, 1, testMem2.Span, 0, 15);
+
+ MemoryUtil.Copy(testMem.Span, 0, testHandle, 0, 16);
+ MemoryUtil.Copy(testMem.Span, 0, testHandle, 1, 15);
+ MemoryUtil.Copy(testMem.Span, 1, testHandle, 0, 15);
+
+ //Array in-bounds
+ MemoryUtil.CopyArray(testHandle, 0, testArray, 0, 16);
+ MemoryUtil.CopyArray(testHandle, 0, testArray, 1, 15);
+ MemoryUtil.CopyArray(testHandle, 1, testArray, 0, 15);
+
+ MemoryUtil.CopyArray(testArray, 0, testHandle, 0, 16);
+ MemoryUtil.CopyArray(testArray, 0, testHandle, 1, 15);
+ MemoryUtil.CopyArray(testArray, 1, testHandle, 0, 15);
+
+ /*
+ * Test nop for zero-length copies
+ */
+
+ MemoryUtil.Copy(testHandle, 0, testHandle, 0, 0);
+ MemoryUtil.Copy(testHandle, 0, testMem2, 0, 0);
+ MemoryUtil.Copy(testHandle, 0, testMem2.Span, 0, 0);
+ MemoryUtil.Copy(testMem, 0, testHandle, 0, 0);
+ MemoryUtil.Copy(testMem.Span, 0, testHandle, 0, 0);
+ MemoryUtil.CopyArray(testHandle, 0, testArray, 0, 0);
+ MemoryUtil.CopyArray(testArray, 0, testHandle, 0, 0);
+
+ /*
+ * Test negative values for span/memory overloads that
+ * accept integers
+ */
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, -1, testMem2, 0, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testMem2, -1, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testMem2, 0, -1));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem, -1, testHandle, 0, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem, 0, testHandle, 0, -1));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, -1, testMem2.Span, 0, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testMem2.Span, -1, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testHandle, 0, testMem2.Span, 0, -1));
+
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem.Span, -1, testHandle, 0, 16));
+ Assert.ThrowsException<ArgumentOutOfRangeException>(() => MemoryUtil.Copy(testMem.Span, 0, testHandle, 0, -1));
+ }
}
} \ No newline at end of file