From d37ea17d49dc29b972bcd863acbcb81fd054fe2e Mon Sep 17 00:00:00 2001 From: vnugent Date: Fri, 3 Nov 2023 18:34:06 -0400 Subject: a little api polish --- .../src/IdentityUtility/HashingExtensions.cs | 4 +- .../src/IdentityUtility/JsonWebToken.cs | 2 +- lib/Hashing.Portable/src/ManagedHash.cs | 8 +- lib/Net.Messaging.FBM/src/Client/FBMRequest.cs | 2 +- .../src/Accounts/PasswordHashing.cs | 2 +- lib/Utils/src/Extensions/MemoryExtensions.cs | 44 ++++- lib/Utils/src/Memory/ArrayPoolBuffer.cs | 33 +++- lib/Utils/src/Memory/IResizeableMemoryHandle.cs | 6 +- lib/Utils/src/Memory/IUnmangedHeap.cs | 6 +- lib/Utils/src/Memory/MemoryHandle.cs | 17 +- lib/Utils/src/Memory/MemoryUtil.cs | 82 ++++++++- lib/Utils/src/Memory/MemoryUtilAlloc.cs | 31 ++-- lib/Utils/src/Memory/UnmanagedHeapBase.cs | 6 +- lib/Utils/src/Memory/UnsafeMemoryHandle.cs | 118 ++++++------ lib/Utils/src/Memory/VnString.cs | 198 +++++++++++---------- lib/Utils/src/VnEncoding.cs | 109 +++++++----- lib/Utils/tests/Memory/MemoryUtilTests.cs | 6 +- 17 files changed, 427 insertions(+), 247 deletions(-) diff --git a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs index 278359c..65704df 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs @@ -163,7 +163,7 @@ namespace VNLib.Hashing.IdentityUtility using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(hashBufSize); //compute hash - if (!hmac.TryComputeHash(raw, buffer, out int hashBytesWritten)) + if (!hmac.TryComputeHash(raw, buffer.Span, out int hashBytesWritten)) { throw new InternalBufferTooSmallException("Hash buffer size was too small"); } @@ -199,7 +199,7 @@ namespace VNLib.Hashing.IdentityUtility using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(buffSize, true); //Encode data - int converted = enc.GetBytes(data, buffer); + int converted = enc.GetBytes(data, buffer.Span); //Try encrypt return !alg.TryEncrypt(buffer.Span, output, padding, out int bytesWritten) ? ERRNO.E_FAIL : (ERRNO)bytesWritten; diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs index c5409a8..158a1b8 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs @@ -69,7 +69,7 @@ namespace VNLib.Hashing.IdentityUtility using MemoryHandle binBuffer = heap.Alloc(utf8Size, true); //Decode to utf8 - utf8Size = textEncoding.GetBytes(urlEncJwtString, binBuffer); + utf8Size = textEncoding.GetBytes(urlEncJwtString, binBuffer.Span); //Parse and return the jwt return ParseRaw(binBuffer.Span[..utf8Size], heap); diff --git a/lib/Hashing.Portable/src/ManagedHash.cs b/lib/Hashing.Portable/src/ManagedHash.cs index 5a6aaba..43c2a7c 100644 --- a/lib/Hashing.Portable/src/ManagedHash.cs +++ b/lib/Hashing.Portable/src/ManagedHash.cs @@ -136,7 +136,7 @@ namespace VNLib.Hashing using UnsafeMemoryHandle binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf); + byteCount = CharEncoding.GetBytes(data, binbuf.Span); //hash the buffer return ComputeHash(binbuf.Span[..byteCount], buffer, type); @@ -156,7 +156,7 @@ namespace VNLib.Hashing //Alloc buffer using UnsafeMemoryHandle binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf); + byteCount = CharEncoding.GetBytes(data, binbuf.Span); //hash the buffer return ComputeHash(binbuf.Span[..byteCount], type); } @@ -289,7 +289,7 @@ namespace VNLib.Hashing using UnsafeMemoryHandle binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf); + byteCount = CharEncoding.GetBytes(data, binbuf.Span); //hash the buffer return ComputeHmac(key, binbuf.Span[..byteCount], output, type); @@ -312,7 +312,7 @@ namespace VNLib.Hashing using UnsafeMemoryHandle binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf); + byteCount = CharEncoding.GetBytes(data, binbuf.Span); //hash the buffer return ComputeHmac(key, binbuf.Span[..byteCount], type); diff --git a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs index 418a9ec..8432d27 100644 --- a/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs +++ b/lib/Net.Messaging.FBM/src/Client/FBMRequest.cs @@ -244,7 +244,7 @@ namespace VNLib.Net.Messaging.FBM.Client using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(charSize + 128); - ERRNO count = Compile(buffer); + ERRNO count = Compile(buffer.Span); return buffer.AsSpan(0, count).ToString(); } diff --git a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs index b9fde20..509a8d0 100644 --- a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs +++ b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs @@ -103,7 +103,7 @@ namespace VNLib.Plugins.Essentials.Accounts //Alloc heap buffer using UnsafeMemoryHandle secretBuffer = MemoryUtil.UnsafeAlloc(_secret.BufferSize, true); - return VerifyInternal(passHash, password, secretBuffer); + return VerifyInternal(passHash, password, secretBuffer.Span); } } diff --git a/lib/Utils/src/Extensions/MemoryExtensions.cs b/lib/Utils/src/Extensions/MemoryExtensions.cs index 9553578..7ff9936 100644 --- a/lib/Utils/src/Extensions/MemoryExtensions.cs +++ b/lib/Utils/src/Extensions/MemoryExtensions.cs @@ -48,7 +48,39 @@ 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 => new(pool, size, zero); + public static UnsafeMemoryHandle UnsafeAlloc(this ArrayPool pool, int size, bool zero = false) where T : unmanaged + { + T[] array = pool.Rent(size); + + if (zero) + { + MemoryUtil.InitializeBlock(array, (uint)size); + } + + return new(pool, array, size); + } + + /// + /// Rents a new array and stores it as a resource within an to return the + /// array when work is completed + /// + /// + /// + /// 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 IMemoryHandle SafeAlloc(this ArrayPool pool, int size, bool zero = false) where T : struct + { + T[] array = pool.Rent(size); + + if (zero) + { + MemoryUtil.InitializeBlock(array, (uint)size); + } + + //Use the array pool buffer wrapper to return the array to the pool when the handle is disposed + return new ArrayPoolBuffer(pool, array, size); + } /// /// Retreives a buffer that is at least the reqested length, and clears the array from 0-size. @@ -60,14 +92,14 @@ namespace VNLib.Utils.Extensions /// True if contents should be zeroed /// The zeroed array [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] Rent(this ArrayPool pool, int size, bool zero) + public static T[] Rent(this ArrayPool pool, int size, bool zero) { //Rent the array T[] arr = pool.Rent(size); //If zero flag is set, zero only the used section if (zero) { - arr.AsSpan().Clear(); + Array.Clear(arr, 0, size); } return arr; } @@ -405,7 +437,7 @@ namespace VNLib.Utils.Extensions /// /// A reference to the structure [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void StructFreeRef(this IUnmangedHeap heap, ref T structRef) where T : unmanaged => MemoryUtil.StructFreeRef(heap, ref structRef); + public static void StructFreeRef(this IUnmangedHeap heap, ref T structRef) where T : unmanaged => MemoryUtil.StructFreeRef(heap, ref structRef); /// /// Allocates a block of unmanaged memory of the number of elements to store of an unmanged type @@ -519,7 +551,7 @@ namespace VNLib.Utils.Extensions /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe UnsafeMemoryHandle UnsafeAlloc(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged + public static UnsafeMemoryHandle UnsafeAlloc(this IUnmangedHeap heap, int elements, bool zero = false) where T : unmanaged { if (elements < 1) { @@ -528,7 +560,7 @@ namespace VNLib.Utils.Extensions } //Get element size - nuint elementSize = (nuint)sizeof(T); + nuint elementSize = (nuint)Unsafe.SizeOf(); //If zero flag is set then specify zeroing memory (safe case because of the above check) IntPtr block = heap.Alloc((nuint)elements, elementSize, zero); diff --git a/lib/Utils/src/Memory/ArrayPoolBuffer.cs b/lib/Utils/src/Memory/ArrayPoolBuffer.cs index 92a2022..2f00e66 100644 --- a/lib/Utils/src/Memory/ArrayPoolBuffer.cs +++ b/lib/Utils/src/Memory/ArrayPoolBuffer.cs @@ -3,9 +3,9 @@ * * Library: VNLib * Package: VNLib.Utils -* File: VnTempBuffer.cs +* File: ArrayPoolBuffer.cs * -* VnTempBuffer.cs is part of VNLib.Utils which is part of the larger +* ArrayPoolBuffer.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 @@ -35,7 +35,11 @@ namespace VNLib.Utils.Memory /// A disposable temporary buffer from shared ArrayPool /// /// Type of buffer to create - public sealed class ArrayPoolBuffer : VnDisposeable, IIndexable, IMemoryHandle, IMemoryOwner + public sealed class ArrayPoolBuffer : + VnDisposeable, + IIndexable, + IMemoryHandle, + IMemoryOwner { private readonly ArrayPool Pool; @@ -92,7 +96,28 @@ namespace VNLib.Utils.Memory Buffer = pool.Rent(minSize, zero); InitSize = minSize; } - + + /// + /// Initialzies a new from the specified rented array + /// that belongs to the supplied pool + /// + /// The pool the array was rented from + /// The rented array + /// The size of the buffer around the array. May be smaller or exact size of the array + /// + /// + public ArrayPoolBuffer(ArrayPool pool, T[] array, int size) + { + Pool = pool ?? throw new ArgumentNullException(nameof(pool)); + Buffer = array ?? throw new ArgumentNullException(nameof(array)); + + if (size < 0 || size > array.Length) + throw new ArgumentOutOfRangeException(nameof(size)); + + InitSize = size; + } + + /// /// Gets an offset wrapper around the current buffer /// diff --git a/lib/Utils/src/Memory/IResizeableMemoryHandle.cs b/lib/Utils/src/Memory/IResizeableMemoryHandle.cs index f788b48..1d83196 100644 --- a/lib/Utils/src/Memory/IResizeableMemoryHandle.cs +++ b/lib/Utils/src/Memory/IResizeableMemoryHandle.cs @@ -42,11 +42,15 @@ namespace VNLib.Utils.Memory /// 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. + /// Even if a handle is resizable resizing may not be supported for all types of handles.
+ /// Be careful not to resize handles that are pinned or have raw pointers/references floating. ///
/// 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/IUnmangedHeap.cs b/lib/Utils/src/Memory/IUnmangedHeap.cs index cb4b6ba..fdc0f9b 100644 --- a/lib/Utils/src/Memory/IUnmangedHeap.cs +++ b/lib/Utils/src/Memory/IUnmangedHeap.cs @@ -43,15 +43,19 @@ namespace VNLib.Utils.Memory /// The number of elements to allocate /// An optional parameter to zero the block of memory /// + /// IntPtr Alloc(nuint elements, nuint size, bool zero); /// - /// Resizes the allocated block of memory to the new size + /// Resizes the allocated block of memory to the new size. + /// May not be supported by all heaps. /// /// The block to resize /// The new number of elements /// The size (in bytes) of the type /// An optional parameter to zero the block of memory + /// + /// void Resize(ref IntPtr block, nuint elements, nuint size, bool zero); /// diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs index 4d2ff0c..f474abd 100644 --- a/lib/Utils/src/Memory/MemoryHandle.cs +++ b/lib/Utils/src/Memory/MemoryHandle.cs @@ -71,10 +71,7 @@ namespace VNLib.Utils.Memory get => (IntPtr)GetOffset(0); } - /// - /// Gets a span over the entire allocated block - /// - /// A over the internal data + /// /// /// public unsafe Span Span @@ -145,10 +142,7 @@ namespace VNLib.Utils.Memory Heap = null!; } - /// - /// Resizes the current handle on the heap - /// - /// Positive number of elemnts the current handle should referrence + /// /// /// /// @@ -263,12 +257,5 @@ namespace VNLib.Utils.Memory /// public override int GetHashCode() => base.GetHashCode(); - - /// - public static implicit operator Span(MemoryHandle handle) - { - //If the handle is invalid or closed return an empty span - return handle.IsClosed || handle.IsInvalid || handle._length == 0 ? Span.Empty : handle.Span; - } } } \ No newline at end of file diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index 52a9528..c149c85 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -243,12 +243,9 @@ namespace VNLib.Utils.Memory } uint byteSize = ByteCount((uint)block.Length); - ref T r0 = ref MemoryMarshal.GetReference(block); - ref byte byteRef = ref Unsafe.As(ref r0); - //Calls memset - Unsafe.InitBlock(ref byteRef, 0, byteSize); + ZeroByRef(ref r0, byteSize); } /// @@ -272,6 +269,19 @@ namespace VNLib.Utils.Memory Unsafe.InitBlock(handle.Pointer, 0, byteSize); } + [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] + private static void ZeroByRef(ref T src, uint elements) + { + Debug.Assert(Unsafe.IsNullRef(ref src) == false, "Null reference passed to ZeroByRef"); + + //Convert to bytes + uint byteSize = ByteCount(elements); + ref byte byteRef = ref Unsafe.As(ref src); + + //Call init block + Unsafe.InitBlock(ref byteRef, 0, byteSize); + } + /* * Initializing a non-readonly span/memory as of .NET 6.0 is a reference * reintpretation, essentially a pointer cast, so there is little/no cost @@ -294,6 +304,39 @@ namespace VNLib.Utils.Memory [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void InitializeBlock(Memory block) where T : struct => UnsafeZeroMemory(block); + /// + /// Initializes the entire array with zeros + /// + /// A structure type to initialize + /// The array to zero + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitializeBlock(T[] array) where T : struct => InitializeBlock(array, (uint)array.Length); + + /// + /// Initializes the array with zeros up to the specified count + /// + /// A structure type to initialize + /// The array to zero + /// The number of elements in the array to zero + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitializeBlock(T[] array, uint count) where T: struct + { + if(array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + //Check bounds + CheckBounds(array, 0, count); + + //Get array data reference + ref T arrRef = ref MemoryMarshal.GetArrayDataReference(array); + ZeroByRef(ref arrRef, count); + } /// /// Zeroes a block of memory of the given unmanaged type @@ -314,10 +357,7 @@ namespace VNLib.Utils.Memory return; } - //To bytereference - ref byte byteRef = ref Unsafe.As(ref block); - //Zero block - Unsafe.InitBlock(ref byteRef, 0, ByteCount((uint)itemCount)); + ZeroByRef(ref block, (uint)itemCount); } /// @@ -1287,5 +1327,31 @@ namespace VNLib.Utils.Memory //Multiply back to page sizes return pages * SystemPageSize; } + + /// + /// Rounds the requested number of elements up to the nearest page + /// + /// The unmanaged type + /// The number of elements of size T to round + /// The number of elements rounded to the nearest page in elements + public static nuint NearestPage(nuint elements) where T : unmanaged + { + nuint elSize = (nuint)sizeof(T); + //Round to nearest page (in bytes) + return NearestPage(elements * elSize) / elSize; + } + + /// + /// Rounds the requested number of elements up to the nearest page + /// + /// The unmanaged type + /// The number of elements of size T to round + /// The number of elements rounded to the nearest page in elements + public static nint NearestPage(nint elements) where T : unmanaged + { + nint elSize = sizeof(T); + //Round to nearest page (in bytes) + return NearestPage(elements * elSize) / elSize; + } } } \ No newline at end of file diff --git a/lib/Utils/src/Memory/MemoryUtilAlloc.cs b/lib/Utils/src/Memory/MemoryUtilAlloc.cs index bc0f35e..0f25682 100644 --- a/lib/Utils/src/Memory/MemoryUtilAlloc.cs +++ b/lib/Utils/src/Memory/MemoryUtilAlloc.cs @@ -52,6 +52,11 @@ namespace VNLib.Utils.Memory throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); } + if (elements == 0) + { + return default; + } + /* * We may allocate from the share heap only if the heap is not using locks * or if the element size could cause performance issues because its too large @@ -69,7 +74,8 @@ namespace VNLib.Utils.Memory } else { - return new(ArrayPool.Shared, elements, zero); + //Rent the array from the pool + return ArrayPool.Shared.UnsafeAlloc(elements, zero); } } @@ -84,7 +90,6 @@ namespace VNLib.Utils.Memory /// A handle to the block of memory /// /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UnsafeMemoryHandle UnsafeAllocNearestPage(int elements, bool zero = false) where T : unmanaged { if (elements < 0) @@ -93,11 +98,7 @@ namespace VNLib.Utils.Memory } //Round to nearest page (in bytes) - nint np = NearestPage(ByteCount(elements)); - - //Resize to element size - np /= sizeof(T); - + nint np = NearestPage(elements); return UnsafeAlloc((int)np, zero); } @@ -155,11 +156,7 @@ namespace VNLib.Utils.Memory } //Round to nearest page (in bytes) - nint np = NearestPage(ByteCount(elements)); - - //Resize to element size - np /= sizeof(T); - + nint np = NearestPage(elements); return SafeAlloc((int)np, zero); } @@ -247,7 +244,6 @@ namespace VNLib.Utils.Memory #region ByteOptimimzations - /// /// Allocates a block of unmanaged, or pooled manaaged memory depending on /// compilation flags and runtime unamanged allocators. @@ -264,6 +260,11 @@ namespace VNLib.Utils.Memory throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); } + if(elements == 0) + { + return default; + } + /* * We may allocate from the share heap only if the heap is not using locks * or if the element size could cause performance issues because its too large @@ -281,7 +282,7 @@ namespace VNLib.Utils.Memory } else { - return new(ArrayPool.Shared, elements, zero); + return ArrayPool.Shared.UnsafeAlloc(elements, zero); } } @@ -305,7 +306,6 @@ namespace VNLib.Utils.Memory //Round to nearest page (in bytes) nint np = NearestPage(elements); - return UnsafeAlloc((int)np, zero); } @@ -362,7 +362,6 @@ namespace VNLib.Utils.Memory //Round to nearest page (in bytes) nint np = NearestPage(elements); - return SafeAlloc((int)np, zero); } diff --git a/lib/Utils/src/Memory/UnmanagedHeapBase.cs b/lib/Utils/src/Memory/UnmanagedHeapBase.cs index 0310582..daf360c 100644 --- a/lib/Utils/src/Memory/UnmanagedHeapBase.cs +++ b/lib/Utils/src/Memory/UnmanagedHeapBase.cs @@ -152,10 +152,14 @@ namespace VNLib.Utils.Memory /// /// - /// /// public void Resize(ref LPVOID block, nuint elements, nuint size, bool zero) { + if ((_flags & HeapCreation.SupportsRealloc) == 0) + { + throw new NotSupportedException("The underlying heap does not support block reallocation"); + } + //Check for overflow for size _ = checked(elements * size); diff --git a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs index 6a1fcc8..f79a094 100644 --- a/lib/Utils/src/Memory/UnsafeMemoryHandle.cs +++ b/lib/Utils/src/Memory/UnsafeMemoryHandle.cs @@ -24,6 +24,7 @@ using System; using System.Buffers; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Diagnostics.CodeAnalysis; @@ -32,6 +33,7 @@ using VNLib.Utils.Extensions; namespace VNLib.Utils.Memory { + /// /// Represents an unsafe handle to managed/unmanaged memory that should be used cautiously. /// A referrence counter is not maintained. @@ -59,55 +61,51 @@ namespace VNLib.Utils.Memory public readonly Span Span { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _handleType == HandleType.Pool ? _poolArr.AsSpan(0, _length) : MemoryUtil.GetSpan(_memoryPtr, _length); + get + { + return _handleType switch + { + HandleType.None => Span.Empty, + HandleType.Pool => _poolArr!.AsSpan(0, _length), + HandleType.PrivateHeap => MemoryUtil.GetSpan(_memoryPtr, _length), + _ => throw new InvalidOperationException("Invalid handle type"), + }; + } } + /// /// Gets the integer number of elements of the block of memory pointed to by this handle /// public readonly int IntLength => _length; + /// public readonly nuint Length => (nuint)_length; - /// - /// Creates an empty - /// - public UnsafeMemoryHandle() - { - _pool = null; - _heap = null; - _poolArr = null; - _memoryPtr = IntPtr.Zero; - _handleType = HandleType.None; - _length = 0; - } - /// /// Inializes a new using the specified /// /// /// The number of elements to store - /// Zero initial contents? + /// The array reference to store/param> /// The explicit pool to alloc buffers from /// /// /// - public UnsafeMemoryHandle(ArrayPool pool, int elements, bool zero) + internal UnsafeMemoryHandle(ArrayPool pool, T[] array, int elements) { if (elements < 0) { throw new ArgumentOutOfRangeException(nameof(elements)); } - //Pool is required + //Pool and array is required _pool = pool ?? throw new ArgumentNullException(nameof(pool)); - //Rent the array from the pool and hold referrence to it - _poolArr = pool.Rent(elements, zero); - //Cant store ref to array becase GC can move it - _memoryPtr = IntPtr.Zero; + _poolArr = array ?? throw new ArgumentNullException(nameof(array)); //Set pool handle type _handleType = HandleType.Pool; //No heap being loaded _heap = null; _length = elements; + _memoryPtr = IntPtr.Zero; } /// @@ -129,8 +127,13 @@ namespace VNLib.Utils.Memory /// /// Releases memory back to the pool or heap from which is was allocated. + /// + /// After this method is called, this handle points to invalid memory + /// + /// + /// Warning: Double Free -> Do not call more than once. Using statment is encouraged + /// /// - /// After this method is called, this handle points to invalid memory public readonly void Dispose() { switch (_handleType) @@ -145,42 +148,37 @@ namespace VNLib.Utils.Memory { IntPtr unalloc = _memoryPtr; //Free the unmanaged handle - _heap!.Free(ref unalloc); + bool unsafeFreed = _heap!.Free(ref unalloc); + Debug.Assert(unsafeFreed, "A previously allocated unsafe memhandle failed to free"); } break; } - } - - /// - public readonly override int GetHashCode() => _handleType == HandleType.Pool ? _poolArr!.GetHashCode() : _memoryPtr.GetHashCode(); + } + /// public readonly MemoryHandle Pin(int elementIndex) { - //guard empty handle - if (_handleType == HandleType.None) - { - throw new InvalidOperationException("The handle is empty, and cannot be pinned"); - } - //Guard size if (elementIndex < 0 || elementIndex >= _length) { throw new ArgumentOutOfRangeException(nameof(elementIndex)); - } - - if (_handleType == HandleType.Pool) - { - return MemoryUtil.PinArrayAndGetHandle(_poolArr!, elementIndex); } - else + + switch (_handleType) { - //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 MemoryUtil.GetMemoryHandleFromPointer(offset); + case HandleType.Pool: + return MemoryUtil.PinArrayAndGetHandle(_poolArr!, elementIndex); + case HandleType.PrivateHeap: + //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 MemoryUtil.GetMemoryHandleFromPointer(offset); + default: + throw new InvalidOperationException("The handle is empty, and cannot be pinned"); } } + /// public readonly void Unpin() { @@ -188,6 +186,7 @@ namespace VNLib.Utils.Memory } /// + /// public readonly ref T GetReference() { switch (_handleType) @@ -201,17 +200,37 @@ namespace VNLib.Utils.Memory } } + /// + public readonly override int GetHashCode() + { + //Get hashcode for the proper memory type + return _handleType switch + { + HandleType.Pool => _poolArr!.GetHashCode(), + HandleType.PrivateHeap => _memoryPtr.GetHashCode(), + _ => base.GetHashCode(), + }; + } + /// /// Determines if the other handle represents the same memory block as the /// current handle. /// /// The other handle to test /// True if the other handle points to the same block of memory as the current handle - public readonly bool Equals(UnsafeMemoryHandle other) + public readonly bool Equals(in UnsafeMemoryHandle other) { return _handleType == other._handleType && Length == other.Length && GetHashCode() == other.GetHashCode(); } + /// + /// Determines if the other handle represents the same memory block as the + /// current handle. + /// + /// The other handle to test + /// True if the other handle points to the same block of memory as the current handle + public readonly bool Equals(UnsafeMemoryHandle other) => Equals(in other); + /// /// Override for object equality operator, will cause boxing /// for structures @@ -222,13 +241,7 @@ namespace VNLib.Utils.Memory /// and uses the structure equality operator /// false otherwise. /// - public readonly override bool Equals([NotNullWhen(true)] object? obj) => obj is UnsafeMemoryHandle other && Equals(other); - - /// - /// Casts the handle to it's representation - /// - /// the handle to cast - public static implicit operator Span(in UnsafeMemoryHandle handle) => handle.Span; + public readonly override bool Equals([NotNullWhen(true)] object? obj) => obj is UnsafeMemoryHandle other && Equals(in other); /// /// Equality overload @@ -237,6 +250,7 @@ namespace VNLib.Utils.Memory /// /// True if handles are equal, flase otherwise public static bool operator ==(in UnsafeMemoryHandle left, in UnsafeMemoryHandle right) => left.Equals(right); + /// /// Equality overload /// diff --git a/lib/Utils/src/Memory/VnString.cs b/lib/Utils/src/Memory/VnString.cs index c937ccc..d5b34ac 100644 --- a/lib/Utils/src/Memory/VnString.cs +++ b/lib/Utils/src/Memory/VnString.cs @@ -42,7 +42,13 @@ namespace VNLib.Utils.Memory /// [ComVisible(false)] [ImmutableObject(true)] - public sealed class VnString : VnDisposeable, IEquatable, IEquatable, IEquatable, IComparable, IComparable + public sealed class VnString : + VnDisposeable, + IEquatable, + IEquatable, + IEquatable, + IComparable, + IComparable { private readonly IMemoryHandle? Handle; @@ -58,10 +64,7 @@ namespace VNLib.Utils.Memory /// public bool IsEmpty => Length == 0; - private VnString(SubSequence sequence) - { - _stringSequence = sequence; - } + private VnString(SubSequence sequence) => _stringSequence = sequence; private VnString(IMemoryHandle handle, nuint start, int length) { @@ -97,7 +100,77 @@ namespace VNLib.Utils.Memory //Get subsequence over the whole copy of data _stringSequence = Handle.GetSubSequence(0, data.Length); } - + + /// + /// Creates a new from the binary data, using the specified encoding + /// and allocating the internal buffer from the desired heap, or + /// heap instance if null. If the is empty, an empty + /// is returned. + /// + /// The data to decode + /// + /// + /// The decoded string from the binary data, or an empty string if no data was provided + /// + public static VnString FromBinary(ReadOnlySpan data, Encoding encoding, IUnmangedHeap? heap = null) + { + _ = encoding ?? throw new ArgumentNullException(nameof(encoding)); + + if (data.IsEmpty) + { + return new VnString(); + } + + //Fall back to shared heap + heap ??= MemoryUtil.Shared; + + //Get the number of characters + int numChars = encoding.GetCharCount(data); + + //New handle for decoded data + MemoryHandle charBuffer = heap.Alloc(numChars); + try + { + //Write characters to character buffer + _ = encoding.GetChars(data, charBuffer.Span); + //Consume the new handle + return ConsumeHandle(charBuffer, 0, numChars); + } + catch + { + //If an error occured, dispose the buffer + charBuffer.Dispose(); + throw; + } + } + + /// + /// Creates a new Vnstring from the buffer provided. This function "consumes" + /// a handle, meaning it now takes ownsership of the the memory it points to. + /// + /// The to consume + /// The offset from the begining of the buffer marking the begining of the string + /// The number of characters this string points to + /// The new + /// + public static VnString ConsumeHandle(IMemoryHandle handle, nuint start, int length) + { + if (handle is null) + { + throw new ArgumentNullException(nameof(handle)); + } + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + //Check handle bounts + MemoryUtil.CheckBounds(handle, start, (nuint)length); + + return new VnString(handle, start, length); + } + /// /// Allocates a temporary buffer to read data from the stream until the end of the stream is reached. /// Decodes data from the user-specified encoding @@ -111,7 +184,7 @@ namespace VNLib.Utils.Memory /// /// /// - public static VnString FromStream(Stream stream, Encoding encoding, IUnmangedHeap heap, uint bufferSize) + public static VnString FromStream(Stream stream, Encoding encoding, IUnmangedHeap heap, int bufferSize) { _ = stream ?? throw new ArgumentNullException(nameof(stream)); _ = encoding ?? throw new ArgumentNullException(nameof(encoding)); @@ -125,36 +198,27 @@ namespace VNLib.Utils.Memory //See if the stream is a vn memory stream if (stream is VnMemoryStream vnms) { - //Get the number of characters - int numChars = encoding.GetCharCount(vnms.AsSpan()); - //New handle - MemoryHandle charBuffer = heap.Alloc(numChars); - try - { - //Write characters to character buffer - _ = encoding.GetChars(vnms.AsSpan(), charBuffer); - //Consume the new handle - return ConsumeHandle(charBuffer, 0, numChars); - } - catch - { - //If an error occured, dispose the buffer - charBuffer.Dispose(); - throw; - } + return FromBinary(vnms.AsSpan(), encoding, heap); + } + //Try to get the internal buffer from am memory span + else if (stream is MemoryStream ms && ms.TryGetBuffer(out ArraySegment arrSeg)) + { + return FromBinary(arrSeg.AsSpan(), encoding, heap); } //Need to read from the stream old school with buffers else { + //Allocate a binary buffer + using UnsafeMemoryHandle binBuffer = heap.UnsafeAlloc(bufferSize); + //Create a new char bufer that will expand dyanmically MemoryHandle charBuffer = heap.Alloc(bufferSize); - //Allocate a binary buffer - MemoryHandle binBuffer = heap.Alloc(bufferSize); + try { int length = 0; //span ref to bin buffer - Span buffer = binBuffer; + Span buffer = binBuffer.Span; //Run in checked context for overflows checked { @@ -193,40 +257,8 @@ namespace VNLib.Utils.Memory //We still want the exception to be propagated! throw; } - finally - { - //Dispose the binary buffer - binBuffer.Dispose(); - } - } - } - - /// - /// Creates a new Vnstring from the buffer provided. This function "consumes" - /// a handle, meaning it now takes ownsership of the the memory it points to. - /// - /// The to consume - /// The offset from the begining of the buffer marking the begining of the string - /// The number of characters this string points to - /// The new - /// - public static VnString ConsumeHandle(MemoryHandle handle, nuint start, int length) - { - if (handle is null) - { - throw new ArgumentNullException(nameof(handle)); - } - - if (length < 0) - { - throw new ArgumentOutOfRangeException(nameof(length)); } - - //Check handle bounts - MemoryUtil.CheckBounds(handle, start, (nuint)length); - - return new VnString(handle, start, length); - } + } /// /// Asynchronously reads data from the specified stream and uses the specified encoding @@ -251,36 +283,25 @@ namespace VNLib.Utils.Memory throw new IOException("The input stream is not readable"); } - //See if the stream is a vn memory stream + /* + * If the stream is some type of memory stream, we can just use + * the underlying buffer if possible + */ if (stream is VnMemoryStream vnms) { - //Get the number of characters - int numChars = encoding.GetCharCount(vnms.AsSpan()); - - //New handle - MemoryHandle charBuffer = heap.Alloc(numChars); - - try - { - //Write characters to character buffer - _ = encoding.GetChars(vnms.AsSpan(), charBuffer); - //Consume the new handle - return ConsumeHandle(charBuffer, 0, numChars); - } - catch - { - //If an error occured, dispose the buffer - charBuffer.Dispose(); - throw; - } + return FromBinary(vnms.AsSpan(), encoding, heap); + } + else if(stream is MemoryStream ms && ms.TryGetBuffer(out ArraySegment arrSeg)) + { + return FromBinary(arrSeg.AsSpan(), encoding, heap); } else { - //Create a new char bufer starting with the buffer size - MemoryHandle charBuffer = heap.Alloc(bufferSize); - //Rent a temp binary buffer - IMemoryOwner binBuffer = heap.DirectAlloc(bufferSize); + using MemoryManager binBuffer = heap.DirectAlloc(bufferSize); + + //Create a new char buffer starting with the buffer size + MemoryHandle charBuffer = heap.Alloc(bufferSize); try { @@ -302,14 +323,14 @@ namespace VNLib.Utils.Memory //Guard for overflow if (((ulong)(numChars + length)) >= int.MaxValue) { - throw new OverflowException(); + throw new OverflowException("The provided stream is larger than 2gb and is not supported"); } //Re-alloc buffer charBuffer.ResizeIfSmaller(length + numChars); //Decode and update position - _ = encoding.GetChars(binBuffer.Memory.Span[..read], charBuffer.Span.Slice(length, numChars)); + _ = encoding.GetChars(binBuffer.GetSpan()[..read], charBuffer.Span.Slice(length, numChars)); //Update char count length += numChars; @@ -323,11 +344,6 @@ namespace VNLib.Utils.Memory //We still want the exception to be propagated! throw; } - finally - { - //Dispose the binary buffer - binBuffer.Dispose(); - } } } diff --git a/lib/Utils/src/VnEncoding.cs b/lib/Utils/src/VnEncoding.cs index e24f7df..9eb60df 100644 --- a/lib/Utils/src/VnEncoding.cs +++ b/lib/Utils/src/VnEncoding.cs @@ -69,7 +69,7 @@ namespace VNLib.Utils //resize the handle to fit the data handle = heap.Alloc(byteCount); //encode - int size = encoding.GetBytes(data, handle); + int size = encoding.GetBytes(data, handle.Span); //Consume the handle into a new vnmemstream and return it return VnMemoryStream.ConsumeHandle(handle, size, true); } @@ -459,27 +459,28 @@ namespace VNLib.Utils string value; //Calculate the base32 entropy to alloc an appropriate buffer (minium buffer of 2 chars) int entropy = Base32CalcMaxBufferSize(binBuffer.Length); + //Alloc buffer for enough size (2*long bytes) is not an issue - using (UnsafeMemoryHandle charBuffer = MemoryUtil.UnsafeAlloc(entropy)) + using UnsafeMemoryHandle charBuffer = MemoryUtil.UnsafeAlloc(entropy); + + //Encode + ERRNO encoded = TryToBase32Chars(binBuffer, charBuffer.Span); + if (!encoded) { - //Encode - ERRNO encoded = TryToBase32Chars(binBuffer, charBuffer.Span); - if (!encoded) - { - throw new InternalBufferTooSmallException("Base32 char buffer was too small"); - } - //Convert with or w/o padding - if (withPadding) - { - value = charBuffer.Span[0..(int)encoded].ToString(); - } - else - { - value = charBuffer.Span[0..(int)encoded].Trim('=').ToString(); - } + throw new InternalBufferTooSmallException("Base32 char buffer was too small"); + } + //Convert with or w/o padding + if (withPadding) + { + value = charBuffer.Span[0..(int)encoded].ToString(); } + else + { + value = charBuffer.Span[0..(int)encoded].Trim('=').ToString(); + } + return value; - } + } /// /// Converts the base32 character buffer to its structure representation @@ -506,7 +507,6 @@ namespace VNLib.Utils /// /// The character array to decode /// The byte[] of the decoded binary data, or null if the supplied character array was empty - /// public static byte[]? FromBase32String(ReadOnlySpan base32) { if (base32.IsEmpty) @@ -515,10 +515,12 @@ namespace VNLib.Utils } //Buffer size of the base32 string will always be enough buffer space using UnsafeMemoryHandle tempBuffer = MemoryUtil.UnsafeAlloc(base32.Length); + //Try to decode the data ERRNO decoded = TryFromBase32Chars(base32, tempBuffer.Span); - - return decoded ? tempBuffer.Span[0..(int)decoded].ToArray() : throw new InternalBufferTooSmallException("Binbuffer was too small"); + Debug.Assert(decoded > 0, "The supplied base32 buffer was too small to decode data into, but should not have been"); + + return tempBuffer.Span[0..(int)decoded].ToArray(); } /// @@ -942,16 +944,28 @@ namespace VNLib.Utils { return ERRNO.E_FAIL; } + //Set the encoding to utf8 encoding ??= Encoding.UTF8; //get the number of bytes to alloc a buffer int decodedSize = encoding.GetByteCount(chars); - //alloc buffer - using UnsafeMemoryHandle decodeHandle = MemoryUtil.UnsafeAlloc(decodedSize); - //Get the utf8 binary data - int count = encoding.GetBytes(chars, decodeHandle); - return Base64UrlDecode(decodeHandle.Span[..count], output); + if(decodedSize > MAX_STACKALLOC) + { + //Alloc heap buffer + using UnsafeMemoryHandle decodeHandle = MemoryUtil.UnsafeAlloc(decodedSize); + //Get the utf8 binary data + int count = encoding.GetBytes(chars, decodeHandle.Span); + return Base64UrlDecode(decodeHandle.Span[..count], output); + } + else + { + //Alloc stack buffer + Span decodeBuffer = stackalloc byte[decodedSize]; + //Get the utf8 binary data + int count = encoding.GetBytes(chars, decodeBuffer); + return Base64UrlDecode(decodeBuffer[..count], output); + } } @@ -1036,16 +1050,14 @@ namespace VNLib.Utils if(maxBufSize > MAX_STACKALLOC) { - //alloc buffer + //Alloc heap buffer using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAllocNearestPage(maxBufSize); - return ConvertToBase64UrlStringInternal(rawData, buffer.Span, includePadding); } else { //Stack alloc buffer Span buffer = stackalloc byte[maxBufSize]; - return ConvertToBase64UrlStringInternal(rawData, buffer, includePadding); } } @@ -1116,24 +1128,37 @@ namespace VNLib.Utils //We need to alloc an intermediate buffer, get the base64 max size int maxSize = Base64.GetMaxEncodedToUtf8Length(input.Length); - //Alloc buffer - using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(maxSize); - - //Encode to url safe binary - ERRNO count = Base64UrlEncode(input, buffer.Span, includePadding); - - if (count <= 0) + if (maxSize > MAX_STACKALLOC) { - return count; + //Alloc heap buffer + using UnsafeMemoryHandle buffer = MemoryUtil.UnsafeAlloc(maxSize); + return Base64UrlEncodeCore(input, buffer.Span, output, encoding, includePadding); } + else + { + //Alloc stack buffer + Span bufer = stackalloc byte[maxSize]; + return Base64UrlEncodeCore(input, bufer, output, encoding, includePadding); + } + + static ERRNO Base64UrlEncodeCore(ReadOnlySpan input, Span buffer, Span output, Encoding encoding, bool includePadding) + { + //Encode to url safe binary + ERRNO count = Base64UrlEncode(input, buffer, includePadding); - //Get char count to return to caller - int charCount = encoding.GetCharCount(buffer.Span[..(int)count]); + if (count <= 0) + { + return count; + } - //Encode to characters - encoding.GetChars(buffer.AsSpan(0, count), output); + //Get char count to return to caller + int charCount = encoding.GetCharCount(buffer[..(int)count]); - return charCount; + //Encode to characters + encoding.GetChars(buffer[0..(int)count], output); + + return charCount; + } } #endregion diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index e63dc03..0774f47 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -165,6 +165,10 @@ namespace VNLib.Utils.Memory.Tests Assert.ThrowsException(() => _ = handle.Pin(1024)); } + //Use the byte only overload + using (UnsafeMemoryHandle handle = MemoryUtil.UnsafeAlloc(1024)) + { } + //test against negative number Assert.ThrowsException(() => MemoryUtil.UnsafeAlloc(-1)); @@ -217,7 +221,7 @@ namespace VNLib.Utils.Memory.Tests Assert.IsTrue(0 == empty.IntLength); //Test pinning while empty - Assert.ThrowsException(() => _ = empty.Pin(0)); + Assert.ThrowsException(() => _ = empty.Pin(0)); } //Negative value -- cgit