diff options
Diffstat (limited to 'lib/Utils')
-rw-r--r-- | lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs | 7 | ||||
-rw-r--r-- | lib/Utils/src/Memory/HeapCreation.cs | 54 | ||||
-rw-r--r-- | lib/Utils/src/Memory/IUnmangedHeap.cs | 7 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryHandle.cs | 7 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryUtil.cs | 205 | ||||
-rw-r--r-- | lib/Utils/src/Memory/MemoryUtilAlloc.cs | 291 | ||||
-rw-r--r-- | lib/Utils/src/Memory/NativeHeap.cs | 212 | ||||
-rw-r--r-- | lib/Utils/src/Memory/ProcessHeap.cs | 16 | ||||
-rw-r--r-- | lib/Utils/src/Memory/RpMallocPrivateHeap.cs | 284 | ||||
-rw-r--r-- | lib/Utils/src/Memory/UnmanagedHeapBase.cs | 89 | ||||
-rw-r--r-- | lib/Utils/src/Memory/VnTable.cs | 11 | ||||
-rw-r--r-- | lib/Utils/src/Memory/VnTempBuffer.cs | 37 | ||||
-rw-r--r-- | lib/Utils/src/Memory/Win32PrivateHeap.cs | 52 | ||||
-rw-r--r-- | lib/Utils/src/VnEncoding.cs | 6 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/MemoryHandleTest.cs | 8 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/MemoryUtilTests.cs | 50 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/NativeHeapTests.cs | 32 | ||||
-rw-r--r-- | lib/Utils/tests/Memory/VnTableTests.cs | 14 |
18 files changed, 861 insertions, 521 deletions
diff --git a/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs b/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs index 2069d08..41b08c1 100644 --- a/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs +++ b/lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -23,10 +23,8 @@ */ using System; -using System.Collections; using System.Collections.Concurrent; - namespace VNLib.Utils.Memory.Diagnostics { /// <summary> @@ -39,6 +37,9 @@ namespace VNLib.Utils.Memory.Diagnostics private readonly object _statsLock; private readonly ConcurrentDictionary<IntPtr, ulong> _table; + ///<inheritdoc/> + public HeapCreation CreationFlags => _heap.CreationFlags; + /// <summary> /// Gets the underlying heap /// </summary> diff --git a/lib/Utils/src/Memory/HeapCreation.cs b/lib/Utils/src/Memory/HeapCreation.cs new file mode 100644 index 0000000..2d30c29 --- /dev/null +++ b/lib/Utils/src/Memory/HeapCreation.cs @@ -0,0 +1,54 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: HeapCreation.cs +* +* HeapCreation.cs is part of VNLib.Utils which is part of the larger +* VNLib collection of libraries and utilities. +* +* VNLib.Utils is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Utils is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Utils. If not, see http://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Utils.Memory +{ + /// <summary> + /// Internal heap creation flags passed to the heap creation method + /// on initialization + /// </summary> + [Flags] + public enum HeapCreation : int + { + /// <summary> + /// Default/no flags + /// </summary> + None, + /// <summary> + /// Specifies that all allocations be zeroed before returning to caller + /// </summary> + GlobalZero = 0x01, + /// <summary> + /// Specifies that the heap should use internal locking, aka its not thread safe + /// and needs to be made thread safe + /// </summary> + UseSynchronization = 0x02, + /// <summary> + /// Specifies that the requested heap will be a shared heap for the process/library + /// </summary> + IsSharedHeap = 0x04 + } +}
\ No newline at end of file diff --git a/lib/Utils/src/Memory/IUnmangedHeap.cs b/lib/Utils/src/Memory/IUnmangedHeap.cs index 94f34c8..cb4b6ba 100644 --- a/lib/Utils/src/Memory/IUnmangedHeap.cs +++ b/lib/Utils/src/Memory/IUnmangedHeap.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -32,6 +32,11 @@ namespace VNLib.Utils.Memory public interface IUnmangedHeap : IDisposable { /// <summary> + /// The creation flags the heap was initialized with + /// </summary> + HeapCreation CreationFlags { get; } + + /// <summary> /// Allocates a block of memory from the heap and returns a pointer to the new memory block /// </summary> /// <param name="size">The size (in bytes) of the element</param> diff --git a/lib/Utils/src/Memory/MemoryHandle.cs b/lib/Utils/src/Memory/MemoryHandle.cs index 7a7cb6a..067f6c0 100644 --- a/lib/Utils/src/Memory/MemoryHandle.cs +++ b/lib/Utils/src/Memory/MemoryHandle.cs @@ -179,7 +179,9 @@ namespace VNLib.Utils.Memory { throw new ArgumentOutOfRangeException(nameof(elements), "Element offset cannot be larger than allocated size"); } + this.ThrowIfClosed(); + //Get ptr and offset it T* bs = ((T*)handle) + elements; return bs; @@ -194,6 +196,11 @@ namespace VNLib.Utils.Memory ///</remarks> public unsafe MemoryHandle Pin(int elementIndex) { + if(elementIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(elementIndex)); + } + //Get ptr and guard checks before adding the referrence T* ptr = GetOffset((nuint)elementIndex); diff --git a/lib/Utils/src/Memory/MemoryUtil.cs b/lib/Utils/src/Memory/MemoryUtil.cs index 5fa381a..7f96c4a 100644 --- a/lib/Utils/src/Memory/MemoryUtil.cs +++ b/lib/Utils/src/Memory/MemoryUtil.cs @@ -35,23 +35,26 @@ using VNLib.Utils.Memory.Diagnostics; namespace VNLib.Utils.Memory { + /// <summary> /// Provides optimized cross-platform maanged/umanaged safe/unsafe memory operations /// </summary> [SecurityCritical] [ComVisible(false)] - public unsafe static class MemoryUtil + public static unsafe partial class MemoryUtil { /// <summary> /// The environment variable name used to specify the shared heap type /// to create /// </summary> - public const string SHARED_HEAP_TYPE_ENV= "VNLIB_SHARED_HEAP_TYPE"; + public const string SHARED_HEAP_FILE_PATH = "VNLIB_SHARED_HEAP_FILE_PATH"; + /// <summary> /// When creating a heap that accepts an initial size, this value is passed /// to it, otherwise no initial heap size is set. /// </summary> public const string SHARED_HEAP_INTIAL_SIZE_ENV = "VNLIB_SHARED_HEAP_SIZE"; + /// <summary> /// The environment variable name used to enable share heap diagnostics /// </summary> @@ -70,7 +73,7 @@ namespace VNLib.Utils.Memory /// that will use the array pool before falling back to the <see cref="Shared"/>. /// heap. /// </summary> - public const int MAX_UNSAFE_POOL_SIZE = 500 * 1024; + public const int MAX_UNSAFE_POOL_SIZE = 80 * 1024; /// <summary> /// Provides a shared heap instance for the process to allocate memory from. @@ -140,38 +143,59 @@ namespace VNLib.Utils.Memory bool IsWindows = OperatingSystem.IsWindows(); //Get environment varable - string? heapType = Environment.GetEnvironmentVariable(SHARED_HEAP_TYPE_ENV); - - //Get inital size - string? sharedSize = Environment.GetEnvironmentVariable(SHARED_HEAP_INTIAL_SIZE_ENV); - - //Try to parse the shared size from the env - if (!nuint.TryParse(sharedSize, out nuint defaultSize)) + string? heapDllPath = Environment.GetEnvironmentVariable(SHARED_HEAP_FILE_PATH); + + //Default flags + HeapCreation cFlags = HeapCreation.UseSynchronization; + + /* + * We need to set the shared flag and the synchronziation flag. + * + * The heap impl may reset the synchronziation flag if it does not + * need serialziation + */ + cFlags |= isShared ? HeapCreation.IsSharedHeap : HeapCreation.None; + + IUnmangedHeap heap; + + //Check for heap api dll + if (!string.IsNullOrWhiteSpace(heapDllPath)) { - defaultSize = SHARED_HEAP_INIT_SIZE; + //Attempt to load the heap + heap = NativeHeap.LoadHeap(heapDllPath, DllImportSearchPath.SafeDirectories, cFlags, 0); } + //No user heap was specified, use fallback + else if (IsWindows) + { + //We can use win32 heaps + + //Get inital size + string? sharedSize = Environment.GetEnvironmentVariable(SHARED_HEAP_INTIAL_SIZE_ENV); - //convert to upper - heapType = heapType?.ToUpperInvariant(); - - //Create the heap - IUnmangedHeap heap = heapType switch + //Try to parse the shared size from the env + if (!nuint.TryParse(sharedSize, out nuint defaultSize)) + { + defaultSize = SHARED_HEAP_INIT_SIZE; + } + + //Create win32 private heap + heap = Win32PrivateHeap.Create(defaultSize, cFlags); + } + else { - "WIN32" => IsWindows ? Win32PrivateHeap.Create(defaultSize) : throw new PlatformNotSupportedException("Win32 private heaps are not supported on non-windows platforms"), - //If the shared heap is being allocated, then return a lock free global heap - "RPMALLOC" => isShared ? RpMallocPrivateHeap.GlobalHeap : new RpMallocPrivateHeap(false), - //Get the process heap if the heap is shared, otherwise create a new win32 private heap - _ => IsWindows && !isShared ? Win32PrivateHeap.Create(defaultSize) : new ProcessHeap(), - }; - - //If diagnosticts is enabled, wrap the heap in a stats heap + //Finally fallback to .NET native mem impl + heap = new ProcessHeap(); + } + + //Enable heap statistics return enableStats ? new TrackedHeapWrapper(heap) : heap; } /// <summary> - /// Gets a value that indicates if the Rpmalloc native library is loaded + /// Gets a value that indicates if the use defined a custom heap + /// implementation /// </summary> - public static bool IsRpMallocLoaded { get; } = Environment.GetEnvironmentVariable(SHARED_HEAP_TYPE_ENV)?.ToUpperInvariant() == "RPMALLOC"; + public static bool IsUserDefinedHeap { get; } = !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(SHARED_HEAP_FILE_PATH)); #region Zero @@ -579,7 +603,7 @@ namespace VNLib.Utils.Memory { if (((nuint)block.LongLength - offset) <= count) { - throw new ArgumentException("The offset or count is outside of the range of the block of memory"); + throw new ArgumentOutOfRangeException("The offset or count is outside of the range of the block of memory"); } } @@ -596,8 +620,13 @@ namespace VNLib.Utils.Memory /// <exception cref="IndexOutOfRangeException"></exception> public static MemoryHandle PinArrayAndGetHandle<T>(T[] array, int elementOffset) { - //Quick verify index exists - _ = array[elementOffset]; + if(elementOffset < 0) + { + throw new ArgumentOutOfRangeException(nameof(elementOffset)); + } + + //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); @@ -609,8 +638,6 @@ namespace VNLib.Utils.Memory return new(indexOffet, arrHandle); } - #region alloc - /// <summary> /// Gets a <see cref="Span{T}"/> from the supplied address /// </summary> @@ -652,121 +679,5 @@ namespace VNLib.Utils.Memory //Multiply back to page sizes return pages * Environment.SystemPageSize; } - - /// <summary> - /// Allocates a block of unmanaged, or pooled manaaged memory depending on - /// compilation flags and runtime unamanged allocators. - /// </summary> - /// <typeparam name="T">The unamanged type to allocate</typeparam> - /// <param name="elements">The number of elements of the type within the block</param> - /// <param name="zero">Flag to zero elements during allocation before the method returns</param> - /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="OutOfMemoryException"></exception> - public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(int elements, bool zero = false) where T : unmanaged - { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } - - if(elements > MAX_UNSAFE_POOL_SIZE || IsRpMallocLoaded) - { - // Alloc from heap - IntPtr block = Shared.Alloc((uint)elements, (uint)sizeof(T), zero); - //Init new handle - return new(Shared, block, elements); - } - else - { - return new(ArrayPool<T>.Shared, elements, zero); - } - } - - /// <summary> - /// Allocates a block of unmanaged, or pooled manaaged memory depending on - /// compilation flags and runtime unamanged allocators, rounded up to the - /// neareset memory page. - /// </summary> - /// <typeparam name="T">The unamanged type to allocate</typeparam> - /// <param name="elements">The number of elements of the type within the block</param> - /// <param name="zero">Flag to zero elements during allocation before the method returns</param> - /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="OutOfMemoryException"></exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static UnsafeMemoryHandle<T> UnsafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged - { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } - //Round to nearest page (in bytes) - nint np = NearestPage(elements * sizeof(T)); - - //Resize to element size - np /= sizeof(T); - - return UnsafeAlloc<T>((int)np, zero); - } - - /// <summary> - /// Allocates a block of unmanaged, or pooled manaaged memory depending on - /// compilation flags and runtime unamanged allocators. - /// </summary> - /// <typeparam name="T">The unamanged type to allocate</typeparam> - /// <param name="elements">The number of elements of the type within the block</param> - /// <param name="zero">Flag to zero elements during allocation before the method returns</param> - /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="OutOfMemoryException"></exception> - public static IMemoryHandle<T> SafeAlloc<T>(int elements, bool zero = false) where T: unmanaged - { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } - - //If the element count is larger than max pool size, alloc from shared heap - if (elements > MAX_UNSAFE_POOL_SIZE) - { - //Alloc from shared heap - return Shared.Alloc<T>(elements, zero); - } - else - { - //Get temp buffer from shared buffer pool - return new VnTempBuffer<T>(elements, zero); - } - } - - /// <summary> - /// Allocates a block of unmanaged, or pooled manaaged memory depending on - /// compilation flags and runtime unamanged allocators, rounded up to the - /// neareset memory page. - /// </summary> - /// <typeparam name="T">The unamanged type to allocate</typeparam> - /// <param name="elements">The number of elements of the type within the block</param> - /// <param name="zero">Flag to zero elements during allocation before the method returns</param> - /// <returns>A handle to the block of memory</returns> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="OutOfMemoryException"></exception> - public static IMemoryHandle<T> SafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged - { - if (elements < 0) - { - throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); - } - - //Round to nearest page (in bytes) - nint np = NearestPage(elements * sizeof(T)); - - //Resize to element size - np /= sizeof(T); - - return SafeAlloc<T>((int)np, zero); - } - - #endregion } }
\ No newline at end of file diff --git a/lib/Utils/src/Memory/MemoryUtilAlloc.cs b/lib/Utils/src/Memory/MemoryUtilAlloc.cs new file mode 100644 index 0000000..e4210e7 --- /dev/null +++ b/lib/Utils/src/Memory/MemoryUtilAlloc.cs @@ -0,0 +1,291 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: MemoryUtilAlloc.cs +* +* MemoryUtilAlloc.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 VNLib.Utils.Extensions; + +namespace VNLib.Utils.Memory +{ + public static unsafe partial class MemoryUtil + { + #region alloc + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators. + /// </summary> + /// <typeparam name="T">The unamanged type to allocate</typeparam> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + public static UnsafeMemoryHandle<T> UnsafeAlloc<T>(int elements, bool zero = false) where T : unmanaged + { + if (elements < 0) + { + throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + } + + /* + * 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 + * to use a managed array. + * + * We want to avoid allocations, that may end up in the LOH if we can + */ + + if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE) + { + // Alloc from heap + IntPtr block = Shared.Alloc((uint)elements, (uint)sizeof(T), zero); + //Init new handle + return new(Shared, block, elements); + } + else + { + return new(ArrayPool<T>.Shared, elements, zero); + } + } + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// </summary> + /// <typeparam name="T">The unamanged type to allocate</typeparam> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeMemoryHandle<T> UnsafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged + { + if (elements < 0) + { + throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + } + + //Round to nearest page (in bytes) + nint np = NearestPage(elements * sizeof(T)); + + //Resize to element size + np /= sizeof(T); + + return UnsafeAlloc<T>((int)np, zero); + } + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators. + /// </summary> + /// <typeparam name="T">The unamanged type to allocate</typeparam> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + public static IMemoryHandle<T> SafeAlloc<T>(int elements, bool zero = false) where T : unmanaged + { + if (elements < 0) + { + throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + } + + /* + * 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 + * to use a managed array. + * + * We want to avoid allocations, that may end up in the LOH if we can + */ + + if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || ByteCount<T>((uint)elements) > MAX_UNSAFE_POOL_SIZE) + { + return Shared.Alloc<T>(elements, zero); + } + else + { + return new VnTempBuffer<T>(ArrayPool<T>.Shared, elements, zero); + } + } + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// </summary> + /// <typeparam name="T">The unamanged type to allocate</typeparam> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + public static IMemoryHandle<T> SafeAllocNearestPage<T>(int elements, bool zero = false) where T : unmanaged + { + if (elements < 0) + { + throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + } + + //Round to nearest page (in bytes) + nint np = NearestPage(elements * sizeof(T)); + + //Resize to element size + np /= sizeof(T); + + return SafeAlloc<T>((int)np, zero); + } + + #endregion + + #region ByteOptimimzations + + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators. + /// </summary> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + public static UnsafeMemoryHandle<byte> UnsafeAlloc(int elements, bool zero = false) + { + if (elements < 0) + { + throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + } + + /* + * 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 + * to use a managed array. + * + * We want to avoid allocations, that may end up in the LOH if we can + */ + + if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || elements > MAX_UNSAFE_POOL_SIZE) + { + // Alloc from heap + IntPtr block = Shared.Alloc((uint)elements, 1, zero); + //Init new handle + return new(Shared, block, elements); + } + else + { + return new(ArrayPool<byte>.Shared, elements, zero); + } + } + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// </summary> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static UnsafeMemoryHandle<byte> UnsafeAllocNearestPage(int elements, bool zero = false) + { + if (elements < 0) + { + throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + } + + //Round to nearest page (in bytes) + nint np = NearestPage(elements); + + return UnsafeAlloc((int)np, zero); + } + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators. + /// </summary> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + public static IMemoryHandle<byte> SafeAlloc(int elements, bool zero = false) + { + if (elements < 0) + { + throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + } + + /* + * 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 + * to use a managed array. + * + * We want to avoid allocations, that may end up in the LOH if we can + */ + + if ((Shared.CreationFlags & HeapCreation.UseSynchronization) == 0 || elements > MAX_UNSAFE_POOL_SIZE) + { + return Shared.Alloc<byte>(elements, zero); + } + else + { + return new VnTempBuffer<byte>(ArrayPool<byte>.Shared, elements, zero); + } + } + + /// <summary> + /// Allocates a block of unmanaged, or pooled manaaged memory depending on + /// compilation flags and runtime unamanged allocators, rounded up to the + /// neareset memory page. + /// </summary> + /// <param name="elements">The number of elements of the type within the block</param> + /// <param name="zero">Flag to zero elements during allocation before the method returns</param> + /// <returns>A handle to the block of memory</returns> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="OutOfMemoryException"></exception> + public static IMemoryHandle<byte> SafeAllocNearestPage(int elements, bool zero = false) + { + if (elements < 0) + { + throw new ArgumentException("Number of elements must be a positive integer", nameof(elements)); + } + + //Round to nearest page (in bytes) + nint np = NearestPage(elements); + + return SafeAlloc((int)np, zero); + } + + #endregion + } + +} diff --git a/lib/Utils/src/Memory/NativeHeap.cs b/lib/Utils/src/Memory/NativeHeap.cs new file mode 100644 index 0000000..30a65ae --- /dev/null +++ b/lib/Utils/src/Memory/NativeHeap.cs @@ -0,0 +1,212 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Utils +* File: NativeHeap.cs +* +* NativeHeap.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.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +using VNLib.Utils.Native; + +namespace VNLib.Utils.Memory +{ + /// <summary> + /// <para> + /// Allows for exposing a dynamically loaded native heap implementation. + /// </para> + /// </summary> + public class NativeHeap : UnmanagedHeapBase + { + public const string CREATE_METHOD_NAME = "heapCreate"; + public const string ALLOCATE_METHOD_NAME = "heapAlloc"; + public const string REALLOC_METHOD_NAME = "heapRealloc"; + public const string FREE_METHOD_NAME = "heapFree"; + public const string DESTROY_METHOD_NAME = "heapDestroy"; + + /// <summary> + /// <para> + /// Loads an unmanaged heap at runtime, into the current process at the given path. The dll must conform + /// to the unmanaged heap format. After the method table is loaded, the heapCreate method is called to + /// initialze the heap. + /// </para> + /// </summary> + /// <param name="dllPath">The path to the heap's dll file to load into the process.</param> + /// <param name="searchPath">The native library search path</param> + /// <param name="creationFlags">Specifes the creation flags to pass to the heap creaetion method</param> + /// <param name="flags">Generic flags passed directly to the heap creation method</param> + /// <returns>The newly initialized <see cref="NativeHeap"/></returns> + public unsafe static NativeHeap LoadHeap(string dllPath, DllImportSearchPath searchPath, HeapCreation creationFlags, ERRNO flags) + { + //Create a flags structure + UnmanagedHeapFlags hf; + UnmanagedHeapFlags* hFlags = &hf; + + //Set defaults + hFlags->Flags = flags; + hFlags->InternalFlags = creationFlags; + hFlags->HeapPointer = IntPtr.Zero; + + //Create the heap + return LoadHeapCore(dllPath, searchPath, hFlags); + } + + private unsafe static NativeHeap LoadHeapCore(string path, DllImportSearchPath searchPath, UnmanagedHeapFlags* flags) + { + //Try to load the library + SafeLibraryHandle library = SafeLibraryHandle.LoadLibrary(path, searchPath); + try + { + //Open method table + HeapMethods table = new() + { + //Get method delegates + Alloc = library.DangerousGetMethod<AllocDelegate>(ALLOCATE_METHOD_NAME), + + Destroy = library.DangerousGetMethod<DestroyHeapDelegate>(DESTROY_METHOD_NAME), + + Free = library.DangerousGetMethod<FreeDelegate>(FREE_METHOD_NAME), + + Realloc = library.DangerousGetMethod<ReallocDelegate>(REALLOC_METHOD_NAME), + + Library = library + }; + + //Get the create method + CreateHeapDelegate create = library.DangerousGetMethod<CreateHeapDelegate>(CREATE_METHOD_NAME); + + //Create the new heap + bool success = create(flags); + + if (!success) + { + throw new NativeMemoryException("Failed to create the new heap, the heap create method returned a null pointer"); + } + + //Return the neap heap + return new(flags, table); + } + catch + { + //Cleanup + library.Dispose(); + throw; + } + } + + + private readonly SafeLibraryHandle LibHandle; + private AllocDelegate AllocMethod; + private ReallocDelegate ReallocMethod; + private FreeDelegate FreeMethod; + private DestroyHeapDelegate Destroy; + + private unsafe NativeHeap(UnmanagedHeapFlags* flags, HeapMethods methodTable) :base(flags->InternalFlags, true) + { + //Store heap pointer + handle = flags->HeapPointer; + + //Store the method table + AllocMethod = methodTable.Alloc; + ReallocMethod = methodTable.Realloc; + FreeMethod = methodTable.Free; + Destroy = methodTable.Destroy; + + //Store library + LibHandle = methodTable.Library; + } + + ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override IntPtr AllocBlock(nuint elements, nuint size, bool zero) => AllocMethod(handle, elements, size, zero); + + ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override IntPtr ReAllocBlock(IntPtr block, nuint elements, nuint size, bool zero) => ReallocMethod(handle, block, elements, size, zero); + + ///<inheritdoc/> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected override bool FreeBlock(IntPtr block) => FreeMethod(handle, block); + + ///<inheritdoc/> + protected override bool ReleaseHandle() + { + //Destroy the heap + bool ret = Destroy(handle); + + //Cleanup the method table + Cleanup(); + + //Free the library + LibHandle.Dispose(); + + return ret; + } + +#nullable disable + private void Cleanup() + { + AllocMethod = null; + ReallocMethod = null; + FreeMethod = null; + Destroy = null; + } +#nullable enable + + /* + * Delegate methods match the native header impl for unmanaged heaps + */ + + unsafe delegate ERRNO CreateHeapDelegate(UnmanagedHeapFlags* createFlags); + + delegate IntPtr AllocDelegate(IntPtr handle, nuint elements, nuint alignment, [MarshalAs(UnmanagedType.Bool)] bool zero); + + delegate IntPtr ReallocDelegate(IntPtr heap, IntPtr block, nuint elements, nuint alignment, [MarshalAs(UnmanagedType.Bool)] bool zero); + + delegate ERRNO FreeDelegate(IntPtr heap, IntPtr block); + + delegate ERRNO DestroyHeapDelegate(IntPtr heap); + + [StructLayout(LayoutKind.Sequential)] + record struct UnmanagedHeapFlags + { + public IntPtr HeapPointer; + + public HeapCreation InternalFlags; + + public ERRNO Flags; + } + + readonly record struct HeapMethods + { + public readonly SafeLibraryHandle Library { get; init; } + + public readonly AllocDelegate Alloc { get; init; } + + public readonly ReallocDelegate Realloc { get; init; } + + public readonly FreeDelegate Free { get; init; } + + public readonly DestroyHeapDelegate Destroy { get; init; } + } + } +}
\ No newline at end of file diff --git a/lib/Utils/src/Memory/ProcessHeap.cs b/lib/Utils/src/Memory/ProcessHeap.cs index 7afe4b1..2792af9 100644 --- a/lib/Utils/src/Memory/ProcessHeap.cs +++ b/lib/Utils/src/Memory/ProcessHeap.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -36,6 +36,20 @@ namespace VNLib.Utils.Memory public unsafe class ProcessHeap : VnDisposeable, IUnmangedHeap { /// <summary> + /// Gets the shared process heap instance + /// </summary> + public static ProcessHeap Shared { get; } = new(); + + /// <summary> + /// <inheritdoc/> + /// <para> + /// Is always <see cref="HeapCreation.IsSharedHeap"/> as this heap is the default + /// process heap. Meaining memory will be shared across the process + /// </para> + /// </summary> + public HeapCreation CreationFlags { get; } = HeapCreation.IsSharedHeap; + + /// <summary> /// Initalizes a new global (cross platform) process heap /// </summary> public ProcessHeap() diff --git a/lib/Utils/src/Memory/RpMallocPrivateHeap.cs b/lib/Utils/src/Memory/RpMallocPrivateHeap.cs deleted file mode 100644 index 323f228..0000000 --- a/lib/Utils/src/Memory/RpMallocPrivateHeap.cs +++ /dev/null @@ -1,284 +0,0 @@ -/* -* Copyright (c) 2023 Vaughn Nugent -* -* Library: VNLib -* Package: VNLib.Utils -* File: RpMallocPrivateHeap.cs -* -* RpMallocPrivateHeap.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.Diagnostics; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; - -using LPVOID = System.IntPtr; -using LPHEAPHANDLE = System.IntPtr; - -namespace VNLib.Utils.Memory -{ - /// <summary> - /// A wrapper class for cross platform RpMalloc implementation. - /// </summary> - [ComVisible(false)] - public sealed class RpMallocPrivateHeap : UnmanagedHeapBase - { - const string DLL_NAME = "rpmalloc"; - - #region statics - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern int rpmalloc_initialize(); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern void rpmalloc_finalize(); - - //Heap api - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPHEAPHANDLE rpmalloc_heap_acquire(); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern void rpmalloc_heap_release(LPHEAPHANDLE heap); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_alloc(LPHEAPHANDLE heap, nuint size); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_aligned_alloc(LPHEAPHANDLE heap, nuint alignment, nuint size); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_calloc(LPHEAPHANDLE heap, nuint num, nuint size); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_aligned_calloc(LPHEAPHANDLE heap, nuint alignment, nuint num, nuint size); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_realloc(LPHEAPHANDLE heap, LPVOID ptr, nuint size, nuint flags); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc_heap_aligned_realloc(LPHEAPHANDLE heap, LPVOID ptr, nuint alignment, nuint size, nuint flags); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern void rpmalloc_heap_free(LPHEAPHANDLE heap, LPVOID ptr); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern void rpmalloc_heap_free_all(LPHEAPHANDLE heap); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern void rpmalloc_heap_thread_set_current(LPHEAPHANDLE heap); - - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern void rpmalloc_thread_initialize(); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern int rpmalloc_is_thread_initialized(); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern void rpmalloc_thread_finalize(int release_caches); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpmalloc(nuint size); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rpcalloc(nuint num, nuint size); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern LPVOID rprealloc(LPVOID ptr, nuint size); - [DllImport(DLL_NAME, ExactSpelling = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.SafeDirectories)] - static extern void rpfree(LPVOID ptr); - - #endregion - - private sealed class RpMallocGlobalHeap : IUnmangedHeap - { - IntPtr IUnmangedHeap.Alloc(nuint elements, nuint size, bool zero) - { - return RpMalloc(elements, size, zero); - } - - //Global heap does not need to be disposed - void IDisposable.Dispose() - { } - - bool IUnmangedHeap.Free(ref IntPtr block) - { - //Free the block - RpFree(ref block); - return true; - } - - void IUnmangedHeap.Resize(ref IntPtr block, nuint elements, nuint size, bool zero) - { - //Try to resize the block - IntPtr resize = RpRealloc(block, elements, size); - - //assign ptr - block = resize != IntPtr.Zero ? resize : throw new NativeMemoryOutOfMemoryException("Failed to resize the block"); - } - } - - /// <summary> - /// <para> - /// A <see cref="IUnmangedHeap"/> API for the RPMalloc library if loaded. - /// </para> - /// <para> - /// This heap is thread safe and may be converted to a <see cref="MemoryManager{T}"/> - /// infinitley and disposed safely. - /// </para> - /// <para> - /// If the native library is not loaded, calls to this API will throw a <see cref="DllNotFoundException"/>. - /// </para> - /// </summary> - public static IUnmangedHeap GlobalHeap { get; } = new RpMallocGlobalHeap(); - - /// <summary> - /// <para> - /// Initializes RpMalloc for the current thread and alloctes a block of memory - /// </para> - /// </summary> - /// <param name="elements">The number of elements to allocate</param> - /// <param name="size">The number of bytes per element type (aligment)</param> - /// <param name="zero">Zero the block of memory before returning</param> - /// <returns>A pointer to the block, (zero if failed)</returns> - public static LPVOID RpMalloc(nuint elements, nuint size, bool zero) - { - //See if the current thread has been initialized - if (rpmalloc_is_thread_initialized() == 0) - { - //Initialize the current thread - rpmalloc_thread_initialize(); - } - - //Alloc block - LPVOID block; - - if (zero) - { - block = rpcalloc(elements, size); - } - else - { - //Calculate the block size - nuint blockSize = checked(elements * size); - - block = rpmalloc(blockSize); - } - return block; - } - - /// <summary> - /// Frees a block of memory allocated by RpMalloc - /// </summary> - /// <param name="block">A ref to the pointer of the block to free</param> - public static void RpFree(ref LPVOID block) - { - if (block != IntPtr.Zero) - { - rpfree(block); - block = IntPtr.Zero; - } - } - - /// <summary> - /// Attempts to re-allocate the specified block on the global heap - /// </summary> - /// <param name="block">A pointer to a previously allocated block of memory</param> - /// <param name="elements">The number of elements in the block</param> - /// <param name="size">The number of bytes in the element</param> - /// <returns>A pointer to the new block if the reallocation succeeded, null if the resize failed</returns> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="OverflowException"></exception> - public static LPVOID RpRealloc(LPVOID block, nuint elements, nuint size) - { - if(block == IntPtr.Zero) - { - throw new ArgumentException("The supplied block is not valid", nameof(block)); - } - - //Calc new block size - nuint blockSize = checked(elements * size); - - return rprealloc(block, blockSize); - } - - #region instance - - /// <summary> - /// Initializes a new RpMalloc first class heap to allocate memory blocks from - /// </summary> - /// <param name="zeroAll">A global flag to zero all blocks of memory allocated</param> - /// <exception cref="NativeMemoryException"></exception> - public RpMallocPrivateHeap(bool zeroAll):base(zeroAll, true) - { - //Alloc the heap - handle = rpmalloc_heap_acquire(); - if(IsInvalid) - { - throw new NativeMemoryException("Failed to aquire a new heap"); - } -#if TRACE - Trace.WriteLine($"RPMalloc heap {handle:x} created"); -#endif - } - - ///<inheritdoc/> - protected override bool ReleaseHandle() - { -#if TRACE - Trace.WriteLine($"RPMalloc heap {handle:x} destroyed"); -#endif - //Release all heap memory - rpmalloc_heap_free_all(handle); - //Destroy the heap - rpmalloc_heap_release(handle); - //Release base - return true; - } - - ///<inheritdoc/> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected sealed override LPVOID AllocBlock(nuint elements, nuint size, bool zero) - { - //Alloc or calloc and initalize - return zero ? rpmalloc_heap_calloc(handle, elements, size) : rpmalloc_heap_alloc(handle, checked(size * elements)); - } - - ///<inheritdoc/> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected sealed override bool FreeBlock(LPVOID block) - { - //Free block - rpmalloc_heap_free(handle, block); - return true; - } - - ///<inheritdoc/> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected sealed override LPVOID ReAllocBlock(LPVOID block, nuint elements, nuint size, bool zero) - { - //Realloc - return rpmalloc_heap_realloc(handle, block, checked(elements * size), 0); - } - - #endregion - } -} diff --git a/lib/Utils/src/Memory/UnmanagedHeapBase.cs b/lib/Utils/src/Memory/UnmanagedHeapBase.cs index 4f5084a..eea84c8 100644 --- a/lib/Utils/src/Memory/UnmanagedHeapBase.cs +++ b/lib/Utils/src/Memory/UnmanagedHeapBase.cs @@ -48,22 +48,37 @@ namespace VNLib.Utils.Memory protected readonly bool GlobalZero; /// <summary> + /// A value that inidicates that locking will + /// be used when invoking heap operations + /// </summary> + protected readonly bool UseSynchronization; + + /// <summary> /// Initalizes the unmanaged heap base class (init synchronization handle) /// </summary> - /// <param name="globalZero">A global flag to zero all blocks of memory during allocation</param> + /// <param name="flags">Creation flags to obey</param> /// <param name="ownsHandle">A flag that indicates if the handle is owned by the instance</param> - protected UnmanagedHeapBase(bool globalZero, bool ownsHandle) : base(ownsHandle) + protected UnmanagedHeapBase(HeapCreation flags, bool ownsHandle) : base(ownsHandle) { HeapLock = new(); - GlobalZero = globalZero; + GlobalZero = flags.HasFlag(HeapCreation.GlobalZero); + UseSynchronization = flags.HasFlag(HeapCreation.UseSynchronization); + CreationFlags = flags; } ///<inheritdoc/> - ///<remarks>Increments the handle count</remarks> + public HeapCreation CreationFlags { get; } + + ///<inheritdoc/> + ///<remarks>Increments the handle count, free must be called to decrement the handle count</remarks> + ///<exception cref="OverflowException"></exception> ///<exception cref="OutOfMemoryException"></exception> ///<exception cref="ObjectDisposedException"></exception> public LPVOID Alloc(nuint elements, nuint size, bool zero) { + //Check for overflow for size + _ = checked(elements * size); + //Force zero if global flag is set zero |= GlobalZero; bool handleCountIncremented = false; @@ -80,10 +95,20 @@ namespace VNLib.Utils.Memory try { LPVOID block; - //Enter lock - lock(HeapLock) + + //Check if lock should be used + if (UseSynchronization) { - //Alloc block + //Enter lock + lock(HeapLock) + { + //Alloc block + block = AllocBlock(elements, size, zero); + } + } + else + { + //Alloc block without lock block = AllocBlock(elements, size, zero); } //Check if block was allocated @@ -96,8 +121,9 @@ namespace VNLib.Utils.Memory throw; } } - + ///<inheritdoc/> + ///<exception cref="OverflowException"></exception> ///<remarks>Decrements the handle count</remarks> public bool Free(ref LPVOID block) { @@ -110,12 +136,20 @@ namespace VNLib.Utils.Memory return true; } - //wait for lock - lock (HeapLock) + if (UseSynchronization) { - //Free block + //wait for lock + lock (HeapLock) + { + //Free block + result = FreeBlock(block); + //Release lock before releasing handle + } + } + else + { + //No lock result = FreeBlock(block); - //Release lock before releasing handle } //Decrement handle count @@ -126,20 +160,35 @@ namespace VNLib.Utils.Memory } ///<inheritdoc/> + ///<exception cref="OverflowException"></exception> ///<exception cref="OutOfMemoryException"></exception> ///<exception cref="ObjectDisposedException"></exception> public void Resize(ref LPVOID block, nuint elements, nuint size, bool zero) { + //Check for overflow for size + _ = checked(elements * size); + LPVOID newBlock; - lock (HeapLock) + //Global zero flag will cause a zero + zero |= GlobalZero; + + /* + * Realloc may return a null pointer if allocation fails + * so check the results and only assign the block pointer + * if the result is valid. Otherwise pointer block should + * be left untouched + */ + + if (UseSynchronization) + { + lock (HeapLock) + { + newBlock = ReAllocBlock(block, elements, size, zero); + } + } + else { - /* - * Realloc may return a null pointer if allocation fails - * so check the results and only assign the block pointer - * if the result is valid. Otherwise pointer block should - * be left untouched - */ newBlock = ReAllocBlock(block, elements, size, zero); } @@ -167,7 +216,7 @@ namespace VNLib.Utils.Memory /// </summary> /// <param name="block">The block to free</param> protected abstract bool FreeBlock(LPVOID block); - + /// <summary> /// Resizes the previously allocated block of memory on the current heap /// </summary> diff --git a/lib/Utils/src/Memory/VnTable.cs b/lib/Utils/src/Memory/VnTable.cs index 2c6ce74..7769c23 100644 --- a/lib/Utils/src/Memory/VnTable.cs +++ b/lib/Utils/src/Memory/VnTable.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -23,6 +23,7 @@ */ using System; +using System.Runtime.CompilerServices; using VNLib.Utils.Extensions; @@ -84,13 +85,11 @@ namespace VNLib.Utils.Memory this.Rows = rows; this.Cols = cols; - ulong tableSize = checked((ulong) rows * (ulong) cols); + ulong tableSize = checked((ulong)rows * (ulong)cols); - if (tableSize > nuint.MaxValue) + if ((tableSize * (uint)Unsafe.SizeOf<T>()) > nuint.MaxValue) { -#pragma warning disable CA2201 // Do not raise reserved exception types - throw new OutOfMemoryException("Table size is too large"); -#pragma warning restore CA2201 // Do not raise reserved exception types + throw new ArgumentOutOfRangeException("Rows and cols","Table size is too large"); } //Alloc a buffer with zero memory enabled, with Rows * Cols number of elements diff --git a/lib/Utils/src/Memory/VnTempBuffer.cs b/lib/Utils/src/Memory/VnTempBuffer.cs index 1d8e42f..5f5f831 100644 --- a/lib/Utils/src/Memory/VnTempBuffer.cs +++ b/lib/Utils/src/Memory/VnTempBuffer.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -24,8 +24,6 @@ using System; using System.Buffers; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; using VNLib.Utils.Extensions; @@ -35,7 +33,7 @@ namespace VNLib.Utils.Memory /// A disposable temporary buffer from shared ArrayPool /// </summary> /// <typeparam name="T">Type of buffer to create</typeparam> - public sealed class VnTempBuffer<T> : VnDisposeable, IIndexable<int, T>, IMemoryHandle<T> + public sealed class VnTempBuffer<T> : VnDisposeable, IIndexable<int, T>, IMemoryHandle<T>, IMemoryOwner<T> { private readonly ArrayPool<T> Pool; @@ -43,6 +41,7 @@ namespace VNLib.Utils.Memory /// Referrence to internal buffer /// </summary> public T[] Buffer { get; private set; } + /// <summary> /// Inital/desired size of internal buffer /// </summary> @@ -64,6 +63,9 @@ namespace VNLib.Utils.Memory } } + ///<inheritdoc/> + Memory<T> IMemoryOwner<T>.Memory => AsMemory(); + /// <summary> /// Allocates a new <see cref="VnTempBuffer{BufType}"/> with a new buffer from shared array-pool /// </summary> @@ -177,33 +179,16 @@ namespace VNLib.Utils.Memory #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. } - unsafe MemoryHandle IPinnable.Pin(int elementIndex) - { - //Guard - if (elementIndex < 0 || elementIndex >= Buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(elementIndex)); - } - - //Pin the array - GCHandle arrHandle = GCHandle.Alloc(Buffer, GCHandleType.Pinned); - - //Get array base address - void* basePtr = (void*)arrHandle.AddrOfPinnedObject(); - - //Get element offset - void* indexOffet = Unsafe.Add<T>(basePtr, elementIndex); - - return new(indexOffet, arrHandle, this); - } + //Pin, will also check bounds + ///<inheritdoc/> + public MemoryHandle Pin(int elementIndex) => MemoryUtil.PinArrayAndGetHandle(Buffer, elementIndex); void IPinnable.Unpin() { //Gchandle will manage the unpin } - ~VnTempBuffer() => Free(); - - + ///<inheritdoc/> + ~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 9911195..d5f08fc 100644 --- a/lib/Utils/src/Memory/Win32PrivateHeap.cs +++ b/lib/Utils/src/Memory/Win32PrivateHeap.cs @@ -31,7 +31,6 @@ using System.Runtime.CompilerServices; using DWORD = System.Int64; using LPVOID = System.IntPtr; - namespace VNLib.Utils.Memory { ///<summary> @@ -58,6 +57,11 @@ namespace VNLib.Utils.Memory public const DWORD HEAP_REALLOC_IN_PLACE_ONLY = 0x10; public const DWORD HEAP_ZERO_MEMORY = 0x08; + + [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern LPVOID GetProcessHeap(); + [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] private static extern LPVOID HeapAlloc(IntPtr hHeap, DWORD flags, nuint dwBytes); @@ -88,7 +92,7 @@ namespace VNLib.Utils.Memory [DllImport(KERNEL_DLL, SetLastError = true, ExactSpelling = true)] [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] private static extern nuint HeapSize(IntPtr hHeap, DWORD flags, LPVOID lpMem); - + #endregion /// <summary> @@ -97,12 +101,39 @@ namespace VNLib.Utils.Memory /// <param name="initialSize">Intial size of the heap</param> /// <param name="maxHeapSize">Maximum size allowed for the heap (disabled = 0, default)</param> /// <param name="flags">Defalt heap flags to set globally for all blocks allocated by the heap (default = 0)</param> - public static Win32PrivateHeap Create(nuint initialSize, nuint maxHeapSize = 0, DWORD flags = HEAP_NO_FLAGS) + /// <param name="cFlags">Flags to configure heap creation</param> + /// <remarks> + /// Win32 heaps are not thread safe, so synchronization is required, you may disabled internal locking if you use + /// your own synchronization. + /// </remarks> + public static Win32PrivateHeap Create(nuint initialSize, HeapCreation cFlags, nuint maxHeapSize = 0, DWORD flags = HEAP_NO_FLAGS) { + if (cFlags.HasFlag(HeapCreation.IsSharedHeap)) + { + //Clear the synchronization flag because we don't need it for a process heap + cFlags &= ~HeapCreation.UseSynchronization; + + //Get the process heap + LPVOID handle = GetProcessHeap(); + + //The heap does not own the handle + return new Win32PrivateHeap(handle, cFlags, false); + } + + if (cFlags.HasFlag(HeapCreation.UseSynchronization)) + { + /* + * When the synchronization flag is set, we dont need to use + * the win32 serialization method + */ + + flags |= HEAP_NO_SERIALIZE; + } + //Call create, throw exception if the heap falled to allocate - IntPtr heapHandle = HeapCreate(flags, initialSize, maxHeapSize); + ERRNO heapHandle = HeapCreate(flags, initialSize, maxHeapSize); - if (heapHandle == IntPtr.Zero) + if (!heapHandle) { throw new NativeMemoryException("Heap could not be created"); } @@ -110,17 +141,19 @@ namespace VNLib.Utils.Memory Trace.WriteLine($"Win32 private heap {heapHandle:x} created"); #endif //Heap has been created so we can wrap it - return new(heapHandle); + return new(heapHandle, cFlags, true); } + /// <summary> /// LIFETIME WARNING. Consumes a valid win32 handle and will manage it's lifetime once constructed. /// Locking and memory blocks will attempt to be allocated from this heap handle. /// </summary> /// <param name="win32HeapHandle">An open and valid handle to a win32 private heap</param> + /// <param name="flags">The heap creation flags to obey</param> /// <returns>A wrapper around the specified heap</returns> - public static Win32PrivateHeap ConsumeExisting(IntPtr win32HeapHandle) => new (win32HeapHandle); + public static Win32PrivateHeap ConsumeExisting(IntPtr win32HeapHandle, HeapCreation flags) => new (win32HeapHandle, flags, true); - private Win32PrivateHeap(IntPtr heapPtr) : base(false, true) => handle = heapPtr; + private Win32PrivateHeap(IntPtr heapPtr, HeapCreation flags, bool ownsHeandle) : base(flags, ownsHeandle) => handle = heapPtr; /// <summary> /// Retrieves the size of a memory block allocated from the current heap. @@ -146,6 +179,7 @@ namespace VNLib.Utils.Memory return result; } + /// <summary> /// Validates the current heap instance. The function scans all the memory blocks in the heap and verifies that the heap control structures maintained by /// the heap manager are in a consistent state. @@ -173,6 +207,7 @@ namespace VNLib.Utils.Memory #endif return HeapDestroy(handle); } + ///<inheritdoc/> [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override sealed LPVOID AllocBlock(nuint elements, nuint size, bool zero) @@ -181,6 +216,7 @@ namespace VNLib.Utils.Memory return HeapAlloc(handle, zero ? HEAP_ZERO_MEMORY : HEAP_NO_FLAGS, bytes); } + ///<inheritdoc/> [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override sealed bool FreeBlock(LPVOID block) => HeapFree(handle, HEAP_NO_FLAGS, block); diff --git a/lib/Utils/src/VnEncoding.cs b/lib/Utils/src/VnEncoding.cs index c9cdbb0..4a95405 100644 --- a/lib/Utils/src/VnEncoding.cs +++ b/lib/Utils/src/VnEncoding.cs @@ -485,7 +485,7 @@ namespace VNLib.Utils //calc size of bin buffer int size = base32.Length; //Rent a bin buffer - using UnsafeMemoryHandle<byte> binBuffer = Memory.MemoryUtil.UnsafeAlloc<byte>(size); + using UnsafeMemoryHandle<byte> binBuffer = Memory.MemoryUtil.UnsafeAlloc(size); //Try to decode the data ERRNO decoded = TryFromBase32Chars(base32, binBuffer.Span); //Marshal back to a struct @@ -505,7 +505,7 @@ namespace VNLib.Utils return null; } //Buffer size of the base32 string will always be enough buffer space - using UnsafeMemoryHandle<byte> tempBuffer = Memory.MemoryUtil.UnsafeAlloc<byte>(base32.Length); + using UnsafeMemoryHandle<byte> tempBuffer = Memory.MemoryUtil.UnsafeAlloc(base32.Length); //Try to decode the data ERRNO decoded = TryFromBase32Chars(base32, tempBuffer.Span); @@ -884,7 +884,7 @@ namespace VNLib.Utils int decodedSize = encoding.GetByteCount(chars); //alloc buffer - using UnsafeMemoryHandle<byte> decodeHandle = MemoryUtil.UnsafeAlloc<byte>(decodedSize); + using UnsafeMemoryHandle<byte> decodeHandle = MemoryUtil.UnsafeAlloc(decodedSize); //Get the utf8 binary data int count = encoding.GetBytes(chars, decodeHandle); return Base64UrlDecode(decodeHandle.Span[..count], output); diff --git a/lib/Utils/tests/Memory/MemoryHandleTest.cs b/lib/Utils/tests/Memory/MemoryHandleTest.cs index f7ab8d4..f8d9b79 100644 --- a/lib/Utils/tests/Memory/MemoryHandleTest.cs +++ b/lib/Utils/tests/Memory/MemoryHandleTest.cs @@ -36,13 +36,15 @@ namespace VNLib.Utils.Memory.Tests { [TestMethod] - public void MemoryHandleAllocLongExtensionTest() + public unsafe void MemoryHandleAllocLongExtensionTest() { + Assert.IsTrue(sizeof(nuint) == 8); + //Check for negatives - Assert.ThrowsException<ArgumentOutOfRangeException>(() => Shared.Alloc<byte>(-1)); + Assert.ThrowsException<ArgumentOutOfRangeException>(() => Shared.Alloc<byte>(-1).Dispose()); //Make sure over-alloc throws - Assert.ThrowsException<OutOfMemoryException>(() => Shared.Alloc<byte>(nuint.MaxValue, false)); + Assert.ThrowsException<OverflowException>(() => Shared.Alloc<short>(nuint.MaxValue, false).Dispose()); } #if TARGET_64_BIT [TestMethod] diff --git a/lib/Utils/tests/Memory/MemoryUtilTests.cs b/lib/Utils/tests/Memory/MemoryUtilTests.cs index 10e5d31..473281f 100644 --- a/lib/Utils/tests/Memory/MemoryUtilTests.cs +++ b/lib/Utils/tests/Memory/MemoryUtilTests.cs @@ -20,20 +20,17 @@ namespace VNLib.Utils.Memory.Tests [TestMethod()] public void InitializeNewHeapForProcessTest() { - //Check if rpmalloc is loaded - if (MemoryUtil.IsRpMallocLoaded) - { - //Initialize the heap - using IUnmangedHeap heap = MemoryUtil.InitializeNewHeapForProcess(); - //Confirm that the heap is actually a rpmalloc heap - Assert.IsInstanceOfType(heap, typeof(RpMallocPrivateHeap)); - } - else - { - //Confirm that Rpmalloc will throw DLLNotFound if the lib is not loaded - Assert.ThrowsException<DllNotFoundException>(() => _ = RpMallocPrivateHeap.GlobalHeap.Alloc(1, 1, false)); - } + //Initialize the heap + using IUnmangedHeap heap = MemoryUtil.InitializeNewHeapForProcess(); + + //Test alloc + IntPtr block = heap.Alloc(1, 1, false); + + //Free block + heap.Free(ref block); + + //TODO verify the heap type by loading a dynamic heap dll } [TestMethod()] @@ -337,7 +334,7 @@ namespace VNLib.Utils.Memory.Tests public void GetSharedHeapStatsTest() { //Confirm heap diagnostics are enabled - Assert.AreEqual<string?>(Environment.GetEnvironmentVariable(MemoryUtil.SHARED_HEAP_ENABLE_DIAGNOISTICS_ENV), "1"); + Assert.AreEqual<string?>("1", Environment.GetEnvironmentVariable(MemoryUtil.SHARED_HEAP_ENABLE_DIAGNOISTICS_ENV)); //Get current stats HeapStatistics preTest = MemoryUtil.GetSharedHeapStats(); @@ -441,6 +438,29 @@ namespace VNLib.Utils.Memory.Tests const int TEST_1 = 1; //Unsafe byte test + using (UnsafeMemoryHandle<byte> byteBuffer = MemoryUtil.UnsafeAllocNearestPage(TEST_1, false)) + { + nuint byteSize = MemoryUtil.ByteSize(byteBuffer); + + //Confirm byte size is working also + Assert.IsTrue(byteSize == byteBuffer.Length); + + //Should be the same as the page size + Assert.IsTrue(byteSize == (nuint)Environment.SystemPageSize); + } + + using(IMemoryHandle<byte> safeByteBuffer = MemoryUtil.SafeAllocNearestPage(TEST_1, false)) + { + nuint byteSize = MemoryUtil.ByteSize(safeByteBuffer); + + //Confirm byte size is working also + Assert.IsTrue(byteSize == safeByteBuffer.Length); + + //Should be the same as the page size + Assert.IsTrue(byteSize == (nuint)Environment.SystemPageSize); + } + + //Unsafe byte test with generics using (UnsafeMemoryHandle<byte> byteBuffer = MemoryUtil.UnsafeAllocNearestPage<byte>(TEST_1, false)) { nuint byteSize = MemoryUtil.ByteSize(byteBuffer); @@ -452,7 +472,7 @@ namespace VNLib.Utils.Memory.Tests Assert.IsTrue(byteSize == (nuint)Environment.SystemPageSize); } - using(IMemoryHandle<byte> safeByteBuffer = MemoryUtil.SafeAllocNearestPage<byte>(TEST_1, false)) + using (IMemoryHandle<byte> safeByteBuffer = MemoryUtil.SafeAllocNearestPage<byte>(TEST_1, false)) { nuint byteSize = MemoryUtil.ByteSize(safeByteBuffer); diff --git a/lib/Utils/tests/Memory/NativeHeapTests.cs b/lib/Utils/tests/Memory/NativeHeapTests.cs new file mode 100644 index 0000000..d27d5fd --- /dev/null +++ b/lib/Utils/tests/Memory/NativeHeapTests.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +using System; + +namespace VNLib.Utils.Memory.Tests +{ + [TestClass()] + public class NativeHeapTests + { + [TestMethod()] + public void LoadHeapTest() + { + const string TEST_HEAP_FILENAME = @"rpmalloc.dll"; + + //Try to load the global heap + using NativeHeap heap = NativeHeap.LoadHeap(TEST_HEAP_FILENAME, System.Runtime.InteropServices.DllImportSearchPath.SafeDirectories, HeapCreation.None, 0); + + Assert.IsFalse(heap.IsInvalid); + + IntPtr block = heap.Alloc(100, sizeof(byte), false); + + Assert.IsTrue(block != IntPtr.Zero); + + //Free the block + Assert.IsTrue(heap.Free(ref block)); + + //confirm the pointer it zeroed + Assert.IsTrue(block == IntPtr.Zero); + + } + } +}
\ No newline at end of file diff --git a/lib/Utils/tests/Memory/VnTableTests.cs b/lib/Utils/tests/Memory/VnTableTests.cs index 474a201..cb8ea91 100644 --- a/lib/Utils/tests/Memory/VnTableTests.cs +++ b/lib/Utils/tests/Memory/VnTableTests.cs @@ -52,12 +52,18 @@ namespace VNLib.Utils.Memory.Tests Assert.IsTrue(10000 == table.Cols); } - - //Test oom, should be native - Assert.ThrowsException<OutOfMemoryException>(() => + try { using VnTable<int> table = new(uint.MaxValue, 20); - }); + + Assert.Fail("The table allocation did not fail as expected"); + } + catch (OutOfMemoryException) + {} + catch(Exception ex) + { + Assert.Fail("Table overflow creation test failed because another exception type was raised, {0}", ex.GetType().Name); + } } [TestMethod()] |