diff options
Diffstat (limited to 'lib/Utils')
-rw-r--r-- | lib/Utils/src/Extensions/MemoryExtensions.cs | 60 | ||||
-rw-r--r-- | lib/Utils/src/IO/VnMemoryStream.cs | 6 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryUtil.cs | 468 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryUtilAlloc.cs | 81 | ||||
-rw-r--r-- | lib/Utils/tests/Async/AsyncAccessSerializerTests.cs | 35 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/MemoryUtilTests.cs | 242 |
6 files changed, 825 insertions, 67 deletions
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 |