aboutsummaryrefslogtreecommitdiff
path: root/lib/Utils
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-03-27 02:20:06 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-03-27 02:20:06 -0400
commit6f7f4a4f03c7e62db64c01b2a0b128586bf11dad (patch)
tree2ef00d7d8527f5153ccd4188665bd9b47573cf27 /lib/Utils
parent6b5ca9e49e33eb3e03d6f7333661da7e6d0546fa (diff)
Native heap api and alloc optimizations
Diffstat (limited to 'lib/Utils')
-rw-r--r--lib/Utils/src/Memory/Diagnostics/TrackedHeapWrapper.cs7
-rw-r--r--lib/Utils/src/Memory/HeapCreation.cs54
-rw-r--r--lib/Utils/src/Memory/IUnmangedHeap.cs7
-rw-r--r--lib/Utils/src/Memory/MemoryHandle.cs7
-rw-r--r--lib/Utils/src/Memory/MemoryUtil.cs205
-rw-r--r--lib/Utils/src/Memory/MemoryUtilAlloc.cs291
-rw-r--r--lib/Utils/src/Memory/NativeHeap.cs212
-rw-r--r--lib/Utils/src/Memory/ProcessHeap.cs16
-rw-r--r--lib/Utils/src/Memory/RpMallocPrivateHeap.cs284
-rw-r--r--lib/Utils/src/Memory/UnmanagedHeapBase.cs89
-rw-r--r--lib/Utils/src/Memory/VnTable.cs11
-rw-r--r--lib/Utils/src/Memory/VnTempBuffer.cs37
-rw-r--r--lib/Utils/src/Memory/Win32PrivateHeap.cs52
-rw-r--r--lib/Utils/src/VnEncoding.cs6
-rw-r--r--lib/Utils/tests/Memory/MemoryHandleTest.cs8
-rw-r--r--lib/Utils/tests/Memory/MemoryUtilTests.cs50
-rw-r--r--lib/Utils/tests/Memory/NativeHeapTests.cs32
-rw-r--r--lib/Utils/tests/Memory/VnTableTests.cs14
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()]