diff options
author | vnugent <public@vaughnnugent.com> | 2023-11-02 01:49:02 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-11-02 01:49:02 -0400 |
commit | 9e3dd9be0f0ec7aaef1a719f09f96425e66369df (patch) | |
tree | 59b8bd4ace8750327db80823fa1e5eccdf44bc74 /lib/Utils | |
parent | eafefadc4b858e5b5be481662a2b0c8e47a43bf4 (diff) |
may have gottem carried away
Diffstat (limited to 'lib/Utils')
22 files changed, 732 insertions, 352 deletions
diff --git a/lib/Utils/src/Async/IAsyncEventSink.cs b/lib/Utils/src/Async/IAsyncEventSink.cs new file mode 100644 index 0000000..634b3e6 --- /dev/null +++ b/lib/Utils/src/Async/IAsyncEventSink.cs @@ -0,0 +1,48 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: IAsyncEventSink.cs +* +* IAsyncEventSink.cs is part of VNLib.Utils which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Utils is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Utils is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. +*/ + +namespace VNLib.Utils.Async +{ + /// <summary> + /// A type that receives events from asynchronous event sources and publishes + /// them to subscribers. + /// </summary> + /// <typeparam name="T">The event type</typeparam> + public interface IAsyncEventSink<T> + { + /// <summary> + /// Publishes a single event to all subscribers + /// </summary> + /// <param name="evnt">The event to publish</param> + /// <returns>A value that indicates if the event was successfully published to subscribers</returns> + bool PublishEvent(T evnt); + + /// <summary> + /// Publishes an array of events to all subscribers + /// </summary> + /// <param name="events">The array of events to publish</param> + /// <returns>A value that indicates if the events were successfully published to subscribers</returns> + bool PublishEvents(T[] events); + } +} diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs index 6525db4..d21ceee 100644 --- a/lib/Utils/src/Extensions/MemoryExtensions.cs +++ b/lib/Utils/src/Extensions/MemoryExtensions.cs @@ -48,11 +48,7 @@ namespace VNLib.Utils.Extensions /// <param name="size">The minimum size array to allocate</param> /// <param name="zero">Should elements from 0 to size be set to default(T)</param> /// <returns>A new <see cref="OpenResourceHandle{T}"/> encapsulating the rented array</returns> - public static UnsafeMemoryHandle<T> Lease<T>(this ArrayPool<T> pool, int size, bool zero = false) where T: unmanaged - { - //Pool buffer handles are considered "safe" so im reusing code for now - return new(pool, size, zero); - } + public static UnsafeMemoryHandle<T> Lease<T>(this ArrayPool<T> pool, int size, bool zero = false) where T : unmanaged => new(pool, size, zero); /// <summary> /// Retreives a buffer that is at least the reqested length, and clears the array from 0-size. @@ -81,32 +77,10 @@ namespace VNLib.Utils.Extensions /// </summary> /// <returns>The string representation of the buffer</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ToString<T>(this T charBuffer) where T: IMemoryHandle<char> - { - return charBuffer.Span.ToString(); - } - - /// <summary> - /// Wraps the <see cref="MemoryHandle{T}"/> instance in System.Buffers.MemoryManager - /// wrapper to provide <see cref="Memory{T}"/> buffers from umanaged handles. - /// </summary> - /// <typeparam name="T">The unmanaged data type</typeparam> - /// <param name="handle"></param> - /// <param name="ownsHandle"> - /// A value that indicates if the new <see cref="MemoryManager{T}"/> owns the handle. - /// When <c>true</c>, the new <see cref="MemoryManager{T}"/> maintains the lifetime of the handle. - /// </param> - /// <returns>The <see cref="MemoryManager{T}"/> wrapper</returns> - /// <remarks>NOTE: This wrapper now manages the lifetime of the current handle</remarks> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryManager<T> ToMemoryManager<T>(this MemoryHandle<T> handle, bool ownsHandle = true) where T : unmanaged - { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); - return new SysBufferMemoryManager<T>(handle, ownsHandle); - } + public static string ToString<T>(this T charBuffer) where T : IMemoryHandle<char> => charBuffer.Span.ToString(); /// <summary> - /// Wraps the <see cref="VnTempBuffer{T}"/> instance in System.Buffers.MemoryManager + /// Wraps the <see cref="IMemoryHandle{T}"/> instance in System.Buffers.MemoryManager /// wrapper to provide <see cref="Memory{T}"/> buffers from umanaged handles. /// </summary> /// <typeparam name="T">The unmanaged data type</typeparam> @@ -117,15 +91,12 @@ namespace VNLib.Utils.Extensions /// </param> /// <returns>The <see cref="MemoryManager{T}"/> wrapper</returns> /// <remarks>NOTE: This wrapper now manages the lifetime of the current handle</remarks> + /// <exception cref="ArgumentNullException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryManager<T> ToMemoryManager<T>(this VnTempBuffer<T> handle, bool ownsHandle = true) where T : unmanaged - { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); - return new SysBufferMemoryManager<T>(handle, ownsHandle); - } + public static MemoryManager<T> ToMemoryManager<T>(this IMemoryHandle<T> handle, bool ownsHandle) => new SysBufferMemoryManager<T>(handle, ownsHandle); /// <summary> - /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="Win32PrivateHeap"/> instance + /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="IUnmangedHeap"/> instance /// of the specified number of elements /// </summary> /// <typeparam name="T">The unmanaged data type</typeparam> @@ -133,10 +104,14 @@ namespace VNLib.Utils.Extensions /// <param name="size">The number of elements to allocate on the heap</param> /// <param name="zero">Optionally zeros conents of the block when allocated</param> /// <returns>The <see cref="MemoryManager{T}"/> wrapper around the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ObjectDisposedException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MemoryManager<T> DirectAlloc<T>(this IUnmangedHeap heap, nuint size, bool zero = false) where T : unmanaged { - return new SysBufferMemoryManager<T>(heap, size, zero); + MemoryHandle<T> handle = heap.Alloc<T>(size, zero); + return new SysBufferMemoryManager<T>(handle, true); } /// <summary> @@ -163,10 +138,10 @@ namespace VNLib.Utils.Extensions /// </returns> //Method only exists for consistancy since unsafe handles are always 32bit [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T: unmanaged => handle.IntLength; + public static int GetIntLength<T>(this in UnsafeMemoryHandle<T> handle) where T : unmanaged => handle.IntLength; /// <summary> - /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="Win32PrivateHeap"/> instance + /// Allows direct allocation of a fixed size <see cref="MemoryManager{T}"/> from a <see cref="IUnmangedHeap"/> instance /// of the specified number of elements /// </summary> /// <typeparam name="T">The unmanaged data type</typeparam> @@ -181,6 +156,7 @@ namespace VNLib.Utils.Extensions { return size >= 0 ? DirectAlloc<T>(heap, (nuint)size, zero) : throw new ArgumentOutOfRangeException(nameof(size), "The size paramter must be a positive integer"); } + /// <summary> /// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks /// </summary> @@ -194,6 +170,7 @@ namespace VNLib.Utils.Extensions { return elements >= 0 ? memory.GetOffset((nuint)elements) : throw new ArgumentOutOfRangeException(nameof(elements), "The elements paramter must be a positive integer"); } + /// <summary> /// Resizes the current handle on the heap /// </summary> @@ -204,7 +181,7 @@ namespace VNLib.Utils.Extensions /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Resize<T>(this MemoryHandle<T> memory, nint elements) where T : unmanaged + public static void Resize<T>(this IResizeableMemoryHandle<T> memory, nint elements) { if (elements < 0) { @@ -224,7 +201,7 @@ namespace VNLib.Utils.Extensions /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResizeIfSmaller<T>(this MemoryHandle<T> handle, nint count) where T : unmanaged + public static void ResizeIfSmaller<T>(this IResizeableMemoryHandle<T> handle, nint count) { if(count < 0) { @@ -244,7 +221,7 @@ namespace VNLib.Utils.Extensions /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResizeIfSmaller<T>(this MemoryHandle<T> handle, nuint count) where T : unmanaged + public static void ResizeIfSmaller<T>(this IResizeableMemoryHandle<T> handle, nuint count) { //Check handle size if(handle.Length < count) @@ -254,6 +231,52 @@ namespace VNLib.Utils.Extensions } } + /// <summary> + /// Gets a reference to the element at the specified offset from the base + /// address of the <see cref="MemoryHandle{T}"/> + /// </summary> + /// <param name="block"></param> + /// <param name="offset">The element offset from the base address to add to the returned reference</param> + /// <returns>The reference to the item at the desired offset</returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetOffsetRef<T>(this IMemoryHandle<T> block, nuint offset) + { + _ = block ?? throw new ArgumentNullException(nameof(block)); + + if (offset >= block.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + return ref Unsafe.Add(ref block.GetReference(), offset); + } + + /// <summary> + /// Gets a reference to the element at the specified offset from the base + /// address of the <see cref="MemoryHandle{T}"/> and casts it to a byte reference + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="block"></param> + /// <param name="offset">The number of elements to offset the base reference by</param> + /// <returns>The reinterpreted byte reference at the first byte of the element offset</returns> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref byte GetByteOffsetRef<T>(this IMemoryHandle<T> block, nuint offset) + { + _ = block ?? throw new ArgumentNullException(nameof(block)); + + if (offset >= block.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + //Get the base reference, then offset by the desired number of elements and cast to a byte reference + ref T baseRef = ref block.GetReference(); + ref T offsetRef = ref Unsafe.Add(ref baseRef, offset); + return ref Unsafe.As<T, byte>(ref offsetRef); + } /// <summary> /// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/> @@ -265,7 +288,7 @@ namespace VNLib.Utils.Extensions /// <returns>The offset span</returns> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, nuint offset, int size) where T: unmanaged + public static Span<T> GetOffsetSpan<T>(this IMemoryHandle<T> block, nuint offset, int size) { _ = block ?? throw new ArgumentNullException(nameof(block)); @@ -282,9 +305,10 @@ namespace VNLib.Utils.Extensions MemoryUtil.CheckBounds(block, offset, (nuint)size); //Get long offset from the destination handle - void* ofPtr = block.GetOffset(offset); - return new Span<T>(ofPtr, size); + ref T ofPtr = ref GetOffsetRef(block, offset); + return MemoryMarshal.CreateSpan(ref ofPtr, size); } + /// <summary> /// Gets a 64bit friendly span offset for the current <see cref="MemoryHandle{T}"/> /// </summary> @@ -295,7 +319,7 @@ namespace VNLib.Utils.Extensions /// <returns>The offset span</returns> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe Span<T> GetOffsetSpan<T>(this MemoryHandle<T> block, nint offset, int size) where T : unmanaged + public static unsafe Span<T> GetOffsetSpan<T>(this IMemoryHandle<T> block, nint offset, int size) { return offset >= 0 ? block.GetOffsetSpan((nuint)offset, size) : throw new ArgumentOutOfRangeException(nameof(offset)); } @@ -310,7 +334,7 @@ namespace VNLib.Utils.Extensions /// <returns>The new <see cref="SubSequence{T}"/> within the block</returns> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SubSequence<T> GetSubSequence<T>(this MemoryHandle<T> block, nuint offset, int size) where T : unmanaged => new (block, offset, size); + public static SubSequence<T> GetSubSequence<T>(this IMemoryHandle<T> block, nuint offset, int size) => new (block, offset, size); /// <summary> /// Gets a <see cref="SubSequence{T}"/> window within the current block @@ -322,7 +346,7 @@ namespace VNLib.Utils.Extensions /// <returns>The new <see cref="SubSequence{T}"/> within the block</returns> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SubSequence<T> GetSubSequence<T>(this MemoryHandle<T> block, nint offset, int size) where T : unmanaged + public static SubSequence<T> GetSubSequence<T>(this IMemoryHandle<T> block, nint offset, int size) { return offset >= 0 ? new (block, (nuint)offset, size) : throw new ArgumentOutOfRangeException(nameof(offset)); } @@ -334,10 +358,7 @@ namespace VNLib.Utils.Extensions /// <typeparam name="T">The unmanged data type to provide allocations from</typeparam> /// <returns>The new <see cref="MemoryPool{T}"/> heap wrapper.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryPool<T> ToPool<T>(this IUnmangedHeap heap, int maxBufferSize = int.MaxValue) where T : unmanaged - { - return new PrivateBuffersMemoryPool<T>(heap, maxBufferSize); - } + 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 @@ -349,14 +370,8 @@ namespace VNLib.Utils.Extensions /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ObjectDisposedException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe T* StructAlloc<T>(this IUnmangedHeap heap) where T : unmanaged - { - //Allocate the struct on the heap and zero memory it points to - IntPtr handle = heap.Alloc(1, (nuint)sizeof(T), true); - //returns the handle - return (T*)handle; - } - + public static unsafe T* StructAlloc<T>(this IUnmangedHeap heap) where T : unmanaged => (T*)heap.Alloc(1, (nuint)sizeof(T), true); + /// <summary> /// Frees a structure at the specified address from the this heap. /// This must be the same heap the structure was allocated from @@ -468,7 +483,7 @@ namespace VNLib.Utils.Extensions /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ObjectDisposedException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteAndResize<T>(this MemoryHandle<T> handle, ReadOnlySpan<T> input) where T: unmanaged + public static void WriteAndResize<T>(this IResizeableMemoryHandle<T> handle, ReadOnlySpan<T> input) where T: unmanaged { handle.Resize(input.Length); MemoryUtil.Copy(input, handle, 0); @@ -618,6 +633,7 @@ namespace VNLib.Utils.Extensions { return GetBytes(enc, chars.AsSpan(offset, charCount), ref writer, flush); } + /// <summary> /// Encodes a set of characters in the input characters span and any characters /// in the internal buffer into a sequence of bytes that are stored in the input @@ -639,6 +655,7 @@ namespace VNLib.Utils.Extensions writer.Advance(written); return written; } + /// <summary> /// Encodes a set of characters in the input characters span and any characters /// in the internal buffer into a sequence of bytes that are stored in the input @@ -657,6 +674,7 @@ namespace VNLib.Utils.Extensions writer.Advance(written); return written; } + /// <summary> /// Decodes a character buffer in the input characters span and any characters /// in the internal buffer into a sequence of bytes that are stored in the input @@ -683,6 +701,7 @@ namespace VNLib.Utils.Extensions /// <returns>A <see cref="PrivateString"/> instance that owns the underlying string memory</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PrivateString ToPrivate(this ref ForwardOnlyWriter<char> buffer) => new(buffer.ToString(), true); + /// <summary> /// Gets a <see cref="Span{T}"/> over the modified section of the internal buffer /// </summary> @@ -711,6 +730,7 @@ namespace VNLib.Utils.Extensions Range sliceRange = new(start, arr.Length - start); return RuntimeHelpers.GetSubArray(arr, sliceRange); } + /// <summary> /// Slices the current array by the specified starting offset to including the /// speciifed number of items diff --git a/lib/Utils/src/IO/VnMemoryStream.cs b/lib/Utils/src/IO/VnMemoryStream.cs index 97cef03..7ac56a6 100644 --- a/lib/Utils/src/IO/VnMemoryStream.cs +++ b/lib/Utils/src/IO/VnMemoryStream.cs @@ -24,7 +24,9 @@ using System; using System.IO; +using System.Buffers; using System.Threading; +using System.Diagnostics; using System.Threading.Tasks; using System.Runtime.InteropServices; @@ -33,6 +35,7 @@ using VNLib.Utils.Extensions; namespace VNLib.Utils.IO { + /// <summary> /// Provides an unmanaged memory stream. Desigend to help reduce garbage collector load for /// high frequency memory operations. Similar to <see cref="UnmanagedMemoryStream"/> @@ -44,35 +47,39 @@ namespace VNLib.Utils.IO private bool _isReadonly; //Memory - private readonly MemoryHandle<byte> _buffer; + private readonly IResizeableMemoryHandle<byte> _buffer; //Default owns handle private readonly bool OwnsHandle = true; /// <summary> /// Creates a new <see cref="VnMemoryStream"/> pointing to the begining of memory, and consumes the handle. /// </summary> - /// <param name="handle"><see cref="MemoryHandle{T}"/> to consume</param> + /// <param name="handle"><see cref="IResizeableMemoryHandle{T}"/> to consume</param> /// <param name="length">Length of the stream</param> /// <param name="readOnly">Should the stream be readonly?</param> /// <exception cref="ArgumentException"></exception> /// <returns>A <see cref="VnMemoryStream"/> wrapper to access the handle data</returns> - public static VnMemoryStream ConsumeHandle(MemoryHandle<byte> handle, nint length, bool readOnly) => FromHandle(handle, true, length, readOnly); + public static VnMemoryStream ConsumeHandle(IResizeableMemoryHandle<byte> handle, nint length, bool readOnly) => FromHandle(handle, true, length, readOnly); /// <summary> /// Creates a new <see cref="VnMemoryStream"/> from the supplied memory handle /// of the initial length. This function also accepts a value that indicates if this stream /// owns the memory handle, which will cause it to be disposed when the stream is disposed. /// </summary> - /// <param name="handle"><see cref="MemoryHandle{T}"/> to consume</param> + /// <param name="handle"><see cref="IResizeableMemoryHandle{T}"/> to consume</param> /// <param name="length">The initial length of the stream</param> /// <param name="readOnly">Should the stream be readonly?</param> /// <param name="ownsHandle">A value that indicates if the current stream owns the memory handle</param> /// <exception cref="ArgumentException"></exception> /// <returns>A <see cref="VnMemoryStream"/> wrapper to access the handle data</returns> - public static VnMemoryStream FromHandle(MemoryHandle<byte> handle, bool ownsHandle, nint length, bool readOnly) + public static VnMemoryStream FromHandle(IResizeableMemoryHandle<byte> handle, bool ownsHandle, nint length, bool readOnly) { - handle.ThrowIfClosed(); - return new VnMemoryStream(handle, length, readOnly, ownsHandle); + //Check the handle + _ = handle ?? throw new ArgumentNullException(nameof(handle)); + + return handle.CanRealloc || readOnly + ? new VnMemoryStream(handle, length, readOnly, ownsHandle) + : throw new ArgumentException("The supplied memory handle must be resizable on a writable stream", nameof(handle)); } /// <summary> @@ -155,8 +162,11 @@ namespace VNLib.Utils.IO /// <param name="length">The length property of the stream</param> /// <param name="readOnly">Is the stream readonly (should mostly be true!)</param> /// <param name="ownsHandle">Does the new stream own the memory -> <paramref name="buffer"/></param> - private VnMemoryStream(MemoryHandle<byte> buffer, nint length, bool readOnly, bool ownsHandle) + private VnMemoryStream(IResizeableMemoryHandle<byte> buffer, nint length, bool readOnly, bool ownsHandle) { + Debug.Assert(length >= 0, "Length must be positive"); + Debug.Assert(buffer.CanRealloc || readOnly, "The supplied buffer is not resizable on a writable stream"); + OwnsHandle = ownsHandle; _buffer = buffer; //Consume the handle _length = length; //Store length of the buffer @@ -200,8 +210,8 @@ namespace VNLib.Utils.IO { throw new IOException("The destinaion stream is not writeable"); } - - do + + while (LenToPosDiff > 0) { //Calc the remaining bytes to read no larger than the buffer size int bytesToRead = (int)Math.Min(LenToPosDiff, bufferSize); @@ -213,8 +223,7 @@ namespace VNLib.Utils.IO //Update position _position += bytesToRead; - - } while (LenToPosDiff > 0); + } } /// <summary> @@ -231,37 +240,47 @@ namespace VNLib.Utils.IO public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { _ = destination ?? throw new ArgumentNullException(nameof(destination)); - + + if (bufferSize < 1) + { + throw new ArgumentOutOfRangeException(nameof(bufferSize), "Buffer size must be greater than 0"); + } + if (!destination.CanWrite) { throw new IOException("The destinaion stream is not writeable"); } - cancellationToken.ThrowIfCancellationRequested(); - - /* - * Alloc temp copy buffer. This is a requirement because - * the stream may be larger than an int32 so it must be - * copied by segment - */ + cancellationToken.ThrowIfCancellationRequested(); - using VnTempBuffer<byte> copyBuffer = new(bufferSize); - - do + if(_length < Int32.MaxValue) { - //read from internal stream - int read = Read(copyBuffer); + //Safe to alloc a memory manager to do copy + using MemoryManager<byte> asMemManager = _buffer.ToMemoryManager(false); + + /* + * CopyTo starts at the current position, as if calling Read() + * so the reader must be offset to match and the _length gives us the + * actual length of the stream and therefor the segment size + */ - if(read <= 0) + while(LenToPosDiff > 0) { - break; - } + int blockSize = Math.Min((int)LenToPosDiff, bufferSize); + Memory<byte> window = asMemManager.Memory.Slice((int)_position, blockSize); - //write async - await destination.WriteAsync(copyBuffer.AsMemory(0, read), cancellationToken); + //write async + await destination.WriteAsync(window, cancellationToken); - } while (true); - + //Update position + _position+= bufferSize; + } + } + else + { + //TODO support 64bit memory stream copy + throw new NotSupportedException("64bit async copies are currently not supported"); + } } /// <summary> @@ -349,13 +368,13 @@ namespace VNLib.Utils.IO } //get the value at the current position - byte* ptr = _buffer.GetOffset(_position); + ref byte ptr = ref _buffer.GetByteOffsetRef((nuint)_position); //Increment position _position++; //Return value - return *ptr; + return ptr; } /* diff --git a/lib/Utils/src/Memory/VnTempBuffer.cs b/lib/Utils/src/Memory/ArrayPoolBuffer.cs index 5f5f831..92a2022 100644 --- a/lib/Utils/src/Memory/VnTempBuffer.cs +++ b/lib/Utils/src/Memory/ArrayPoolBuffer.cs @@ -24,6 +24,8 @@ using System; using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using VNLib.Utils.Extensions; @@ -33,7 +35,7 @@ namespace VNLib.Utils.Memory /// A disposable temporary buffer from shared ArrayPool /// </summary> /// <typeparam name="T">Type of buffer to create</typeparam> - public sealed class VnTempBuffer<T> : VnDisposeable, IIndexable<int, T>, IMemoryHandle<T>, IMemoryOwner<T> + public sealed class ArrayPoolBuffer<T> : VnDisposeable, IIndexable<int, T>, IMemoryHandle<T>, IMemoryOwner<T> { private readonly ArrayPool<T> Pool; @@ -64,25 +66,29 @@ namespace VNLib.Utils.Memory } ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T GetReference() => ref MemoryMarshal.GetArrayDataReference(Buffer); + + ///<inheritdoc/> Memory<T> IMemoryOwner<T>.Memory => AsMemory(); /// <summary> - /// Allocates a new <see cref="VnTempBuffer{BufType}"/> with a new buffer from shared array-pool + /// Allocates a new <see cref="ArrayPoolBuffer{BufType}"/> with a new buffer from shared array-pool /// </summary> /// <param name="minSize">Minimum size of the buffer</param> /// <param name="zero">Set the zero memory flag on close</param> - public VnTempBuffer(int minSize, bool zero = false) :this(ArrayPool<T>.Shared, minSize, zero) - {} + public ArrayPoolBuffer(int minSize, bool zero = false) :this(ArrayPool<T>.Shared, minSize, zero) + { } /// <summary> - /// Allocates a new <see cref="VnTempBuffer{BufType}"/> with a new buffer from specified array-pool + /// Allocates a new <see cref="ArrayPoolBuffer{BufType}"/> with a new buffer from specified array-pool /// </summary> /// <param name="pool">The <see cref="ArrayPool{T}"/> to allocate from and return to</param> /// <param name="minSize">Minimum size of the buffer</param> /// <param name="zero">Set the zero memory flag on close</param> - public VnTempBuffer(ArrayPool<T> pool, int minSize, bool zero = false) + public ArrayPoolBuffer(ArrayPool<T> pool, int minSize, bool zero = false) { - Pool = pool; + Pool = pool ?? throw new ArgumentNullException(nameof(pool)); Buffer = pool.Rent(minSize, zero); InitSize = minSize; } @@ -126,44 +132,64 @@ namespace VNLib.Utils.Memory Check(); return new Memory<T>(Buffer, 0, InitSize); } - + /// <summary> /// Gets a memory structure around the internal buffer /// </summary> /// <param name="count">The number of elements included in the result</param> - /// <param name="start">A value specifying the begining index of the buffer to include</param> /// <returns>A memory structure over the buffer</returns> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public Memory<T> AsMemory(int start, int count) - { - Check(); - return new Memory<T>(Buffer, start, count); - } + public Memory<T> AsMemory(int count) => AsMemory()[..count]; /// <summary> /// Gets a memory structure around the internal buffer /// </summary> /// <param name="count">The number of elements included in the result</param> + /// <param name="start">A value specifying the begining index of the buffer to include</param> /// <returns>A memory structure over the buffer</returns> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public Memory<T> AsMemory(int count) + public Memory<T> AsMemory(int start, int count) => AsMemory().Slice(start, count); + + /// <summary> + /// Gets an array segment around the internal buffer + /// </summary> + /// <returns>The internal array segment</returns> + /// <exception cref="ObjectDisposedException"></exception> + public ArraySegment<T> AsArraySegment() { Check(); - return new Memory<T>(Buffer, 0, count); + return new ArraySegment<T>(Buffer, 0, InitSize); } - - /* - * Allow implict casts to span/arrayseg/memory - */ - public static implicit operator Memory<T>(VnTempBuffer<T> buf) => buf == null ? Memory<T>.Empty : buf.ToMemory(); - public static implicit operator Span<T>(VnTempBuffer<T> buf) => buf == null ? Span<T>.Empty : buf.ToSpan(); - public static implicit operator ArraySegment<T>(VnTempBuffer<T> buf) => buf == null ? ArraySegment<T>.Empty : buf.ToArraySegment(); - public Memory<T> ToMemory() => Disposed ? Memory<T>.Empty : Buffer.AsMemory(0, InitSize); - public Span<T> ToSpan() => Disposed ? Span<T>.Empty : Buffer.AsSpan(0, InitSize); - public ArraySegment<T> ToArraySegment() => Disposed ? ArraySegment<T>.Empty : new(Buffer, 0, InitSize); + /// <summary> + /// Gets an array segment around the internal buffer + /// </summary> + /// <returns>The internal array segment</returns> + /// <exception cref="ObjectDisposedException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public ArraySegment<T> AsArraySegment(int start, int count) + { + if(start< 0 || count < 0) + { + throw new ArgumentOutOfRangeException(start < 0 ? nameof(start) : nameof(count), "Cannot be less than zero"); + } + + MemoryUtil.CheckBounds(Buffer, (uint)start, (uint)count); + + Check(); + return new ArraySegment<T>(Buffer, start, count); + } + + //Pin, will also check bounds + ///<inheritdoc/> + public MemoryHandle Pin(int elementIndex) => MemoryUtil.PinArrayAndGetHandle(Buffer, elementIndex); + + void IPinnable.Unpin() + { + //Gchandle will manage the unpin + } /// <summary> /// Returns buffer to shared array-pool @@ -179,16 +205,7 @@ namespace VNLib.Utils.Memory #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. } - //Pin, will also check bounds - ///<inheritdoc/> - public MemoryHandle Pin(int elementIndex) => MemoryUtil.PinArrayAndGetHandle(Buffer, elementIndex); - - void IPinnable.Unpin() - { - //Gchandle will manage the unpin - } - ///<inheritdoc/> - ~VnTempBuffer() => Free(); + ~ArrayPoolBuffer() => Free(); } }
\ No newline at end of file diff --git a/lib/Utils/src/Memory/Caching/IReusable.cs b/lib/Utils/src/Memory/Caching/IReusable.cs index 618878f..4472ad3 100644 --- a/lib/Utils/src/Memory/Caching/IReusable.cs +++ b/lib/Utils/src/Memory/Caching/IReusable.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -25,14 +25,20 @@ namespace VNLib.Utils.Memory.Caching { /// <summary> - /// Allows for use within a <see cref="ReusableStore{T}"/>, this object is intended to be reused heavily + /// Allows for use within a <see cref="ObjectRental{T}"/>, this object is intended to be reused heavily /// </summary> public interface IReusable { /// <summary> /// The instance should prepare itself for use (or re-use) + /// <para> + /// This method is guarunteed to be called directly after a constructor + /// when a new instance is allocated and before it is ever returned to a + /// caller. + /// </para> /// </summary> void Prepare(); + /// <summary> /// The intance is being returned and should determine if it's state is reusabled /// </summary> diff --git a/lib/Utils/src/Memory/HeapCreation.cs b/lib/Utils/src/Memory/HeapCreation.cs index 9ef9fdb..835226c 100644 --- a/lib/Utils/src/Memory/HeapCreation.cs +++ b/lib/Utils/src/Memory/HeapCreation.cs @@ -49,6 +49,10 @@ namespace VNLib.Utils.Memory /// <summary> /// Specifies that the requested heap will be a shared heap for the process/library /// </summary> - Shared = 0x04 + Shared = 0x04, + /// <summary> + /// Specifies that the heap will support block reallocation + /// </summary> + SupportsRealloc = 0x08, } }
\ No newline at end of file diff --git a/lib/Utils/src/Memory/IMemoryHandle.cs b/lib/Utils/src/Memory/IMemoryHandle.cs index cf19ce9..f4e1a36 100644 --- a/lib/Utils/src/Memory/IMemoryHandle.cs +++ b/lib/Utils/src/Memory/IMemoryHandle.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -42,6 +42,12 @@ namespace VNLib.Utils.Memory /// Gets the internal block as a span /// </summary> Span<T> Span { get; } + + /// <summary> + /// Gets a reference to the first element in the block + /// </summary> + /// <returns>The reference</returns> + ref T GetReference(); } } diff --git a/lib/Utils/src/Memory/IResizeableMemoryHandle.cs b/lib/Utils/src/Memory/IResizeableMemoryHandle.cs new file mode 100644 index 0000000..f788b48 --- /dev/null +++ b/lib/Utils/src/Memory/IResizeableMemoryHandle.cs @@ -0,0 +1,52 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: IResizeableMemoryHandle.cs +* +* IResizeableMemoryHandle.cs is part of VNLib.Utils which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Utils is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Utils is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Utils.Memory +{ + + /// <summary> + /// Represents a memory handle that can be resized in place. + /// </summary> + /// <typeparam name="T">The data type</typeparam> + public interface IResizeableMemoryHandle<T> : IMemoryHandle<T> + { + /// <summary> + /// Gets a value indicating whether the handle supports resizing in place + /// </summary> + bool CanRealloc { get; } + + /// <summary> + /// Resizes a memory handle to a new number of elements. + /// </summary> + /// <remarks> + /// Even if a handle is resizable resizing may not be supported for all types of handles. + /// </remarks> + /// <param name="elements">The new number of elements to resize the handle to</param> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="NotSupportedException"></exception> + void Resize(nuint elements); + } +}
\ No newline at end of file diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs index 30d2b99..4d2ff0c 100644 --- a/lib/Utils/src/Memory/MemoryHandle.cs +++ b/lib/Utils/src/Memory/MemoryHandle.cs @@ -40,8 +40,16 @@ namespace VNLib.Utils.Memory /// Handles are configured to address blocks larger than 2GB, /// so some properties may raise exceptions if large blocks are used. /// </remarks> - public sealed class MemoryHandle<T> : SafeHandleZeroOrMinusOneIsInvalid, IMemoryHandle<T>, IEquatable<MemoryHandle<T>> where T : unmanaged + public sealed class MemoryHandle<T> : + SafeHandleZeroOrMinusOneIsInvalid, + IResizeableMemoryHandle<T>, + IMemoryHandle<T>, + IEquatable<MemoryHandle<T>> + where T : unmanaged { + private readonly bool ZeroMemory; + private readonly IUnmangedHeap Heap; + private nuint _length; /// <summary> /// New <typeparamref name="T"/>* pointing to the base of the allocated block @@ -79,15 +87,11 @@ namespace VNLib.Utils.Memory } } - private readonly bool ZeroMemory; - private readonly IUnmangedHeap Heap; - private nuint _length; - ///<inheritdoc/> - public nuint Length + public nuint Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _length; + get => _length; } /// <summary> @@ -101,6 +105,13 @@ namespace VNLib.Utils.Memory get => MemoryUtil.ByteCount<T>(_length); } + ///<inheritdoc/> + public bool CanRealloc + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Heap != null && Heap.CreationFlags.HasFlag(HeapCreation.SupportsRealloc); + } + /// <summary> /// Creates a new memory handle, for which is holds ownership, and allocates the number of elements specified on the heap. /// </summary> @@ -128,7 +139,7 @@ namespace VNLib.Utils.Memory /// when accessed, however <see cref="IMemoryHandle{T}"/> operations are /// considered "safe" meaning they should never raise excpetions /// </summary> - public MemoryHandle():base(false) + public MemoryHandle() : base(false) { _length = 0; Heap = null!; @@ -151,7 +162,7 @@ namespace VNLib.Utils.Memory * If resize raises an exception the current block pointer * should still be valid, if its not, the pointer should * be set to 0/-1, which will be considered invalid anyway - */ + */ Heap.Resize(ref handle, elements, (nuint)sizeof(T), ZeroMemory); @@ -161,14 +172,14 @@ namespace VNLib.Utils.Memory //Catch the disposed exception so we can invalidate the current ptr catch (ObjectDisposedException) { - base.handle = IntPtr.Zero; + SetHandle(IntPtr.Zero); //Set as invalid so release does not get called - base.SetHandleAsInvalid(); + SetHandleAsInvalid(); //Propagate the exception throw; } } - + /// <summary> /// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks /// </summary> @@ -190,7 +201,14 @@ namespace VNLib.Utils.Memory T* bs = ((T*)handle) + elements; return bs; } - + + ///<inheritdoc/> + public ref T GetReference() + { + this.ThrowIfClosed(); + return ref MemoryUtil.GetRef<T>(handle); + } + ///<inheritdoc/> ///<exception cref="ObjectDisposedException"></exception> ///<exception cref="ArgumentOutOfRangeException"></exception> @@ -200,14 +218,14 @@ namespace VNLib.Utils.Memory ///</remarks> public unsafe MemoryHandle Pin(int elementIndex) { - if(elementIndex < 0) + if (elementIndex < 0) { throw new ArgumentOutOfRangeException(nameof(elementIndex)); } //Get ptr and guard checks before adding the referrence T* ptr = GetOffset((nuint)elementIndex); - + bool addRef = false; //use the pinned field as success val DangerousAddRef(ref addRef); @@ -216,7 +234,7 @@ namespace VNLib.Utils.Memory ? throw new ObjectDisposedException("Failed to increase referrence count on the memory handle because it was released") : new MemoryHandle(ptr, pinnable: this); } - + ///<inheritdoc/> ///<exception cref="ObjectDisposedException"></exception> public void Unpin() @@ -226,11 +244,7 @@ namespace VNLib.Utils.Memory } ///<inheritdoc/> - protected override bool ReleaseHandle() - { - //Return result of free, only if the handle is valid - return Heap.Free(ref handle); - } + protected override bool ReleaseHandle() => Heap.Free(ref handle); /// <summary> /// Determines if the memory blocks are equal by comparing their base addresses. @@ -243,14 +257,13 @@ namespace VNLib.Utils.Memory { return other != null && (IsClosed | other.IsClosed) == false && _length == other._length && handle == other.handle; } - + ///<inheritdoc/> public override bool Equals(object? obj) => obj is MemoryHandle<T> oHandle && Equals(oHandle); - + ///<inheritdoc/> public override int GetHashCode() => base.GetHashCode(); - ///<inheritdoc/> public static implicit operator Span<T>(MemoryHandle<T> handle) { diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index 8cc9736..0261bdf 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -31,7 +31,7 @@ using System.Globalization; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; -using VNLib.Utils.Extensions; +using VNLib.Utils.Resources; using VNLib.Utils.Memory.Diagnostics; namespace VNLib.Utils.Memory @@ -113,7 +113,7 @@ namespace VNLib.Utils.Memory Trace.WriteLineIf(globalZero, "Shared heap global zero enabled"); Lazy<IUnmangedHeap> heap = new (() => InitHeapInternal(true, diagEnable, globalZero), LazyThreadSafetyMode.PublicationOnly); - + //Cleanup the heap on process exit AppDomain.CurrentDomain.DomainUnload += DomainUnloaded; @@ -165,7 +165,7 @@ namespace VNLib.Utils.Memory string? rawFlagsEnv = Environment.GetEnvironmentVariable(SHARED_HEAP_RAW_FLAGS); //Default flags - HeapCreation cFlags = HeapCreation.UseSynchronization; + HeapCreation cFlags = HeapCreation.UseSynchronization | HeapCreation.SupportsRealloc; /* * We need to set the shared flag and the synchronziation flag. @@ -235,7 +235,7 @@ namespace VNLib.Utils.Memory /// <typeparam name="T">Unmanged datatype</typeparam> /// <param name="block">Block of memory to be cleared</param> [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - public static void UnsafeZeroMemory<T>(ReadOnlySpan<T> block) where T : unmanaged + public static void UnsafeZeroMemory<T>(ReadOnlySpan<T> block) where T : struct { if (block.IsEmpty) { @@ -244,11 +244,11 @@ namespace VNLib.Utils.Memory uint byteSize = ByteCount<T>((uint)block.Length); - fixed (void* ptr = &MemoryMarshal.GetReference(block)) - { - //Calls memset - Unsafe.InitBlock(ptr, 0, byteSize); - } + ref T r0 = ref MemoryMarshal.GetReference(block); + ref byte byteRef = ref Unsafe.As<T, byte>(ref r0); + + //Calls memset + Unsafe.InitBlock(ref byteRef, 0, byteSize); } /// <summary> @@ -257,7 +257,7 @@ namespace VNLib.Utils.Memory /// <typeparam name="T">Unmanged datatype</typeparam> /// <param name="block">Block of memory to be cleared</param> [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - public static void UnsafeZeroMemory<T>(ReadOnlyMemory<T> block) where T : unmanaged + public static void UnsafeZeroMemory<T>(ReadOnlyMemory<T> block) where T : struct { if (block.IsEmpty) { @@ -284,7 +284,7 @@ namespace VNLib.Utils.Memory /// <typeparam name="T">The unmanaged</typeparam> /// <param name="block">The block of memory to initialize</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeBlock<T>(Span<T> block) where T : unmanaged => UnsafeZeroMemory<T>(block); + public static void InitializeBlock<T>(Span<T> block) where T : struct => UnsafeZeroMemory<T>(block); /// <summary> /// Initializes a block of memory with zeros @@ -292,7 +292,7 @@ namespace VNLib.Utils.Memory /// <typeparam name="T">The unmanaged</typeparam> /// <param name="block">The block of memory to initialize</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeBlock<T>(Memory<T> block) where T : unmanaged => UnsafeZeroMemory<T>(block); + 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 @@ -303,16 +303,13 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] public static void InitializeBlock<T>(T* block, int itemCount) where T : unmanaged { - if (itemCount == 0) + if (itemCount <= 0 || block == null) { return; } - //Get the size of the structure - int size = sizeof(T); - //Zero block - Unsafe.InitBlock(block, 0, (uint)(size * itemCount)); + Unsafe.InitBlock(block, 0, ByteCount<T>((uint)itemCount)); } /// <summary> @@ -321,33 +318,24 @@ namespace VNLib.Utils.Memory /// <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.AggressiveInlining)] public static void InitializeBlock<T>(IntPtr block, int itemCount) where T : unmanaged => InitializeBlock((T*)block, itemCount); /// <summary> /// Zeroes a block of memory pointing to the structure /// </summary> /// <typeparam name="T">The structure type</typeparam> - /// <param name="block">The pointer to the allocated structure</param> - public static void ZeroStruct<T>(IntPtr block) - { - //get thes size of the structure does not have to be primitive type - int size = Unsafe.SizeOf<T>(); - //Zero block - Unsafe.InitBlock(block.ToPointer(), 0, (uint)size); - } + /// <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>()); /// <summary> /// Zeroes a block of memory pointing to the structure /// </summary> /// <typeparam name="T">The structure type</typeparam> - /// <param name="structPtr">The pointer to the allocated structure</param> - public static void ZeroStruct<T>(void* structPtr) - { - //get thes size of the structure - int size = Unsafe.SizeOf<T>(); - //Zero block - Unsafe.InitBlock(structPtr, 0, (uint)size); - } + /// <param name="block">The pointer to the allocated structure</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ZeroStruct<T>(IntPtr block) => ZeroStruct<T>(block.ToPointer()); /// <summary> /// Zeroes a block of memory pointing to the structure @@ -355,12 +343,20 @@ 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>(T* structPtr) where T : unmanaged => Unsafe.InitBlock(structPtr, 0, (uint)sizeof(T)); + public static void ZeroStruct<T>(T* structPtr) where T : unmanaged => ZeroStruct<T>((void*)structPtr); #endregion #region Copy + /* + * Dirty little trick to access internal Buffer.Memmove method for + * large references. May not always be supported, so optional safe + * 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); + /// <summary> /// Copies data from source memory to destination memory of an umanged data type /// </summary> @@ -369,7 +365,7 @@ namespace VNLib.Utils.Memory /// <param name="dest">Destination <see cref="MemoryHandle{T}"/></param> /// <param name="destOffset">Dest offset</param> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static void Copy<T>(ReadOnlySpan<T> source, MemoryHandle<T> dest, nuint destOffset) where T : unmanaged + public static void Copy<T>(ReadOnlySpan<T> source, IMemoryHandle<T> dest, nuint destOffset) where T: struct { if (dest is null) { @@ -381,11 +377,17 @@ namespace VNLib.Utils.Memory return; } - //Get long offset from the destination handle (also checks bounds) - Span<T> dst = dest.GetOffsetSpan(destOffset, source.Length); + //Check memhandle bounds + CheckBounds(dest, destOffset, (uint)source.Length); - //Copy data - source.CopyTo(dst); + //Get byte ref and byte count + nuint byteCount = ByteCount<T>((uint)source.Length); + 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); + Debug.Assert(success, "Memmove by ref call failed during a 32bit copy"); } /// <summary> @@ -396,24 +398,7 @@ namespace VNLib.Utils.Memory /// <param name="dest">Destination <see cref="MemoryHandle{T}"/></param> /// <param name="destOffset">Dest offset</param> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static void Copy<T>(ReadOnlyMemory<T> source, MemoryHandle<T> dest, nuint destOffset) where T : unmanaged - { - if (dest is null) - { - throw new ArgumentNullException(nameof(dest)); - } - - if (source.IsEmpty) - { - return; - } - - //Get long offset from the destination handle (also checks bounds) - Span<T> dst = dest.GetOffsetSpan(destOffset, source.Length); - - //Copy data - source.Span.CopyTo(dst); - } + public static void Copy<T>(ReadOnlyMemory<T> source, IMemoryHandle<T> dest, nuint destOffset) where T : struct => Copy(source.Span, dest, destOffset); /// <summary> /// Copies data from source memory to destination memory of an umanged data type @@ -425,10 +410,12 @@ namespace VNLib.Utils.Memory /// <param name="destOffset">Dest offset</param> /// <param name="count">Number of elements to copy</param> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static void Copy<T>(MemoryHandle<T> source, nint sourceOffset, Span<T> dest, int destOffset, int count) where T : unmanaged + public static void Copy<T>(IMemoryHandle<T> source, nint sourceOffset, Span<T> dest, int destOffset, int count) where T : struct { + _ = source ?? throw new ArgumentNullException(nameof(source)); + //Validate source/dest/count - ValidateArgs(sourceOffset, destOffset, count); + ValidateCopyArgs(sourceOffset, destOffset, count); //Check count last for debug reasons if (count == 0) @@ -436,14 +423,17 @@ namespace VNLib.Utils.Memory return; } - //Get offset span, also checks bounts - Span<T> src = source.GetOffsetSpan(sourceOffset, count); - - //slice the dest span - Span<T> dst = dest.Slice(destOffset, count); + //Check source bounds + CheckBounds(source, (nuint)sourceOffset, (nuint)count); - //Copy data - src.CopyTo(dst); + //Get byte ref and byte count + nuint byteCount = ByteCount<T>((uint)count); + ref T src = ref source.GetReference(); + ref T dst = ref MemoryMarshal.GetReference(dest); + + //Use memmove by ref + bool success = MemmoveByRef(ref src, (uint)sourceOffset, ref dst, (uint)destOffset, byteCount); + Debug.Assert(success, "Memmove by ref call failed during a 32bit copy"); } /// <summary> @@ -458,13 +448,51 @@ namespace VNLib.Utils.Memory /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy<T>(MemoryHandle<T> source, nint sourceOffset, Memory<T> dest, int destOffset, int count) where T : unmanaged + public static void Copy<T>(IMemoryHandle<T> source, nint sourceOffset, Memory<T> dest, int destOffset, int count) where T : struct + => Copy(source, sourceOffset, dest.Span, destOffset, count); + + /// <summary> + /// Copies data from source memory to destination memory of an umanged data type + /// using references for blocks smaller than <see cref="UInt32.MaxValue"/> and + /// pinning for larger blocks + /// </summary> + /// <typeparam name="T">Unmanged type</typeparam> + /// <param name="source">Source data <see cref="MemoryHandle{T}"/></param> + /// <param name="sourceOffset">Number of elements to offset source data</param> + /// <param name="dest">Destination <see cref="Memory{T}"/></param> + /// <param name="destOffset">Dest offset</param> + /// <param name="count">Number of elements to copy</param> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ArgumentOutOfRangeException"></exception> + public static void Copy<T>(IMemoryHandle<T> source, nuint sourceOffset, IMemoryHandle<T> dest, nuint destOffset, nuint count) where T : unmanaged { - //Call copy method with dest as span - Copy(source, sourceOffset, dest.Span, destOffset, count); + _ = source ?? throw new ArgumentNullException(nameof(source)); + _ = dest ?? throw new ArgumentNullException(nameof(dest)); + + CheckBounds(source, sourceOffset, count); + CheckBounds(dest, destOffset, count); + + //Get byte ref and byte count + nuint byteCount = ByteCount<T>(count); + ref T src = ref source.GetReference(); + ref T dst = ref dest.GetReference(); + + if (!MemmoveByRef(ref src, sourceOffset, ref dst, destOffset, byteCount)) + { + //Copying block larger than 32bit must be done with pointers + using MemoryHandle srcH = source.Pin(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); + } } - private static void ValidateArgs(nint sourceOffset, nint destOffset, nint count) + private static void ValidateCopyArgs(nint sourceOffset, nint destOffset, nint count) { if(sourceOffset < 0) { @@ -483,17 +511,19 @@ namespace VNLib.Utils.Memory } /// <summary> - /// 32/64 bit large block copy + /// 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="offset">The element offset to begin reading 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 Copy<T>(IMemoryHandle<T> source, nuint offset, T[] dest, nuint destOffset, nuint count) where T : unmanaged + public static void Copy<T>(IMemoryHandle<T> source, nuint sourceOffset, T[] dest, nuint destOffset, nuint count) where T : unmanaged { if (source is null) { @@ -511,41 +541,62 @@ namespace VNLib.Utils.Memory } //Check source bounds - CheckBounds(source, offset, count); + CheckBounds(source, sourceOffset, count); - //Check dest bounts + //Check dest bounds CheckBounds(dest, destOffset, count); - //Check if 64bit - if(sizeof(void*) == 8) + //Get byte refs and byte count + nuint byteCount = ByteCount<T>(count); + ref T src = ref source.GetReference(); + ref T dst = ref MemoryMarshal.GetArrayDataReference(dest); + + //Try to memove by ref first, otherwise fallback to pinning + if (!MemmoveByRef(ref src, sourceOffset, ref dst, destOffset, byteCount)) { - //Get the number of bytes to copy - nuint byteCount = ByteCount<T>(count); + //Copying block larger than 32bit must be done with pointers + using MemoryHandle srcH = source.Pin(0); + using MemoryHandle dstH = PinArrayAndGetHandle(dest, 0); - //Get memory handle from source - using MemoryHandle srcHandle = source.Pin(0); + //Get pointers and add offsets + T* srcOffset = ((T*)srcH.Pointer) + sourceOffset; + T* dstOffset = ((T*)dstH.Pointer) + destOffset; - //get source offset - T* src = (T*)srcHandle.Pointer + offset; + //Copy memory + Buffer.MemoryCopy(srcOffset, dstOffset, byteCount, byteCount); + } + } + - //pin array - fixed (T* dst = &MemoryMarshal.GetArrayDataReference(dest)) - { - //Offset dest ptr - T* dstOffset = dst + destOffset; + [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 + { + Debug.Assert(!Unsafe.IsNullRef(ref src), "Null source reference passed to MemmoveByRef"); + Debug.Assert(!Unsafe.IsNullRef(ref dst), "Null destination reference passed to MemmoveByRef"); - //Copy src to set - Buffer.MemoryCopy(src, dstOffset, byteCount, byteCount); - } + //Get offset referrences to the source and destination + ref T srcOffsetPtr = ref Unsafe.Add(ref src, srcOffset); + ref T dstOffsetPtr = ref Unsafe.Add(ref dst, dstOffset); + + //Cast to byte pointers + ref byte srcByte = ref Unsafe.As<T, byte>(ref srcOffsetPtr); + ref byte dstByte = ref Unsafe.As<T, byte>(ref dstOffsetPtr); + + if (_sysMemmove != null) + { + //Call sysinternal memmove + _sysMemmove(ref dstByte, ref srcByte, byteCount); + return true; + } + else if(byteCount < uint.MaxValue) + { + //Use safe 32bit block copy + Unsafe.CopyBlock(ref dstByte, ref srcByte, (uint)byteCount); + return true; } else { - //If 32bit its safe to use spans - - Span<T> src = source.AsSpan((int)offset, (int)count); - Span<T> dst = dest.AsSpan((int)destOffset, (int)count); - //Copy - src.CopyTo(dst); + return false; } } @@ -598,6 +649,26 @@ namespace VNLib.Utils.Memory public static uint ByteCount<T>(uint elementCount) => checked(elementCount * (uint)Unsafe.SizeOf<T>()); /// <summary> + /// Gets the byte multiple of the length parameter. NOTE: Does not verify negative values + /// </summary> + /// <typeparam name="T">The type to get the byte offset of</typeparam> + /// <param name="elementCount">The number of elements to get the byte count of</param> + /// <returns>The byte multiple of the number of elments</returns> + /// <exception cref="OverflowException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint ByteCount<T>(nint elementCount) => checked(elementCount * Unsafe.SizeOf<T>()); + + /// <summary> + /// Gets the byte multiple of the length parameter. NOTE: Does not verify negative values + /// </summary> + /// <typeparam name="T">The type to get the byte offset of</typeparam> + /// <param name="elementCount">The number of elements to get the byte count of</param> + /// <returns>The byte multiple of the number of elments</returns> + /// <exception cref="OverflowException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ByteCount<T>(int elementCount) => checked(elementCount * Unsafe.SizeOf<T>()); + + /// <summary> /// Checks if the offset/count paramters for the given memory handle /// point outside the block wrapped in the handle /// </summary> @@ -682,21 +753,21 @@ namespace VNLib.Utils.Memory throw new ArgumentOutOfRangeException(nameof(elementOffset)); } + _ = array ?? throw new ArgumentNullException(nameof(array)); + //Quick verify index exists, may be the very last index CheckBounds(array, (nuint)elementOffset, 1); //Pin the array GCHandle arrHandle = GCHandle.Alloc(array, GCHandleType.Pinned); - //Get array base address - void* basePtr = (void*)arrHandle.AddrOfPinnedObject(); - - Debug.Assert(basePtr != null); + //safe to get array basee pointer + ref T arrBase = ref MemoryMarshal.GetArrayDataReference(array); //Get element offset - void* indexOffet = Unsafe.Add<T>(basePtr, elementOffset); + ref T indexOffet = ref Unsafe.Add(ref arrBase, elementOffset); - return new(indexOffet, arrHandle); + return new(Unsafe.AsPointer(ref indexOffet), arrHandle); } /// <summary> @@ -741,6 +812,29 @@ namespace VNLib.Utils.Memory public static Span<T> GetSpan<T>(MemoryHandle handle, int size) => new(handle.Pointer, size); /// <summary> + /// Recovers a reference to the supplied pointer + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="address">The base address to cast to a reference</param> + /// <returns>The reference to the supplied address</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetRef<T>(IntPtr address) => ref Unsafe.AsRef<T>(address.ToPointer()); + + /// <summary> + /// Recovers a reference to the supplied pointer + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="address">The base address to cast to a reference</param> + /// <param name="offset">The offset to add to the base address</param> + /// <returns>The reference to the supplied address</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetRef<T>(IntPtr address, nuint offset) + { + ref T baseRef = ref GetRef<T>(address); + return ref Unsafe.Add(ref baseRef, (nint)offset); + } + + /// <summary> /// Rounds the requested byte size up to the nearest page /// number of bytes /// </summary> diff --git a/lib/Utils/src/Memory/MemoryUtilAlloc.cs b/lib/Utils/src/Memory/MemoryUtilAlloc.cs index e4210e7..9e305d0 100644 --- a/lib/Utils/src/Memory/MemoryUtilAlloc.cs +++ b/lib/Utils/src/Memory/MemoryUtilAlloc.cs @@ -92,7 +92,7 @@ namespace VNLib.Utils.Memory } //Round to nearest page (in bytes) - nint np = NearestPage(elements * sizeof(T)); + nint np = NearestPage(ByteCount<T>(elements)); //Resize to element size np /= sizeof(T); @@ -131,7 +131,7 @@ namespace VNLib.Utils.Memory } else { - return new VnTempBuffer<T>(ArrayPool<T>.Shared, elements, zero); + return new ArrayPoolBuffer<T>(ArrayPool<T>.Shared, elements, zero); } } @@ -154,7 +154,7 @@ namespace VNLib.Utils.Memory } //Round to nearest page (in bytes) - nint np = NearestPage(elements * sizeof(T)); + nint np = NearestPage(ByteCount<T>(elements)); //Resize to element size np /= sizeof(T); @@ -258,7 +258,7 @@ namespace VNLib.Utils.Memory } else { - return new VnTempBuffer<byte>(ArrayPool<byte>.Shared, elements, zero); + return new ArrayPoolBuffer<byte>(ArrayPool<byte>.Shared, elements, zero); } } diff --git a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs index f2bbd51..a17a906 100644 --- a/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs +++ b/lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs @@ -25,6 +25,8 @@ using System; using System.Buffers; +using VNLib.Utils.Extensions; + namespace VNLib.Utils.Memory { /// <summary> @@ -48,7 +50,7 @@ namespace VNLib.Utils.Memory ///<exception cref="OutOfMemoryException"></exception> ///<exception cref="ObjectDisposedException"></exception> ///<exception cref="ArgumentOutOfRangeException"></exception> - public override IMemoryOwner<T> Rent(int minBufferSize = 0) => new SysBufferMemoryManager<T>(Heap, (uint)minBufferSize, false); + public override IMemoryOwner<T> Rent(int minBufferSize = 0) => Heap.DirectAlloc<T>(minBufferSize, false); /// <summary> /// Allocates a new <see cref="MemoryManager{T}"/> of a different data type from the pool @@ -56,7 +58,7 @@ namespace VNLib.Utils.Memory /// <typeparam name="TDifType">The unmanaged data type to allocate for</typeparam> /// <param name="minBufferSize">Minumum size of the buffer</param> /// <returns>The memory owner of a different data type</returns> - public IMemoryOwner<TDifType> Rent<TDifType>(int minBufferSize = 0) where TDifType : unmanaged => new SysBufferMemoryManager<TDifType>(Heap, (uint)minBufferSize, false); + public IMemoryOwner<TDifType> Rent<TDifType>(int minBufferSize = 0) where TDifType : unmanaged => Heap.DirectAlloc<TDifType>(minBufferSize, false); ///<inheritdoc/> protected override void Dispose(bool disposing) diff --git a/lib/Utils/src/Memory/ProcessHeap.cs b/lib/Utils/src/Memory/ProcessHeap.cs index 3d581cd..5d1bee6 100644 --- a/lib/Utils/src/Memory/ProcessHeap.cs +++ b/lib/Utils/src/Memory/ProcessHeap.cs @@ -49,7 +49,7 @@ namespace VNLib.Utils.Memory /// process heap. Meaining memory will be shared across the process /// </para> /// </summary> - public HeapCreation CreationFlags { get; } = HeapCreation.Shared; + public HeapCreation CreationFlags { get; } = HeapCreation.Shared | HeapCreation.SupportsRealloc; /// <summary> /// Initalizes a new global (cross platform) process heap diff --git a/lib/Utils/src/Memory/SubSequence.cs b/lib/Utils/src/Memory/SubSequence.cs index 1db0ba5..86b2347 100644 --- a/lib/Utils/src/Memory/SubSequence.cs +++ b/lib/Utils/src/Memory/SubSequence.cs @@ -32,14 +32,14 @@ namespace VNLib.Utils.Memory /// Represents a subset (or window) of data within a <see cref="MemoryHandle{T}"/> /// </summary> /// <typeparam name="T">The unmanaged type to wrap</typeparam> - public readonly record struct SubSequence<T> where T: unmanaged + public readonly record struct SubSequence<T> { readonly nuint _offset; /// <summary> /// The handle that owns the memory block /// </summary> - public readonly MemoryHandle<T> Handle { get; } + public readonly IMemoryHandle<T> Handle { get; } /// <summary> /// The number of elements in the current sequence @@ -54,7 +54,7 @@ namespace VNLib.Utils.Memory /// <param name="size"></param> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public SubSequence(MemoryHandle<T> block, nuint offset, int size) + public SubSequence(IMemoryHandle<T> block, nuint offset, int size) { Handle = block ?? throw new ArgumentNullException(nameof(block)); Size = size >= 0 ? size : throw new ArgumentOutOfRangeException(nameof(size)); diff --git a/lib/Utils/src/Memory/SysBufferMemoryManager.cs b/lib/Utils/src/Memory/SysBufferMemoryManager.cs index aca2543..26c3688 100644 --- a/lib/Utils/src/Memory/SysBufferMemoryManager.cs +++ b/lib/Utils/src/Memory/SysBufferMemoryManager.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -25,8 +25,6 @@ using System; using System.Buffers; -using VNLib.Utils.Extensions; - namespace VNLib.Utils.Memory { /// <summary> @@ -34,7 +32,7 @@ namespace VNLib.Utils.Memory /// as a memory provider which implements a <see cref="System.Runtime.InteropServices.SafeHandle"/> /// </summary> /// <typeparam name="T">Unmanaged memory type</typeparam> - public sealed class SysBufferMemoryManager<T> : MemoryManager<T> where T :unmanaged + public sealed class SysBufferMemoryManager<T> : MemoryManager<T> { private readonly IMemoryHandle<T> BackingMemory; private readonly bool _ownsHandle; @@ -45,30 +43,19 @@ namespace VNLib.Utils.Memory /// </summary> /// <param name="existingHandle">The existing handle to consume</param> /// <param name="ownsHandle">A value that indicates if the memory manager owns the handle reference</param> - internal SysBufferMemoryManager(IMemoryHandle<T> existingHandle, bool ownsHandle) + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="OverflowException"></exception> + public SysBufferMemoryManager(IMemoryHandle<T> existingHandle, bool ownsHandle) { - BackingMemory = existingHandle; - _ownsHandle = ownsHandle; - } - - /// <summary> - /// Allocates a fized size buffer from the specified unmanaged <see cref="Win32PrivateHeap"/> - /// </summary> - /// <param name="heap">The heap to perform allocations from</param> - /// <param name="elements">The number of elements to allocate</param> - /// <param name="zero">Zero allocations</param> - public SysBufferMemoryManager(IUnmangedHeap heap, nuint elements, bool zero) - { - BackingMemory = heap.Alloc<T>(elements, zero); - _ownsHandle = true; - } + BackingMemory = existingHandle ?? throw new ArgumentNullException(nameof(existingHandle)); + + //check for overflow + if(existingHandle.Length > Int32.MaxValue) + { + throw new OverflowException("This memory manager does not accept handles larger than Int32.MaxValue"); + } - ///<inheritdoc/> - protected override bool TryGetArray(out ArraySegment<T> segment) - { - //Always false since no array is available - segment = default; - return false; + _ownsHandle = ownsHandle; } ///<inheritdoc/> @@ -81,11 +68,8 @@ namespace VNLib.Utils.Memory /// </summary> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public unsafe override MemoryHandle Pin(int elementIndex = 0) - { - return BackingMemory.Pin(elementIndex); - } - + public unsafe override MemoryHandle Pin(int elementIndex = 0) => BackingMemory.Pin(elementIndex); + ///<inheritdoc/> public override void Unpin() {} diff --git a/lib/Utils/src/Memory/UnmanagedHeapBase.cs b/lib/Utils/src/Memory/UnmanagedHeapBase.cs index bfa6736..0310582 100644 --- a/lib/Utils/src/Memory/UnmanagedHeapBase.cs +++ b/lib/Utils/src/Memory/UnmanagedHeapBase.cs @@ -39,21 +39,12 @@ namespace VNLib.Utils.Memory /// </summary> public abstract class UnmanagedHeapBase : SafeHandleZeroOrMinusOneIsInvalid, IUnmangedHeap { + private readonly HeapCreation _flags; + /// <summary> /// The heap synchronization handle /// </summary> protected readonly object HeapLock; - - /// <summary> - /// The global heap zero flag - /// </summary> - protected readonly bool GlobalZero; - - /// <summary> - /// A value that inidicates that locking will - /// be used when invoking heap operations - /// </summary> - protected readonly bool UseSynchronization; /// <summary> /// Initalizes the unmanaged heap base class (init synchronization handle) @@ -63,13 +54,11 @@ namespace VNLib.Utils.Memory protected UnmanagedHeapBase(HeapCreation flags, bool ownsHandle) : base(ownsHandle) { HeapLock = new(); - GlobalZero = flags.HasFlag(HeapCreation.GlobalZero); - UseSynchronization = flags.HasFlag(HeapCreation.UseSynchronization); - CreationFlags = flags; + _flags = flags; } ///<inheritdoc/> - public HeapCreation CreationFlags { get; } + public HeapCreation CreationFlags => _flags; ///<inheritdoc/> ///<remarks>Increments the handle count, free must be called to decrement the handle count</remarks> @@ -82,7 +71,7 @@ namespace VNLib.Utils.Memory _ = checked(elements * size); //Force zero if global flag is set - zero |= GlobalZero; + zero |= (_flags & HeapCreation.GlobalZero) > 0; bool handleCountIncremented = false; //Increment handle count to prevent premature release @@ -99,7 +88,7 @@ namespace VNLib.Utils.Memory LPVOID block; //Check if lock should be used - if (UseSynchronization) + if ((_flags & HeapCreation.UseSynchronization) > 0) { //Enter lock lock(HeapLock) @@ -138,7 +127,7 @@ namespace VNLib.Utils.Memory return true; } - if (UseSynchronization) + if ((_flags & HeapCreation.UseSynchronization) > 0) { //wait for lock lock (HeapLock) @@ -173,7 +162,7 @@ namespace VNLib.Utils.Memory LPVOID newBlock; //Global zero flag will cause a zero - zero |= GlobalZero; + zero |= (_flags & HeapCreation.GlobalZero) > 0; /* * Realloc may return a null pointer if allocation fails @@ -182,7 +171,7 @@ namespace VNLib.Utils.Memory * be left untouched */ - if (UseSynchronization) + if ((_flags & HeapCreation.UseSynchronization) > 0) { lock (HeapLock) { diff --git a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs index 6d566f1..e4857d1 100644 --- a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs +++ b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs @@ -165,8 +165,7 @@ namespace VNLib.Utils.Memory if (elementIndex < 0 || elementIndex >= IntLength) { throw new ArgumentOutOfRangeException(nameof(elementIndex)); - } - + } if (_handleType == HandleType.Pool) { @@ -174,10 +173,11 @@ namespace VNLib.Utils.Memory } else { - //Get offset pointer and pass self as pinnable argument, (nothing happens but support it) - void* basePtr = Unsafe.Add<T>(_memoryPtr.ToPointer(), elementIndex); + //Add an offset to the base address of the memory block + int byteOffset = MemoryUtil.ByteCount<T>(elementIndex); + IntPtr offset = IntPtr.Add(_memoryPtr, byteOffset); //Unmanaged memory is always pinned, so no need to pass this as IPinnable, since it will cause a box - return new (basePtr); + return MemoryUtil.GetMemoryHandleFromPointer(offset); } } ///<inheritdoc/> @@ -186,6 +186,20 @@ namespace VNLib.Utils.Memory //Nothing to do since gc handle takes care of array, and unmanaged pointers are not pinned } + ///<inheritdoc/> + public readonly ref T GetReference() + { + switch (_handleType) + { + case HandleType.Pool: + return ref MemoryMarshal.GetArrayDataReference(_poolArr!); + case HandleType.PrivateHeap: + return ref MemoryUtil.GetRef<T>(_memoryPtr); + default: + throw new InvalidOperationException("The handle is empty, and cannot capture a reference"); + } + } + /// <summary> /// Determines if the other handle represents the same memory block as the /// current handle. diff --git a/lib/Utils/src/Memory/VnString.cs b/lib/Utils/src/Memory/VnString.cs index 8542688..c937ccc 100644 --- a/lib/Utils/src/Memory/VnString.cs +++ b/lib/Utils/src/Memory/VnString.cs @@ -44,7 +44,7 @@ namespace VNLib.Utils.Memory [ImmutableObject(true)] public sealed class VnString : VnDisposeable, IEquatable<VnString>, IEquatable<string>, IEquatable<char[]>, IComparable<VnString>, IComparable<string> { - private readonly MemoryHandle<char>? Handle; + private readonly IMemoryHandle<char>? Handle; private readonly SubSequence<char> _stringSequence; @@ -63,7 +63,7 @@ namespace VNLib.Utils.Memory _stringSequence = sequence; } - private VnString(MemoryHandle<char> handle, nuint start, int length) + private VnString(IMemoryHandle<char> handle, nuint start, int length) { Handle = handle ?? throw new ArgumentNullException(nameof(handle)); //get sequence @@ -517,14 +517,10 @@ namespace VNLib.Utils.Memory /// </remarks> /// <exception cref="ObjectDisposedException"></exception> public int GetHashCode(StringComparison stringComparison) => string.GetHashCode(AsSpan(), stringComparison); - + ///<inheritdoc/> - protected override void Free() - { - //Dispose the handle if we own it (null if we do not have the parent handle) - Handle?.Dispose(); - } - + protected override void Free() => Handle?.Dispose(); + public static bool operator ==(VnString left, VnString right) => left is null ? right is not null : left.Equals(right, StringComparison.Ordinal); public static bool operator !=(VnString left, VnString right) => !(left == right); diff --git a/lib/Utils/src/Memory/Win32PrivateHeap.cs b/lib/Utils/src/Memory/Win32PrivateHeap.cs index 41fe33a..42f0328 100644 --- a/lib/Utils/src/Memory/Win32PrivateHeap.cs +++ b/lib/Utils/src/Memory/Win32PrivateHeap.cs @@ -177,8 +177,7 @@ namespace VNLib.Utils.Memory //validate the block on the current heap result = HeapValidate(handle, HEAP_NO_FLAGS, block); } - return result; - + return result; } /// <summary> diff --git a/lib/Utils/src/Resources/ManagedLibrary.cs b/lib/Utils/src/Resources/ManagedLibrary.cs index f9813a1..56835c7 100644 --- a/lib/Utils/src/Resources/ManagedLibrary.cs +++ b/lib/Utils/src/Resources/ManagedLibrary.cs @@ -28,6 +28,7 @@ using System.Linq; using System.Threading; using System.Reflection; using System.Runtime.Loader; +using System.Collections.Generic; using System.Runtime.InteropServices; using VNLib.Utils.IO; @@ -76,11 +77,8 @@ namespace VNLib.Utils.Resources _lazyAssembly = new(LoadAssembly, LazyThreadSafetyMode.PublicationOnly); } - private Assembly LoadAssembly() - { - //Load the assembly into the parent context - return _loadContext.LoadFromAssemblyPath(AssemblyPath); - } + //Load the assembly into the parent context + private Assembly LoadAssembly() => _loadContext.LoadFromAssemblyPath(AssemblyPath); /// <summary> /// Raised when the load context that owns this assembly @@ -136,18 +134,50 @@ namespace VNLib.Utils.Resources /// <exception cref="EntryPointNotFoundException"></exception> public T LoadTypeFromAssembly<T>() { - Type resourceType = typeof(T); - //See if the type is exported - Type exp = (from type in Assembly.GetExportedTypes() - where resourceType.IsAssignableFrom(type) - select type) - .FirstOrDefault() - ?? throw new EntryPointNotFoundException($"Imported assembly does not export desired type {resourceType.FullName}"); + Type exp = TryGetExportedType<T>() ?? throw new EntryPointNotFoundException($"Imported assembly does not export desired type {typeof(T).FullName}"); //Create instance return (T)Activator.CreateInstance(exp)!; - } + } + + /// <summary> + /// Gets the type exported from the current assembly that is + /// assignable to the desired type. + /// </summary> + /// <typeparam name="T">The desired base type to get the exported type of</typeparam> + /// <returns>The exported type that matches the desired type from the current assembly</returns> + public Type? TryGetExportedType<T>() => TryGetExportedType(typeof(T)); + + /// <summary> + /// Gets the type exported from the current assembly that is + /// assignable to the desired type. + /// </summary> + /// <param name="resourceType">The desired base type to get the exported type of</param> + /// <returns>The exported type that matches the desired type from the current assembly</returns> + public Type? TryGetExportedType(Type resourceType) => TryGetAllMatchingTypes(resourceType).FirstOrDefault(); + + /// <summary> + /// Gets all exported types from the current assembly that are + /// assignable to the desired type. + /// </summary> + /// <typeparam name="T">The desired resource type</typeparam> + /// <returns>An enumeration of acceptable types</returns> + public IEnumerable<Type> TryGetAllMatchingTypes<T>() => TryGetAllMatchingTypes(typeof(T)); + + /// <summary> + /// Gets all exported types from the current assembly that are + /// assignable to the desired type. + /// </summary> + /// <param name="resourceType">The desired resource type</param> + /// <returns>An enumeration of acceptable types</returns> + public IEnumerable<Type> TryGetAllMatchingTypes(Type resourceType) + { + //try to get all exported types that match the desired type + return from type in Assembly.GetExportedTypes() + where resourceType.IsAssignableFrom(type) + select type; + } /// <summary> /// Creates a new loader for the desired assembly. The assembly and its dependencies @@ -171,5 +201,87 @@ namespace VNLib.Utils.Resources FileInfo fi = new(assemblyName); return new(fi.FullName, loadContext); } + + /// <summary> + /// A helper method that will attempt to get a named method of the desired + /// delegate type from the specified object. + /// </summary> + /// <typeparam name="TDelegate">The method delegate that matches the signature of the desired method</typeparam> + /// <param name="obj">The object to discover and bind the found method to</param> + /// <param name="methodName">The name of the method to capture</param> + /// <param name="flags">The method binding flags</param> + /// <returns>The namaed method delegate for the object type, or null if the method was not found</returns> + /// <exception cref="ArgumentNullException"></exception> + public static TDelegate? TryGetMethod<TDelegate>( + object obj, + string methodName, + BindingFlags flags = BindingFlags.Public + ) where TDelegate : Delegate + { + _ = obj ?? throw new ArgumentNullException(nameof(obj)); + return TryGetMethodInternal<TDelegate>(obj.GetType(), methodName, obj, flags | BindingFlags.Instance); + } + + /// <summary> + /// A helper method that will attempt to get a named method of the desired + /// delegate type from the specified object. + /// </summary> + /// <typeparam name="TDelegate">The method delegate that matches the signature of the desired method</typeparam> + /// <param name="obj">The object to discover and bind the found method to</param> + /// <param name="methodName">The name of the method to capture</param> + /// <param name="flags">The method binding flags</param> + /// <returns>The namaed method delegate for the object type or an exception if not found</returns> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="MissingMethodException"></exception> + public static TDelegate GetMethod<TDelegate>( + object obj, + string methodName, + BindingFlags flags = BindingFlags.Public + ) where TDelegate : Delegate + { + return TryGetMethod<TDelegate>(obj, methodName, flags) + ?? throw new MissingMethodException($"Type {obj.GetType().FullName} is missing desired method {methodName}"); + } + + /// <summary> + /// A helper method that will attempt to get a named static method of the desired + /// delegate type from the specified type. + /// </summary> + /// <typeparam name="TDelegate"></typeparam> + /// <param name="type">The type to get the static method for</param> + /// <param name="methodName">The name of the static method</param> + /// <param name="flags">The optional method binind flags</param> + /// <returns>The delegate if found <see langword="null"/> otherwise</returns> + /// <exception cref="ArgumentNullException"></exception> + public static TDelegate? TryGetStaticMethod<TDelegate>(Type type, string methodName, BindingFlags flags = BindingFlags.Public) where TDelegate : Delegate + => TryGetMethodInternal<TDelegate>(type, methodName, null, flags | BindingFlags.Static); + + /// <summary> + /// A helper method that will attempt to get a named static method of the desired + /// delegate type from the specified type. + /// </summary> + /// <typeparam name="TDelegate">The delegate method type</typeparam> + /// <typeparam name="TType">The type to get the static method for</typeparam> + /// <param name="methodName">The name of the static method</param> + /// <param name="flags">The optional method binind flags</param> + /// <returns>The delegate if found <see langword="null"/> otherwise</returns> + /// <exception cref="ArgumentNullException"></exception> + public static TDelegate? TryGetStaticMethod<TDelegate, TType>(string methodName,BindingFlags flags = BindingFlags.Public) where TDelegate : Delegate + => TryGetMethodInternal<TDelegate>(typeof(TType), methodName, null, flags | BindingFlags.Static); + + private static TDelegate? TryGetMethodInternal<TDelegate>(Type type, string methodName, object? target, BindingFlags flags) where TDelegate : Delegate + { + _ = type ?? throw new ArgumentNullException(nameof(type)); + + //Get delegate argument types incase of a method overload + Type[] delegateArgs = typeof(TDelegate).GetMethod("Invoke")! + .GetParameters() + .Select(static p => p.ParameterType) + .ToArray(); + + //get the named method and always add the static flag + return type.GetMethod(methodName, flags, delegateArgs) + ?.CreateDelegate<TDelegate>(target); + } } } diff --git a/lib/Utils/tests/Memory/MemoryHandleTest.cs b/lib/Utils/tests/Memory/MemoryHandleTest.cs index 212eb0c..8880010 100644 --- a/lib/Utils/tests/Memory/MemoryHandleTest.cs +++ b/lib/Utils/tests/Memory/MemoryHandleTest.cs @@ -23,6 +23,7 @@ */ using System; +using System.Runtime.CompilerServices; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -197,6 +198,8 @@ namespace VNLib.Utils.Memory.Tests //Pin should throw Assert.ThrowsException<ArgumentOutOfRangeException>(() => _ = thandle.Pin(0)); + + Assert.ThrowsException<ObjectDisposedException>(() => _ = thandle.GetReference()); } //Full ref to mhandle check status @@ -217,6 +220,8 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException<ObjectDisposedException>(() => mHandle.Resize(10)); Assert.ThrowsException<ArgumentOutOfRangeException>(() => mHandle.BasePtr); + + Assert.ThrowsException<ObjectDisposedException>(() => _ = mHandle.GetReference()); } } } diff --git a/lib/Utils/tests/VNLib.UtilsTests.csproj b/lib/Utils/tests/VNLib.UtilsTests.csproj index c29915d..9053c51 100644 --- a/lib/Utils/tests/VNLib.UtilsTests.csproj +++ b/lib/Utils/tests/VNLib.UtilsTests.csproj @@ -16,7 +16,7 @@ <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" /> <PackageReference Include="MSTest.TestAdapter" Version="3.1.1" /> <PackageReference Include="MSTest.TestFramework" Version="3.1.1" /> <PackageReference Include="coverlet.collector" Version="6.0.0"> |