From 9e3dd9be0f0ec7aaef1a719f09f96425e66369df Mon Sep 17 00:00:00 2001 From: vnugent Date: Thu, 2 Nov 2023 01:49:02 -0400 Subject: may have gottem carried away --- lib/Utils/src/Async/IAsyncEventSink.cs | 48 ++++ lib/Utils/src/Extensions/MemoryExtensions.cs | 140 ++++++----- lib/Utils/src/IO/VnMemoryStream.cs | 87 ++++--- lib/Utils/src/Memory/ArrayPoolBuffer.cs | 211 ++++++++++++++++ lib/Utils/src/Memory/Caching/IReusable.cs | 10 +- lib/Utils/src/Memory/HeapCreation.cs | 6 +- lib/Utils/src/Memory/IMemoryHandle.cs | 8 +- lib/Utils/src/Memory/IResizeableMemoryHandle.cs | 52 ++++ lib/Utils/src/Memory/MemoryHandle.cs | 61 +++-- lib/Utils/src/Memory/MemoryUtil.cs | 300 +++++++++++++++-------- lib/Utils/src/Memory/MemoryUtilAlloc.cs | 8 +- lib/Utils/src/Memory/PrivateBuffersMemoryPool.cs | 6 +- lib/Utils/src/Memory/ProcessHeap.cs | 2 +- lib/Utils/src/Memory/SubSequence.cs | 6 +- lib/Utils/src/Memory/SysBufferMemoryManager.cs | 46 ++-- lib/Utils/src/Memory/UnmanagedHeapBase.cs | 29 +-- lib/Utils/src/Memory/UnsafeMemoryHandle.cs | 24 +- lib/Utils/src/Memory/VnString.cs | 14 +- lib/Utils/src/Memory/VnTempBuffer.cs | 194 --------------- lib/Utils/src/Memory/Win32PrivateHeap.cs | 3 +- lib/Utils/src/Resources/ManagedLibrary.cs | 138 ++++++++++- lib/Utils/tests/Memory/MemoryHandleTest.cs | 5 + lib/Utils/tests/VNLib.UtilsTests.csproj | 2 +- 23 files changed, 890 insertions(+), 510 deletions(-) create mode 100644 lib/Utils/src/Async/IAsyncEventSink.cs create mode 100644 lib/Utils/src/Memory/ArrayPoolBuffer.cs create mode 100644 lib/Utils/src/Memory/IResizeableMemoryHandle.cs delete mode 100644 lib/Utils/src/Memory/VnTempBuffer.cs (limited to 'lib/Utils') 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 +{ + /// + /// A type that receives events from asynchronous event sources and publishes + /// them to subscribers. + /// + /// The event type + public interface IAsyncEventSink + { + /// + /// Publishes a single event to all subscribers + /// + /// The event to publish + /// A value that indicates if the event was successfully published to subscribers + bool PublishEvent(T evnt); + + /// + /// Publishes an array of events to all subscribers + /// + /// The array of events to publish + /// A value that indicates if the events were successfully published to subscribers + 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 /// The minimum size array to allocate /// Should elements from 0 to size be set to default(T) /// A new encapsulating the rented array - public static UnsafeMemoryHandle Lease(this ArrayPool 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 Lease(this ArrayPool pool, int size, bool zero = false) where T : unmanaged => new(pool, size, zero); /// /// 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 /// /// The string representation of the buffer [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string ToString(this T charBuffer) where T: IMemoryHandle - { - return charBuffer.Span.ToString(); - } - - /// - /// Wraps the instance in System.Buffers.MemoryManager - /// wrapper to provide buffers from umanaged handles. - /// - /// The unmanaged data type - /// - /// - /// A value that indicates if the new owns the handle. - /// When true, the new maintains the lifetime of the handle. - /// - /// The wrapper - /// NOTE: This wrapper now manages the lifetime of the current handle - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryManager ToMemoryManager(this MemoryHandle handle, bool ownsHandle = true) where T : unmanaged - { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); - return new SysBufferMemoryManager(handle, ownsHandle); - } + public static string ToString(this T charBuffer) where T : IMemoryHandle => charBuffer.Span.ToString(); /// - /// Wraps the instance in System.Buffers.MemoryManager + /// Wraps the instance in System.Buffers.MemoryManager /// wrapper to provide buffers from umanaged handles. /// /// The unmanaged data type @@ -117,15 +91,12 @@ namespace VNLib.Utils.Extensions /// /// The wrapper /// NOTE: This wrapper now manages the lifetime of the current handle + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryManager ToMemoryManager(this VnTempBuffer handle, bool ownsHandle = true) where T : unmanaged - { - _ = handle ?? throw new ArgumentNullException(nameof(handle)); - return new SysBufferMemoryManager(handle, ownsHandle); - } + public static MemoryManager ToMemoryManager(this IMemoryHandle handle, bool ownsHandle) => new SysBufferMemoryManager(handle, ownsHandle); /// - /// Allows direct allocation of a fixed size from a instance + /// Allows direct allocation of a fixed size from a instance /// of the specified number of elements /// /// The unmanaged data type @@ -133,10 +104,14 @@ namespace VNLib.Utils.Extensions /// The number of elements to allocate on the heap /// Optionally zeros conents of the block when allocated /// The wrapper around the block of memory + /// + /// + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MemoryManager DirectAlloc(this IUnmangedHeap heap, nuint size, bool zero = false) where T : unmanaged { - return new SysBufferMemoryManager(heap, size, zero); + MemoryHandle handle = heap.Alloc(size, zero); + return new SysBufferMemoryManager(handle, true); } /// @@ -163,10 +138,10 @@ namespace VNLib.Utils.Extensions /// //Method only exists for consistancy since unsafe handles are always 32bit [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetIntLength(this in UnsafeMemoryHandle handle) where T: unmanaged => handle.IntLength; + public static int GetIntLength(this in UnsafeMemoryHandle handle) where T : unmanaged => handle.IntLength; /// - /// Allows direct allocation of a fixed size from a instance + /// Allows direct allocation of a fixed size from a instance /// of the specified number of elements /// /// The unmanaged data type @@ -181,6 +156,7 @@ namespace VNLib.Utils.Extensions { return size >= 0 ? DirectAlloc(heap, (nuint)size, zero) : throw new ArgumentOutOfRangeException(nameof(size), "The size paramter must be a positive integer"); } + /// /// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks /// @@ -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"); } + /// /// Resizes the current handle on the heap /// @@ -204,7 +181,7 @@ namespace VNLib.Utils.Extensions /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Resize(this MemoryHandle memory, nint elements) where T : unmanaged + public static void Resize(this IResizeableMemoryHandle memory, nint elements) { if (elements < 0) { @@ -224,7 +201,7 @@ namespace VNLib.Utils.Extensions /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResizeIfSmaller(this MemoryHandle handle, nint count) where T : unmanaged + public static void ResizeIfSmaller(this IResizeableMemoryHandle handle, nint count) { if(count < 0) { @@ -244,7 +221,7 @@ namespace VNLib.Utils.Extensions /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ResizeIfSmaller(this MemoryHandle handle, nuint count) where T : unmanaged + public static void ResizeIfSmaller(this IResizeableMemoryHandle handle, nuint count) { //Check handle size if(handle.Length < count) @@ -254,6 +231,52 @@ namespace VNLib.Utils.Extensions } } + /// + /// Gets a reference to the element at the specified offset from the base + /// address of the + /// + /// + /// The element offset from the base address to add to the returned reference + /// The reference to the item at the desired offset + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetOffsetRef(this IMemoryHandle 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); + } + + /// + /// Gets a reference to the element at the specified offset from the base + /// address of the and casts it to a byte reference + /// + /// + /// + /// The number of elements to offset the base reference by + /// The reinterpreted byte reference at the first byte of the element offset + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref byte GetByteOffsetRef(this IMemoryHandle 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(ref offsetRef); + } /// /// Gets a 64bit friendly span offset for the current @@ -265,7 +288,7 @@ namespace VNLib.Utils.Extensions /// The offset span /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe Span GetOffsetSpan(this MemoryHandle block, nuint offset, int size) where T: unmanaged + public static Span GetOffsetSpan(this IMemoryHandle 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(ofPtr, size); + ref T ofPtr = ref GetOffsetRef(block, offset); + return MemoryMarshal.CreateSpan(ref ofPtr, size); } + /// /// Gets a 64bit friendly span offset for the current /// @@ -295,7 +319,7 @@ namespace VNLib.Utils.Extensions /// The offset span /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe Span GetOffsetSpan(this MemoryHandle block, nint offset, int size) where T : unmanaged + public static unsafe Span GetOffsetSpan(this IMemoryHandle 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 /// The new within the block /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SubSequence GetSubSequence(this MemoryHandle block, nuint offset, int size) where T : unmanaged => new (block, offset, size); + public static SubSequence GetSubSequence(this IMemoryHandle block, nuint offset, int size) => new (block, offset, size); /// /// Gets a window within the current block @@ -322,7 +346,7 @@ namespace VNLib.Utils.Extensions /// The new within the block /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SubSequence GetSubSequence(this MemoryHandle block, nint offset, int size) where T : unmanaged + public static SubSequence GetSubSequence(this IMemoryHandle 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 /// The unmanged data type to provide allocations from /// The new heap wrapper. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MemoryPool ToPool(this IUnmangedHeap heap, int maxBufferSize = int.MaxValue) where T : unmanaged - { - return new PrivateBuffersMemoryPool(heap, maxBufferSize); - } + public static MemoryPool ToPool(this IUnmangedHeap heap, int maxBufferSize = int.MaxValue) where T : unmanaged => new PrivateBuffersMemoryPool(heap, maxBufferSize); /// /// 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 /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe T* StructAlloc(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(this IUnmangedHeap heap) where T : unmanaged => (T*)heap.Alloc(1, (nuint)sizeof(T), true); + /// /// 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 /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void WriteAndResize(this MemoryHandle handle, ReadOnlySpan input) where T: unmanaged + public static void WriteAndResize(this IResizeableMemoryHandle handle, ReadOnlySpan 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); } + /// /// 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; } + /// /// 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; } + /// /// 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 /// A instance that owns the underlying string memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PrivateString ToPrivate(this ref ForwardOnlyWriter buffer) => new(buffer.ToString(), true); + /// /// Gets a over the modified section of the internal buffer /// @@ -711,6 +730,7 @@ namespace VNLib.Utils.Extensions Range sliceRange = new(start, arr.Length - start); return RuntimeHelpers.GetSubArray(arr, sliceRange); } + /// /// 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 { + /// /// Provides an unmanaged memory stream. Desigend to help reduce garbage collector load for /// high frequency memory operations. Similar to @@ -44,35 +47,39 @@ namespace VNLib.Utils.IO private bool _isReadonly; //Memory - private readonly MemoryHandle _buffer; + private readonly IResizeableMemoryHandle _buffer; //Default owns handle private readonly bool OwnsHandle = true; /// /// Creates a new pointing to the begining of memory, and consumes the handle. /// - /// to consume + /// to consume /// Length of the stream /// Should the stream be readonly? /// /// A wrapper to access the handle data - public static VnMemoryStream ConsumeHandle(MemoryHandle handle, nint length, bool readOnly) => FromHandle(handle, true, length, readOnly); + public static VnMemoryStream ConsumeHandle(IResizeableMemoryHandle handle, nint length, bool readOnly) => FromHandle(handle, true, length, readOnly); /// /// Creates a new 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. /// - /// to consume + /// to consume /// The initial length of the stream /// Should the stream be readonly? /// A value that indicates if the current stream owns the memory handle /// /// A wrapper to access the handle data - public static VnMemoryStream FromHandle(MemoryHandle handle, bool ownsHandle, nint length, bool readOnly) + public static VnMemoryStream FromHandle(IResizeableMemoryHandle 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)); } /// @@ -155,8 +162,11 @@ namespace VNLib.Utils.IO /// The length property of the stream /// Is the stream readonly (should mostly be true!) /// Does the new stream own the memory -> - private VnMemoryStream(MemoryHandle buffer, nint length, bool readOnly, bool ownsHandle) + private VnMemoryStream(IResizeableMemoryHandle 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); + } } /// @@ -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 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 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 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"); + } } /// @@ -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/ArrayPoolBuffer.cs b/lib/Utils/src/Memory/ArrayPoolBuffer.cs new file mode 100644 index 0000000..92a2022 --- /dev/null +++ b/lib/Utils/src/Memory/ArrayPoolBuffer.cs @@ -0,0 +1,211 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: VnTempBuffer.cs +* +* VnTempBuffer.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; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using VNLib.Utils.Extensions; + +namespace VNLib.Utils.Memory +{ + /// + /// A disposable temporary buffer from shared ArrayPool + /// + /// Type of buffer to create + public sealed class ArrayPoolBuffer : VnDisposeable, IIndexable, IMemoryHandle, IMemoryOwner + { + private readonly ArrayPool Pool; + + /// + /// Referrence to internal buffer + /// + public T[] Buffer { get; private set; } + + /// + /// Inital/desired size of internal buffer + /// + public int InitSize { get; } + + /// + /// Actual length of internal buffer + /// + public nuint Length => (nuint)Buffer.LongLength; + + /// + /// + public Span Span + { + get + { + Check(); + return new Span(Buffer, 0, InitSize); + } + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T GetReference() => ref MemoryMarshal.GetArrayDataReference(Buffer); + + /// + Memory IMemoryOwner.Memory => AsMemory(); + + /// + /// Allocates a new with a new buffer from shared array-pool + /// + /// Minimum size of the buffer + /// Set the zero memory flag on close + public ArrayPoolBuffer(int minSize, bool zero = false) :this(ArrayPool.Shared, minSize, zero) + { } + + /// + /// Allocates a new with a new buffer from specified array-pool + /// + /// The to allocate from and return to + /// Minimum size of the buffer + /// Set the zero memory flag on close + public ArrayPoolBuffer(ArrayPool pool, int minSize, bool zero = false) + { + Pool = pool ?? throw new ArgumentNullException(nameof(pool)); + Buffer = pool.Rent(minSize, zero); + InitSize = minSize; + } + + /// + /// Gets an offset wrapper around the current buffer + /// + /// Offset from begining of current buffer + /// Number of from offset + /// An wrapper around the current buffer containing the offset + public ArraySegment GetOffsetWrapper(int offset, int count) + { + Check(); + //Let arraysegment throw exceptions for checks + return new ArraySegment(Buffer, offset, count); + } + + /// + public T this[int index] + { + get + { + Check(); + return Buffer[index]; + } + set + { + Check(); + Buffer[index] = value; + } + } + + /// + /// Gets a memory structure around the internal buffer + /// + /// A memory structure over the buffer + /// + /// + public Memory AsMemory() + { + Check(); + return new Memory(Buffer, 0, InitSize); + } + + /// + /// Gets a memory structure around the internal buffer + /// + /// The number of elements included in the result + /// A memory structure over the buffer + /// + /// + public Memory AsMemory(int count) => AsMemory()[..count]; + + /// + /// Gets a memory structure around the internal buffer + /// + /// The number of elements included in the result + /// A value specifying the begining index of the buffer to include + /// A memory structure over the buffer + /// + /// + public Memory AsMemory(int start, int count) => AsMemory().Slice(start, count); + + /// + /// Gets an array segment around the internal buffer + /// + /// The internal array segment + /// + public ArraySegment AsArraySegment() + { + Check(); + return new ArraySegment(Buffer, 0, InitSize); + } + + /// + /// Gets an array segment around the internal buffer + /// + /// The internal array segment + /// + /// + public ArraySegment 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(Buffer, start, count); + } + + //Pin, will also check bounds + /// + public MemoryHandle Pin(int elementIndex) => MemoryUtil.PinArrayAndGetHandle(Buffer, elementIndex); + + void IPinnable.Unpin() + { + //Gchandle will manage the unpin + } + + /// + /// Returns buffer to shared array-pool + /// + protected override void Free() + { + //Return the buffer to the array pool + Pool.Return(Buffer); + + //Set buffer to null, +#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. + Buffer = null; +#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. + } + + /// + ~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 { /// - /// Allows for use within a , this object is intended to be reused heavily + /// Allows for use within a , this object is intended to be reused heavily /// public interface IReusable { /// /// The instance should prepare itself for use (or re-use) + /// + /// 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. + /// /// void Prepare(); + /// /// The intance is being returned and should determine if it's state is reusabled /// 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 /// /// Specifies that the requested heap will be a shared heap for the process/library /// - Shared = 0x04 + Shared = 0x04, + /// + /// Specifies that the heap will support block reallocation + /// + 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 /// Span Span { get; } + + /// + /// Gets a reference to the first element in the block + /// + /// The reference + 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 +{ + + /// + /// Represents a memory handle that can be resized in place. + /// + /// The data type + public interface IResizeableMemoryHandle : IMemoryHandle + { + /// + /// Gets a value indicating whether the handle supports resizing in place + /// + bool CanRealloc { get; } + + /// + /// Resizes a memory handle to a new number of elements. + /// + /// + /// Even if a handle is resizable resizing may not be supported for all types of handles. + /// + /// The new number of elements to resize the handle to + /// + /// + 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. /// - public sealed class MemoryHandle : SafeHandleZeroOrMinusOneIsInvalid, IMemoryHandle, IEquatable> where T : unmanaged + public sealed class MemoryHandle : + SafeHandleZeroOrMinusOneIsInvalid, + IResizeableMemoryHandle, + IMemoryHandle, + IEquatable> + where T : unmanaged { + private readonly bool ZeroMemory; + private readonly IUnmangedHeap Heap; + private nuint _length; /// /// New * 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; - /// - public nuint Length + public nuint Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _length; + get => _length; } /// @@ -101,6 +105,13 @@ namespace VNLib.Utils.Memory get => MemoryUtil.ByteCount(_length); } + /// + public bool CanRealloc + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Heap != null && Heap.CreationFlags.HasFlag(HeapCreation.SupportsRealloc); + } + /// /// Creates a new memory handle, for which is holds ownership, and allocates the number of elements specified on the heap. /// @@ -128,7 +139,7 @@ namespace VNLib.Utils.Memory /// when accessed, however operations are /// considered "safe" meaning they should never raise excpetions /// - 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; } } - + /// /// Gets an offset pointer from the base postion to the number of bytes specified. Performs bounds checks /// @@ -190,7 +201,14 @@ namespace VNLib.Utils.Memory T* bs = ((T*)handle) + elements; return bs; } - + + /// + public ref T GetReference() + { + this.ThrowIfClosed(); + return ref MemoryUtil.GetRef(handle); + } + /// /// /// @@ -200,14 +218,14 @@ namespace VNLib.Utils.Memory /// 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); } - + /// /// public void Unpin() @@ -226,11 +244,7 @@ namespace VNLib.Utils.Memory } /// - 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); /// /// 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; } - + /// public override bool Equals(object? obj) => obj is MemoryHandle oHandle && Equals(oHandle); - + /// public override int GetHashCode() => base.GetHashCode(); - /// public static implicit operator Span(MemoryHandle 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 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 /// Unmanged datatype /// Block of memory to be cleared [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - public static void UnsafeZeroMemory(ReadOnlySpan block) where T : unmanaged + public static void UnsafeZeroMemory(ReadOnlySpan block) where T : struct { if (block.IsEmpty) { @@ -244,11 +244,11 @@ namespace VNLib.Utils.Memory uint byteSize = ByteCount((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(ref r0); + + //Calls memset + Unsafe.InitBlock(ref byteRef, 0, byteSize); } /// @@ -257,7 +257,7 @@ namespace VNLib.Utils.Memory /// Unmanged datatype /// Block of memory to be cleared [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] - public static void UnsafeZeroMemory(ReadOnlyMemory block) where T : unmanaged + public static void UnsafeZeroMemory(ReadOnlyMemory block) where T : struct { if (block.IsEmpty) { @@ -284,7 +284,7 @@ namespace VNLib.Utils.Memory /// The unmanaged /// The block of memory to initialize [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeBlock(Span block) where T : unmanaged => UnsafeZeroMemory(block); + public static void InitializeBlock(Span block) where T : struct => UnsafeZeroMemory(block); /// /// Initializes a block of memory with zeros @@ -292,7 +292,7 @@ namespace VNLib.Utils.Memory /// The unmanaged /// The block of memory to initialize [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void InitializeBlock(Memory block) where T : unmanaged => UnsafeZeroMemory(block); + public static void InitializeBlock(Memory block) where T : struct => UnsafeZeroMemory(block); /// /// 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* 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((uint)itemCount)); } /// @@ -321,33 +318,24 @@ namespace VNLib.Utils.Memory /// The unmanaged type to zero /// A pointer to the block of memory to zero /// The number of elements in the block to zero + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void InitializeBlock(IntPtr block, int itemCount) where T : unmanaged => InitializeBlock((T*)block, itemCount); /// /// Zeroes a block of memory pointing to the structure /// /// The structure type - /// The pointer to the allocated structure - public static void ZeroStruct(IntPtr block) - { - //get thes size of the structure does not have to be primitive type - int size = Unsafe.SizeOf(); - //Zero block - Unsafe.InitBlock(block.ToPointer(), 0, (uint)size); - } + /// The pointer to the allocated structure + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ZeroStruct(void* structPtr) => Unsafe.InitBlock(structPtr, 0, (uint)Unsafe.SizeOf()); /// /// Zeroes a block of memory pointing to the structure /// /// The structure type - /// The pointer to the allocated structure - public static void ZeroStruct(void* structPtr) - { - //get thes size of the structure - int size = Unsafe.SizeOf(); - //Zero block - Unsafe.InitBlock(structPtr, 0, (uint)size); - } + /// The pointer to the allocated structure + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ZeroStruct(IntPtr block) => ZeroStruct(block.ToPointer()); /// /// Zeroes a block of memory pointing to the structure @@ -355,12 +343,20 @@ namespace VNLib.Utils.Memory /// The structure type /// The pointer to the allocated structure [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ZeroStruct(T* structPtr) where T : unmanaged => Unsafe.InitBlock(structPtr, 0, (uint)sizeof(T)); + public static void ZeroStruct(T* structPtr) where T : unmanaged => ZeroStruct((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(typeof(Buffer), "Memmove", System.Reflection.BindingFlags.NonPublic); + /// /// Copies data from source memory to destination memory of an umanged data type /// @@ -369,7 +365,7 @@ namespace VNLib.Utils.Memory /// Destination /// Dest offset /// - public static void Copy(ReadOnlySpan source, MemoryHandle dest, nuint destOffset) where T : unmanaged + public static void Copy(ReadOnlySpan source, IMemoryHandle 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 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((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"); } /// @@ -396,24 +398,7 @@ namespace VNLib.Utils.Memory /// Destination /// Dest offset /// - public static void Copy(ReadOnlyMemory source, MemoryHandle 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 dst = dest.GetOffsetSpan(destOffset, source.Length); - - //Copy data - source.Span.CopyTo(dst); - } + public static void Copy(ReadOnlyMemory source, IMemoryHandle dest, nuint destOffset) where T : struct => Copy(source.Span, dest, destOffset); /// /// Copies data from source memory to destination memory of an umanged data type @@ -425,10 +410,12 @@ namespace VNLib.Utils.Memory /// Dest offset /// Number of elements to copy /// - public static void Copy(MemoryHandle source, nint sourceOffset, Span dest, int destOffset, int count) where T : unmanaged + public static void Copy(IMemoryHandle source, nint sourceOffset, Span 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 src = source.GetOffsetSpan(sourceOffset, count); - - //slice the dest span - Span 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((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"); } /// @@ -458,13 +448,51 @@ namespace VNLib.Utils.Memory /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(MemoryHandle source, nint sourceOffset, Memory dest, int destOffset, int count) where T : unmanaged + public static void Copy(IMemoryHandle source, nint sourceOffset, Memory dest, int destOffset, int count) where T : struct + => Copy(source, sourceOffset, dest.Span, destOffset, count); + + /// + /// Copies data from source memory to destination memory of an umanged data type + /// using references for blocks smaller than and + /// pinning for larger blocks + /// + /// Unmanged type + /// Source data + /// Number of elements to offset source data + /// Destination + /// Dest offset + /// Number of elements to copy + /// + /// + public static void Copy(IMemoryHandle source, nuint sourceOffset, IMemoryHandle 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(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 } /// - /// 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 /// /// /// The source memory handle to copy data from - /// The element offset to begin reading from + /// The element offset to begin reading from /// The destination array to write data to /// /// The number of elements to copy /// /// - public static void Copy(IMemoryHandle source, nuint offset, T[] dest, nuint destOffset, nuint count) where T : unmanaged + public static void Copy(IMemoryHandle 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(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(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(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(ref srcOffsetPtr); + ref byte dstByte = ref Unsafe.As(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 src = source.AsSpan((int)offset, (int)count); - Span dst = dest.AsSpan((int)destOffset, (int)count); - //Copy - src.CopyTo(dst); + return false; } } @@ -597,6 +648,26 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint ByteCount(uint elementCount) => checked(elementCount * (uint)Unsafe.SizeOf()); + /// + /// Gets the byte multiple of the length parameter. NOTE: Does not verify negative values + /// + /// The type to get the byte offset of + /// The number of elements to get the byte count of + /// The byte multiple of the number of elments + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint ByteCount(nint elementCount) => checked(elementCount * Unsafe.SizeOf()); + + /// + /// Gets the byte multiple of the length parameter. NOTE: Does not verify negative values + /// + /// The type to get the byte offset of + /// The number of elements to get the byte count of + /// The byte multiple of the number of elments + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ByteCount(int elementCount) => checked(elementCount * Unsafe.SizeOf()); + /// /// Checks if the offset/count paramters for the given memory handle /// point outside the block wrapped in the handle @@ -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(basePtr, elementOffset); + ref T indexOffet = ref Unsafe.Add(ref arrBase, elementOffset); - return new(indexOffet, arrHandle); + return new(Unsafe.AsPointer(ref indexOffet), arrHandle); } /// @@ -740,6 +811,29 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Span GetSpan(MemoryHandle handle, int size) => new(handle.Pointer, size); + /// + /// Recovers a reference to the supplied pointer + /// + /// + /// The base address to cast to a reference + /// The reference to the supplied address + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetRef(IntPtr address) => ref Unsafe.AsRef(address.ToPointer()); + + /// + /// Recovers a reference to the supplied pointer + /// + /// + /// The base address to cast to a reference + /// The offset to add to the base address + /// The reference to the supplied address + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetRef(IntPtr address, nuint offset) + { + ref T baseRef = ref GetRef(address); + return ref Unsafe.Add(ref baseRef, (nint)offset); + } + /// /// Rounds the requested byte size up to the nearest page /// number of bytes 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(elements)); //Resize to element size np /= sizeof(T); @@ -131,7 +131,7 @@ namespace VNLib.Utils.Memory } else { - return new VnTempBuffer(ArrayPool.Shared, elements, zero); + return new ArrayPoolBuffer(ArrayPool.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(elements)); //Resize to element size np /= sizeof(T); @@ -258,7 +258,7 @@ namespace VNLib.Utils.Memory } else { - return new VnTempBuffer(ArrayPool.Shared, elements, zero); + return new ArrayPoolBuffer(ArrayPool.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 { /// @@ -48,7 +50,7 @@ namespace VNLib.Utils.Memory /// /// /// - public override IMemoryOwner Rent(int minBufferSize = 0) => new SysBufferMemoryManager(Heap, (uint)minBufferSize, false); + public override IMemoryOwner Rent(int minBufferSize = 0) => Heap.DirectAlloc(minBufferSize, false); /// /// Allocates a new of a different data type from the pool @@ -56,7 +58,7 @@ namespace VNLib.Utils.Memory /// The unmanaged data type to allocate for /// Minumum size of the buffer /// The memory owner of a different data type - public IMemoryOwner Rent(int minBufferSize = 0) where TDifType : unmanaged => new SysBufferMemoryManager(Heap, (uint)minBufferSize, false); + public IMemoryOwner Rent(int minBufferSize = 0) where TDifType : unmanaged => Heap.DirectAlloc(minBufferSize, false); /// 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 /// /// - public HeapCreation CreationFlags { get; } = HeapCreation.Shared; + public HeapCreation CreationFlags { get; } = HeapCreation.Shared | HeapCreation.SupportsRealloc; /// /// 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 /// /// The unmanaged type to wrap - public readonly record struct SubSequence where T: unmanaged + public readonly record struct SubSequence { readonly nuint _offset; /// /// The handle that owns the memory block /// - public readonly MemoryHandle Handle { get; } + public readonly IMemoryHandle Handle { get; } /// /// The number of elements in the current sequence @@ -54,7 +54,7 @@ namespace VNLib.Utils.Memory /// /// /// - public SubSequence(MemoryHandle block, nuint offset, int size) + public SubSequence(IMemoryHandle 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 { /// @@ -34,7 +32,7 @@ namespace VNLib.Utils.Memory /// as a memory provider which implements a /// /// Unmanaged memory type - public sealed class SysBufferMemoryManager : MemoryManager where T :unmanaged + public sealed class SysBufferMemoryManager : MemoryManager { private readonly IMemoryHandle BackingMemory; private readonly bool _ownsHandle; @@ -45,30 +43,19 @@ namespace VNLib.Utils.Memory /// /// The existing handle to consume /// A value that indicates if the memory manager owns the handle reference - internal SysBufferMemoryManager(IMemoryHandle existingHandle, bool ownsHandle) + /// + /// + public SysBufferMemoryManager(IMemoryHandle existingHandle, bool ownsHandle) { - BackingMemory = existingHandle; - _ownsHandle = ownsHandle; - } - - /// - /// Allocates a fized size buffer from the specified unmanaged - /// - /// The heap to perform allocations from - /// The number of elements to allocate - /// Zero allocations - public SysBufferMemoryManager(IUnmangedHeap heap, nuint elements, bool zero) - { - BackingMemory = heap.Alloc(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"); + } - /// - protected override bool TryGetArray(out ArraySegment segment) - { - //Always false since no array is available - segment = default; - return false; + _ownsHandle = ownsHandle; } /// @@ -81,11 +68,8 @@ namespace VNLib.Utils.Memory /// /// /// - public unsafe override MemoryHandle Pin(int elementIndex = 0) - { - return BackingMemory.Pin(elementIndex); - } - + public unsafe override MemoryHandle Pin(int elementIndex = 0) => BackingMemory.Pin(elementIndex); + /// 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 /// public abstract class UnmanagedHeapBase : SafeHandleZeroOrMinusOneIsInvalid, IUnmangedHeap { + private readonly HeapCreation _flags; + /// /// The heap synchronization handle /// protected readonly object HeapLock; - - /// - /// The global heap zero flag - /// - protected readonly bool GlobalZero; - - /// - /// A value that inidicates that locking will - /// be used when invoking heap operations - /// - protected readonly bool UseSynchronization; /// /// 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; } /// - public HeapCreation CreationFlags { get; } + public HeapCreation CreationFlags => _flags; /// ///Increments the handle count, free must be called to decrement the handle count @@ -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(_memoryPtr.ToPointer(), elementIndex); + //Add an offset to the base address of the memory block + int byteOffset = MemoryUtil.ByteCount(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); } } /// @@ -186,6 +186,20 @@ namespace VNLib.Utils.Memory //Nothing to do since gc handle takes care of array, and unmanaged pointers are not pinned } + /// + public readonly ref T GetReference() + { + switch (_handleType) + { + case HandleType.Pool: + return ref MemoryMarshal.GetArrayDataReference(_poolArr!); + case HandleType.PrivateHeap: + return ref MemoryUtil.GetRef(_memoryPtr); + default: + throw new InvalidOperationException("The handle is empty, and cannot capture a reference"); + } + } + /// /// 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, IEquatable, IEquatable, IComparable, IComparable { - private readonly MemoryHandle? Handle; + private readonly IMemoryHandle? Handle; private readonly SubSequence _stringSequence; @@ -63,7 +63,7 @@ namespace VNLib.Utils.Memory _stringSequence = sequence; } - private VnString(MemoryHandle handle, nuint start, int length) + private VnString(IMemoryHandle handle, nuint start, int length) { Handle = handle ?? throw new ArgumentNullException(nameof(handle)); //get sequence @@ -517,14 +517,10 @@ namespace VNLib.Utils.Memory /// /// public int GetHashCode(StringComparison stringComparison) => string.GetHashCode(AsSpan(), stringComparison); - + /// - 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/VnTempBuffer.cs b/lib/Utils/src/Memory/VnTempBuffer.cs deleted file mode 100644 index 5f5f831..0000000 --- a/lib/Utils/src/Memory/VnTempBuffer.cs +++ /dev/null @@ -1,194 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: VnTempBuffer.cs -* -* VnTempBuffer.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; -using System.Buffers; - -using VNLib.Utils.Extensions; - -namespace VNLib.Utils.Memory -{ - /// - /// A disposable temporary buffer from shared ArrayPool - /// - /// Type of buffer to create - public sealed class VnTempBuffer : VnDisposeable, IIndexable, IMemoryHandle, IMemoryOwner - { - private readonly ArrayPool Pool; - - /// - /// Referrence to internal buffer - /// - public T[] Buffer { get; private set; } - - /// - /// Inital/desired size of internal buffer - /// - public int InitSize { get; } - - /// - /// Actual length of internal buffer - /// - public nuint Length => (nuint)Buffer.LongLength; - - /// - /// - public Span Span - { - get - { - Check(); - return new Span(Buffer, 0, InitSize); - } - } - - /// - Memory IMemoryOwner.Memory => AsMemory(); - - /// - /// Allocates a new with a new buffer from shared array-pool - /// - /// Minimum size of the buffer - /// Set the zero memory flag on close - public VnTempBuffer(int minSize, bool zero = false) :this(ArrayPool.Shared, minSize, zero) - {} - - /// - /// Allocates a new with a new buffer from specified array-pool - /// - /// The to allocate from and return to - /// Minimum size of the buffer - /// Set the zero memory flag on close - public VnTempBuffer(ArrayPool pool, int minSize, bool zero = false) - { - Pool = pool; - Buffer = pool.Rent(minSize, zero); - InitSize = minSize; - } - - /// - /// Gets an offset wrapper around the current buffer - /// - /// Offset from begining of current buffer - /// Number of from offset - /// An wrapper around the current buffer containing the offset - public ArraySegment GetOffsetWrapper(int offset, int count) - { - Check(); - //Let arraysegment throw exceptions for checks - return new ArraySegment(Buffer, offset, count); - } - - /// - public T this[int index] - { - get - { - Check(); - return Buffer[index]; - } - set - { - Check(); - Buffer[index] = value; - } - } - - /// - /// Gets a memory structure around the internal buffer - /// - /// A memory structure over the buffer - /// - /// - public Memory AsMemory() - { - Check(); - return new Memory(Buffer, 0, InitSize); - } - - /// - /// Gets a memory structure around the internal buffer - /// - /// The number of elements included in the result - /// A value specifying the begining index of the buffer to include - /// A memory structure over the buffer - /// - /// - public Memory AsMemory(int start, int count) - { - Check(); - return new Memory(Buffer, start, count); - } - - /// - /// Gets a memory structure around the internal buffer - /// - /// The number of elements included in the result - /// A memory structure over the buffer - /// - /// - public Memory AsMemory(int count) - { - Check(); - return new Memory(Buffer, 0, count); - } - - /* - * Allow implict casts to span/arrayseg/memory - */ - public static implicit operator Memory(VnTempBuffer buf) => buf == null ? Memory.Empty : buf.ToMemory(); - public static implicit operator Span(VnTempBuffer buf) => buf == null ? Span.Empty : buf.ToSpan(); - public static implicit operator ArraySegment(VnTempBuffer buf) => buf == null ? ArraySegment.Empty : buf.ToArraySegment(); - - public Memory ToMemory() => Disposed ? Memory.Empty : Buffer.AsMemory(0, InitSize); - public Span ToSpan() => Disposed ? Span.Empty : Buffer.AsSpan(0, InitSize); - public ArraySegment ToArraySegment() => Disposed ? ArraySegment.Empty : new(Buffer, 0, InitSize); - - /// - /// Returns buffer to shared array-pool - /// - protected override void Free() - { - //Return the buffer to the array pool - Pool.Return(Buffer); - - //Set buffer to null, -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Buffer = null; -#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. - } - - //Pin, will also check bounds - /// - public MemoryHandle Pin(int elementIndex) => MemoryUtil.PinArrayAndGetHandle(Buffer, elementIndex); - - void IPinnable.Unpin() - { - //Gchandle will manage the unpin - } - - /// - ~VnTempBuffer() => Free(); - } -} \ No newline at end of file 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; } /// 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); /// /// Raised when the load context that owns this assembly @@ -136,18 +134,50 @@ namespace VNLib.Utils.Resources /// public T LoadTypeFromAssembly() { - 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() ?? throw new EntryPointNotFoundException($"Imported assembly does not export desired type {typeof(T).FullName}"); //Create instance return (T)Activator.CreateInstance(exp)!; - } + } + + /// + /// Gets the type exported from the current assembly that is + /// assignable to the desired type. + /// + /// The desired base type to get the exported type of + /// The exported type that matches the desired type from the current assembly + public Type? TryGetExportedType() => TryGetExportedType(typeof(T)); + + /// + /// Gets the type exported from the current assembly that is + /// assignable to the desired type. + /// + /// The desired base type to get the exported type of + /// The exported type that matches the desired type from the current assembly + public Type? TryGetExportedType(Type resourceType) => TryGetAllMatchingTypes(resourceType).FirstOrDefault(); + + /// + /// Gets all exported types from the current assembly that are + /// assignable to the desired type. + /// + /// The desired resource type + /// An enumeration of acceptable types + public IEnumerable TryGetAllMatchingTypes() => TryGetAllMatchingTypes(typeof(T)); + + /// + /// Gets all exported types from the current assembly that are + /// assignable to the desired type. + /// + /// The desired resource type + /// An enumeration of acceptable types + public IEnumerable 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; + } /// /// 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); } + + /// + /// A helper method that will attempt to get a named method of the desired + /// delegate type from the specified object. + /// + /// The method delegate that matches the signature of the desired method + /// The object to discover and bind the found method to + /// The name of the method to capture + /// The method binding flags + /// The namaed method delegate for the object type, or null if the method was not found + /// + public static TDelegate? TryGetMethod( + object obj, + string methodName, + BindingFlags flags = BindingFlags.Public + ) where TDelegate : Delegate + { + _ = obj ?? throw new ArgumentNullException(nameof(obj)); + return TryGetMethodInternal(obj.GetType(), methodName, obj, flags | BindingFlags.Instance); + } + + /// + /// A helper method that will attempt to get a named method of the desired + /// delegate type from the specified object. + /// + /// The method delegate that matches the signature of the desired method + /// The object to discover and bind the found method to + /// The name of the method to capture + /// The method binding flags + /// The namaed method delegate for the object type or an exception if not found + /// + /// + public static TDelegate GetMethod( + object obj, + string methodName, + BindingFlags flags = BindingFlags.Public + ) where TDelegate : Delegate + { + return TryGetMethod(obj, methodName, flags) + ?? throw new MissingMethodException($"Type {obj.GetType().FullName} is missing desired method {methodName}"); + } + + /// + /// A helper method that will attempt to get a named static method of the desired + /// delegate type from the specified type. + /// + /// + /// The type to get the static method for + /// The name of the static method + /// The optional method binind flags + /// The delegate if found otherwise + /// + public static TDelegate? TryGetStaticMethod(Type type, string methodName, BindingFlags flags = BindingFlags.Public) where TDelegate : Delegate + => TryGetMethodInternal(type, methodName, null, flags | BindingFlags.Static); + + /// + /// A helper method that will attempt to get a named static method of the desired + /// delegate type from the specified type. + /// + /// The delegate method type + /// The type to get the static method for + /// The name of the static method + /// The optional method binind flags + /// The delegate if found otherwise + /// + public static TDelegate? TryGetStaticMethod(string methodName,BindingFlags flags = BindingFlags.Public) where TDelegate : Delegate + => TryGetMethodInternal(typeof(TType), methodName, null, flags | BindingFlags.Static); + + private static TDelegate? TryGetMethodInternal(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(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(() => _ = thandle.Pin(0)); + + Assert.ThrowsException(() => _ = thandle.GetReference()); } //Full ref to mhandle check status @@ -217,6 +220,8 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException(() => mHandle.Resize(10)); Assert.ThrowsException(() => mHandle.BasePtr); + + Assert.ThrowsException(() => _ = 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 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + -- cgit