feat(core)!: refactor safety/debug defines, remove FreeList in JobSchedular
Refactor to use MHP_ENABLE_SAFETY_CHECKS, MHP_ENABLE_STACKTRACE, and MHP_ENABLE_MIMALLOC for feature toggling. Remove FreeList allocator in JobSchedular and debug-layer code, simplifying memory management. Improve memory leak detection and reporting, update memory allocation API, and guard all safety/debug features with new defines. Update csproj files, README, and code samples to match new API and toggles. Fix and improve collection types for correct behavior with and without safety checks. The codebase is now more modular and easier to configure for different build environments. BREAKING CHANGE: Old defines (ENABLE_SAFETY_CHECKS, ENABLE_DEBUG_LAYER, ENABLE_MIMALLOC) are replaced with MHP_* equivalents. FreeList allocator and related debug features are removed. Some APIs and behaviors have changed for safety/debug configuration.
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
#define ENABLE_DEBUG_LAYER
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
using Misaki.HighPerformance.Collections;
|
||||
#endif
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
#if MHP_ENABLE_STACKTRACE
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
@@ -32,11 +30,11 @@ public readonly struct AllocationInfo
|
||||
get; init;
|
||||
}
|
||||
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
#if MHP_ENABLE_STACKTRACE
|
||||
/// <summary>
|
||||
/// Gets the stack trace at the time of allocation for debugging purposes.
|
||||
/// </summary>
|
||||
public GCHandle StackTrace
|
||||
public StackTrace? StackTrace
|
||||
{
|
||||
get; init;
|
||||
}
|
||||
@@ -67,18 +65,6 @@ public readonly struct AllocationManagerInitOpts
|
||||
/// </summary>
|
||||
public static unsafe class AllocationManager
|
||||
{
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct AllocationHeader
|
||||
{
|
||||
public AllocationHeader* prev;
|
||||
public AllocationHeader* next;
|
||||
public void* basePtr; // pointer returned by underlying allocator
|
||||
public nuint userSize; // requested size from the user
|
||||
public GCHandle stackHandle; // GCHandle to managed StackTrace
|
||||
}
|
||||
#endif
|
||||
|
||||
private struct ArenaAllocator : IAllocator, IDisposable
|
||||
{
|
||||
private const int _ARENA_MAGIC_ID = -3941029;
|
||||
@@ -99,7 +85,7 @@ public static unsafe class AllocationManager
|
||||
Alloc = &Allocate,
|
||||
Realloc = &Reallocate,
|
||||
Free = null,
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
IsValid = &IsValid
|
||||
#else
|
||||
IsValid = null
|
||||
@@ -110,7 +96,7 @@ public static unsafe class AllocationManager
|
||||
}
|
||||
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
@@ -119,20 +105,20 @@ public static unsafe class AllocationManager
|
||||
var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption);
|
||||
if (ptr == null)
|
||||
{
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = MemoryHandle.Invalid;
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
|
||||
#endif
|
||||
return ptr;
|
||||
}
|
||||
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
@@ -140,7 +126,7 @@ public static unsafe class AllocationManager
|
||||
if (ptr == null)
|
||||
{
|
||||
return Allocate(instance, newSize, alignment, allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, pHandle
|
||||
#endif
|
||||
);
|
||||
@@ -155,13 +141,13 @@ public static unsafe class AllocationManager
|
||||
|
||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
|
||||
#endif
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
private static bool IsValid(void* instance, MemoryHandle handle)
|
||||
{
|
||||
var selfPtr = (ArenaAllocator*)instance;
|
||||
@@ -195,7 +181,7 @@ public static unsafe class AllocationManager
|
||||
Alloc = &Allocate,
|
||||
Realloc = &Reallocate,
|
||||
Free = &Free,
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
IsValid = &IsValid
|
||||
#else
|
||||
IsValid = null
|
||||
@@ -204,13 +190,13 @@ public static unsafe class AllocationManager
|
||||
}
|
||||
|
||||
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
{
|
||||
return HeapAlloc(size, alignment, allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, pHandle
|
||||
#else
|
||||
, default
|
||||
@@ -219,7 +205,7 @@ public static unsafe class AllocationManager
|
||||
}
|
||||
|
||||
private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
@@ -227,7 +213,7 @@ public static unsafe class AllocationManager
|
||||
if (ptr == null)
|
||||
{
|
||||
return Allocate(null, newSize, alignment, allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, pHandle
|
||||
#endif
|
||||
);
|
||||
@@ -242,27 +228,27 @@ public static unsafe class AllocationManager
|
||||
|
||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||
HeapFree(ptr
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, *pHandle
|
||||
#else
|
||||
, default
|
||||
#endif
|
||||
);
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = newHandle;
|
||||
#endif
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
private static void Free(void* _, void* ptr
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle handle
|
||||
#endif
|
||||
)
|
||||
{
|
||||
HeapFree(ptr
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, handle
|
||||
#else
|
||||
, default
|
||||
@@ -270,7 +256,7 @@ public static unsafe class AllocationManager
|
||||
);
|
||||
}
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
private static bool IsValid(void* _, MemoryHandle handle)
|
||||
{
|
||||
return ContainsAllocation(handle);
|
||||
@@ -301,7 +287,7 @@ public static unsafe class AllocationManager
|
||||
Alloc = &Allocate,
|
||||
Realloc = &Reallocate,
|
||||
Free = null,
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
IsValid = &IsValid
|
||||
#else
|
||||
IsValid = null
|
||||
@@ -349,7 +335,7 @@ public static unsafe class AllocationManager
|
||||
}
|
||||
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
@@ -359,20 +345,20 @@ public static unsafe class AllocationManager
|
||||
var ptr = s_stack.Allocate(size, alignment, allocationOption);
|
||||
if (ptr == null)
|
||||
{
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = MemoryHandle.Invalid;
|
||||
#endif
|
||||
return null;
|
||||
}
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
|
||||
#endif
|
||||
return ptr;
|
||||
}
|
||||
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
@@ -380,7 +366,7 @@ public static unsafe class AllocationManager
|
||||
if (ptr == null)
|
||||
{
|
||||
return Allocate(instance, newSize, alignment, allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, pHandle
|
||||
#endif
|
||||
);
|
||||
@@ -402,7 +388,7 @@ public static unsafe class AllocationManager
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
|
||||
#endif
|
||||
return ptr;
|
||||
@@ -416,13 +402,13 @@ public static unsafe class AllocationManager
|
||||
|
||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
|
||||
#endif
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
private static bool IsValid(void* instance, MemoryHandle handle)
|
||||
{
|
||||
return handle.ID == _STACK_MAGIC_ID && handle.Generation <= (int)s_stack.Offset;
|
||||
@@ -465,7 +451,7 @@ public static unsafe class AllocationManager
|
||||
Alloc = &Allocate,
|
||||
Realloc = &Reallocate,
|
||||
Free = &Free,
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
IsValid = &IsValid
|
||||
#else
|
||||
IsValid = null
|
||||
@@ -474,54 +460,26 @@ public static unsafe class AllocationManager
|
||||
}
|
||||
|
||||
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
{
|
||||
var selfPtr = (FreeListAllocator*)instance;
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
var pad = alignment == 0 ? (nuint)IntPtr.Size : alignment;
|
||||
var total = size + (nuint)sizeof(AllocationHeader) + (pad - 1);
|
||||
var basePtr = selfPtr->_freeList.Allocate(total, pad, allocationOption);
|
||||
if (basePtr == null)
|
||||
{
|
||||
*pHandle = MemoryHandle.Invalid;
|
||||
return null;
|
||||
}
|
||||
|
||||
var user = AlignUp((byte*)basePtr + (nuint)sizeof(AllocationHeader), pad);
|
||||
var header = (AllocationHeader*)(user - (nuint)sizeof(AllocationHeader));
|
||||
|
||||
header->basePtr = basePtr;
|
||||
header->userSize = size;
|
||||
HeaderSetHandle(header, GCHandle.Alloc(new StackTrace(2, true)));
|
||||
|
||||
LinkHeader(header);
|
||||
|
||||
if (allocationOption.HasFlag(AllocationOption.Clear))
|
||||
{
|
||||
MemClear(user, size);
|
||||
}
|
||||
|
||||
*pHandle = AddAllocation(user, size);
|
||||
return user;
|
||||
#else
|
||||
var ptr = selfPtr->_freeList.Allocate(size, alignment, allocationOption);
|
||||
if (ptr == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = AddAllocation(ptr, size);
|
||||
#endif
|
||||
return ptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
@@ -529,48 +487,13 @@ public static unsafe class AllocationManager
|
||||
if (ptr == null)
|
||||
{
|
||||
return Allocate(instance, newSize, alignment, allocationOption
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, pHandle
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
var selfPtr = (FreeListAllocator*)instance;
|
||||
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
var oldHeader = (AllocationHeader*)((byte*)ptr - (nuint)sizeof(AllocationHeader));
|
||||
var handle = HeaderGetHandle(oldHeader);
|
||||
|
||||
var pad = alignment == 0 ? (nuint)IntPtr.Size : alignment;
|
||||
var total = newSize + (nuint)sizeof(AllocationHeader) + (pad - 1);
|
||||
var newBase = selfPtr->_freeList.Allocate(total, pad, allocationOption);
|
||||
if (newBase == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var newUser = AlignUp((byte*)newBase + (nuint)sizeof(AllocationHeader), pad);
|
||||
var newHeader = (AllocationHeader*)(newUser - (nuint)sizeof(AllocationHeader));
|
||||
|
||||
newHeader->basePtr = newBase;
|
||||
newHeader->userSize = newSize;
|
||||
HeaderSetHandle(newHeader, handle);
|
||||
|
||||
LinkHeader(newHeader);
|
||||
|
||||
MemCpy(newUser, ptr, Math.Min(oldSize, newSize));
|
||||
if (allocationOption.HasFlag(AllocationOption.Clear) && newSize > oldSize)
|
||||
{
|
||||
MemClear(newUser + oldSize, newSize - oldSize);
|
||||
}
|
||||
|
||||
UnlinkHeader(oldHeader);
|
||||
selfPtr->_freeList.Free(oldHeader->basePtr);
|
||||
RemoveAllocation(*pHandle);
|
||||
|
||||
*pHandle = AddAllocation(newUser, newSize);
|
||||
return newUser;
|
||||
#else
|
||||
var newPtr = selfPtr->_freeList.Allocate(newSize, alignment, allocationOption);
|
||||
if (newPtr == null)
|
||||
{
|
||||
@@ -581,16 +504,15 @@ public static unsafe class AllocationManager
|
||||
|
||||
selfPtr->_freeList.Free(ptr);
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
RemoveAllocation(*pHandle);
|
||||
*pHandle = AddAllocation(newPtr, newSize);
|
||||
#endif
|
||||
|
||||
return newPtr;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
private static bool IsValid(void* instance, MemoryHandle handle)
|
||||
{
|
||||
return ContainsAllocation(handle);
|
||||
@@ -598,23 +520,15 @@ public static unsafe class AllocationManager
|
||||
#endif
|
||||
|
||||
private static void Free(void* instance, void* ptr
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle handle
|
||||
#endif
|
||||
)
|
||||
{
|
||||
var selfPtr = (FreeListAllocator*)instance;
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
var header = (AllocationHeader*)((byte*)ptr - (nuint)sizeof(AllocationHeader));
|
||||
UnlinkHeader(header);
|
||||
HeaderFreeHandle(header);
|
||||
selfPtr->_freeList.Free(header->basePtr);
|
||||
RemoveAllocation(handle);
|
||||
#else
|
||||
selfPtr->_freeList.Free(ptr);
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
RemoveAllocation(handle);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -629,14 +543,8 @@ public static unsafe class AllocationManager
|
||||
private static StackAllocator* s_pStackAllocator;
|
||||
private static FreeListAllocator* s_pFreeListAllocator;
|
||||
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
private static SpinLock s_liveLock;
|
||||
private static AllocationHeader* s_pLiveHead;
|
||||
#endif
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
private static ConcurrentSlotMap<AllocationInfo> s_allocations = null!;
|
||||
public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of live tracked heap allocations.
|
||||
@@ -644,7 +552,7 @@ public static unsafe class AllocationManager
|
||||
public static int LiveAllocationCount => s_allocations.Count;
|
||||
#endif
|
||||
|
||||
private static bool s_initialized;
|
||||
private static volatile bool s_initialized;
|
||||
private static nuint s_threadLocalStackDefaultSize;
|
||||
|
||||
public static void Initialize(AllocationManagerInitOpts opts)
|
||||
@@ -654,11 +562,7 @@ public static unsafe class AllocationManager
|
||||
return;
|
||||
}
|
||||
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
s_liveLock = new SpinLock(false);
|
||||
s_pLiveHead = null;
|
||||
#endif
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
s_allocations = new ConcurrentSlotMap<AllocationInfo>(256);
|
||||
#endif
|
||||
|
||||
@@ -679,161 +583,6 @@ public static unsafe class AllocationManager
|
||||
s_initialized = true;
|
||||
}
|
||||
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static byte* AlignUp(byte* p, nuint alignment)
|
||||
{
|
||||
var a = alignment == 0 ? (nuint)IntPtr.Size : alignment;
|
||||
return (byte*)(((nuint)p + (a - 1)) & ~(a - 1));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static GCHandle HeaderGetHandle(AllocationHeader* header)
|
||||
{
|
||||
return header->stackHandle;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void HeaderSetHandle(AllocationHeader* header, GCHandle handle)
|
||||
{
|
||||
header->stackHandle = handle;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void HeaderFreeHandle(AllocationHeader* header)
|
||||
{
|
||||
if (header->stackHandle.IsAllocated)
|
||||
{
|
||||
header->stackHandle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void LinkHeader(AllocationHeader* header)
|
||||
{
|
||||
var taken = false;
|
||||
try
|
||||
{
|
||||
s_liveLock.Enter(ref taken);
|
||||
header->prev = null;
|
||||
header->next = s_pLiveHead;
|
||||
if (s_pLiveHead != null)
|
||||
{
|
||||
s_pLiveHead->prev = header;
|
||||
}
|
||||
s_pLiveHead = header;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (taken)
|
||||
{
|
||||
s_liveLock.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void UnlinkHeader(AllocationHeader* header)
|
||||
{
|
||||
var taken = false;
|
||||
try
|
||||
{
|
||||
s_liveLock.Enter(ref taken);
|
||||
var prev = header->prev;
|
||||
var next = header->next;
|
||||
|
||||
if (prev != null)
|
||||
{
|
||||
prev->next = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
s_pLiveHead = next;
|
||||
}
|
||||
|
||||
if (next != null)
|
||||
{
|
||||
next->prev = prev;
|
||||
}
|
||||
|
||||
header->prev = header->next = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (taken)
|
||||
{
|
||||
s_liveLock.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void* DebugAllocate(nuint size, nuint alignment)
|
||||
{
|
||||
// Over-allocate to fit header + alignment padding; we align the user pointer, header is placed just before it.
|
||||
var pad = alignment == 0 ? (nuint)IntPtr.Size : alignment;
|
||||
var total = size + (nuint)sizeof(AllocationHeader) + (pad - 1);
|
||||
|
||||
var basePtr = AlignedAlloc(total, pad);
|
||||
var user = AlignUp((byte*)basePtr + (nuint)sizeof(AllocationHeader), pad);
|
||||
var header = (AllocationHeader*)(user - (nuint)sizeof(AllocationHeader));
|
||||
|
||||
header->basePtr = basePtr;
|
||||
header->userSize = size;
|
||||
HeaderSetHandle(header, GCHandle.Alloc(new StackTrace(2, true)));
|
||||
|
||||
LinkHeader(header);
|
||||
return user;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void DebugFree(void* userPtr)
|
||||
{
|
||||
var header = (AllocationHeader*)((byte*)userPtr - (nuint)sizeof(AllocationHeader));
|
||||
UnlinkHeader(header);
|
||||
HeaderFreeHandle(header);
|
||||
AlignedFree(header->basePtr);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void* DebugReallocate(void* userPtr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
|
||||
{
|
||||
if (userPtr == null)
|
||||
{
|
||||
return DebugAllocate(newSize, alignment);
|
||||
}
|
||||
|
||||
var oldHeader = (AllocationHeader*)((byte*)userPtr - (nuint)sizeof(AllocationHeader));
|
||||
var handle = HeaderGetHandle(oldHeader); // preserve original allocation StackTrace
|
||||
|
||||
var pad = alignment == 0 ? (nuint)IntPtr.Size : alignment;
|
||||
var total = newSize + (nuint)sizeof(AllocationHeader) + (pad - 1);
|
||||
var newBase = AlignedAlloc(total, pad);
|
||||
var newUser = AlignUp((byte*)newBase + (nuint)sizeof(AllocationHeader), pad);
|
||||
var newHeader = (AllocationHeader*)(newUser - (nuint)sizeof(AllocationHeader));
|
||||
|
||||
newHeader->basePtr = newBase;
|
||||
newHeader->userSize = newSize;
|
||||
HeaderSetHandle(newHeader, handle); // transfer ownership to the new header
|
||||
|
||||
LinkHeader(newHeader);
|
||||
|
||||
// Mirror original behavior: copy newSize bytes
|
||||
MemCpy(newUser, userPtr, newSize);
|
||||
if (allocationOption.HasFlag(AllocationOption.Clear) && newSize > oldSize)
|
||||
{
|
||||
MemClear(newUser + oldSize, newSize - oldSize);
|
||||
}
|
||||
|
||||
// Unlink and free the old block (without freeing the StackTrace pHandle again)
|
||||
//oldHeader->stackHandle = GCHandle.FromIntPtr(0);
|
||||
UnlinkHeader(oldHeader);
|
||||
AlignedFree(oldHeader->basePtr);
|
||||
|
||||
return newUser;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the allocation pHandle for the specified allocator type.
|
||||
/// </summary>
|
||||
@@ -867,12 +616,7 @@ public static unsafe class AllocationManager
|
||||
{
|
||||
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
var ptr = DebugAllocate(size, alignment);
|
||||
#else
|
||||
var ptr = AlignedAlloc(size, alignment);
|
||||
#endif
|
||||
|
||||
if (ptr == null)
|
||||
{
|
||||
*pHandle = MemoryHandle.Invalid;
|
||||
@@ -884,7 +628,9 @@ public static unsafe class AllocationManager
|
||||
MemClear(ptr, size);
|
||||
}
|
||||
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
*pHandle = AddAllocation(ptr, size);
|
||||
#endif
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -898,18 +644,9 @@ public static unsafe class AllocationManager
|
||||
{
|
||||
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
if (handle != MagicHandle)
|
||||
{
|
||||
DebugFree(ptr);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
AlignedFree(ptr);
|
||||
}
|
||||
AlignedFree(ptr);
|
||||
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
RemoveAllocation(handle);
|
||||
#endif
|
||||
}
|
||||
@@ -922,9 +659,9 @@ public static unsafe class AllocationManager
|
||||
{
|
||||
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||
|
||||
if (TryGetAllocation(handle, out var ptr))
|
||||
if (TryGetAllocation(handle, out var info))
|
||||
{
|
||||
HeapFree((void*)ptr, handle);
|
||||
HeapFree((void*)info.Address, handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -953,22 +690,23 @@ public static unsafe class AllocationManager
|
||||
/// Registers a memory allocation and returns a handle that can be used to manage or reference the allocated memory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Always returns an invalid handle if ENABLE_SAFETY_CHECKS is disabled.
|
||||
/// Always returns an invalid handle if MHP_ENABLE_SAFETY_CHECKS is disabled.
|
||||
/// </remarks>
|
||||
/// <param name="ptr">A pointer to the memory block to be registered. The pointer must reference a valid, allocated memory region.</param>
|
||||
/// <param name="size">The size of the memory block to be registered.</param>
|
||||
/// <returns>A MemoryHandle representing the registered allocation.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static MemoryHandle AddAllocation(void* ptr, nuint size)
|
||||
{
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||
|
||||
var info = new AllocationInfo
|
||||
{
|
||||
Address = (IntPtr)ptr,
|
||||
Size = size,
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
StackTrace = GCHandle.Alloc(new StackTrace(1, true))
|
||||
#if MHP_ENABLE_STACKTRACE
|
||||
StackTrace = new StackTrace(1, true)
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -990,9 +728,9 @@ public static unsafe class AllocationManager
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool RemoveAllocation(MemoryHandle handle)
|
||||
{
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||
return s_allocations.Remove(handle.ID, handle.Generation);
|
||||
return s_allocations.Remove(handle.ID, handle.Generation, out var info);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
@@ -1005,16 +743,16 @@ public static unsafe class AllocationManager
|
||||
/// Always returns false if debug layer is disabled, and the output pointer will be set to <see cref="IntPtr.Zero"/>.
|
||||
/// </remarks>
|
||||
/// <param name="handle">The memory handle identifying the allocation to retrieve allocation.</param>
|
||||
/// <param name="ptr">When this method returns, contains the pointer to the memory allocation if found; otherwise, <see cref="IntPtr.Zero"/>.</param>
|
||||
/// <returns>true if the allocation was found and <paramref name="ptr"/> contains a valid pointer; otherwise, false.</returns>
|
||||
/// <param name="info">When this method returns, contains the allocation information associated with the specified handle, if the allocation was found; otherwise, an uninitialized value.</param>
|
||||
/// <returns>true if the allocation was found and <paramref name="info"/> contains valid allocation information; otherwise, false.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetAllocation(MemoryHandle handle, out IntPtr ptr)
|
||||
public static bool TryGetAllocation(MemoryHandle handle, out AllocationInfo info)
|
||||
{
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||
return s_allocations.TryGetElement(handle.ID, handle.Generation, out ptr);
|
||||
return s_allocations.TryGetElement(handle.ID, handle.Generation, out info);
|
||||
#else
|
||||
ptr = IntPtr.Zero;
|
||||
info = default;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -1032,14 +770,8 @@ public static unsafe class AllocationManager
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool ContainsAllocation(MemoryHandle handle)
|
||||
{
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
|
||||
|
||||
if (handle == MagicHandle)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return s_allocations.Contains(handle.ID, handle.Generation);
|
||||
#else
|
||||
return false;
|
||||
@@ -1056,51 +788,12 @@ public static unsafe class AllocationManager
|
||||
return;
|
||||
}
|
||||
|
||||
#if ENABLE_DEBUG_LAYER
|
||||
// In debug mode, walk the intrusive list to surface any leaks.
|
||||
var snapshot = new List<AllocationInfo>();
|
||||
var taken = false;
|
||||
try
|
||||
{
|
||||
s_liveLock.Enter(ref taken);
|
||||
if (s_pLiveHead != null)
|
||||
{
|
||||
snapshot.Capacity = 128;
|
||||
for (var p = s_pLiveHead; p != null; p = p->next)
|
||||
{
|
||||
var trace = (StackTrace)HeaderGetHandle(p).Target!;
|
||||
snapshot.Add(new AllocationInfo
|
||||
{
|
||||
Size = p->userSize,
|
||||
StackTrace = trace
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (taken)
|
||||
{
|
||||
s_liveLock.Exit();
|
||||
}
|
||||
}
|
||||
s_initialized = false;
|
||||
|
||||
nuint unfreeBytes = 0u;
|
||||
foreach (var info in snapshot)
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
if (s_allocations.Count > 0)
|
||||
{
|
||||
unfreeBytes += info.Size;
|
||||
}
|
||||
|
||||
if (unfreeBytes > 0u)
|
||||
{
|
||||
throw new MemoryLeakException(CollectionsMarshal.AsSpan(snapshot));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
if (s_allocations.Count != 0)
|
||||
{
|
||||
throw new InvalidOperationException($"There are still {s_allocations.Count} live tracked allocations.");
|
||||
throw new MemoryLeakException(s_allocations);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1109,7 +802,5 @@ public static unsafe class AllocationManager
|
||||
s_pFreeListAllocator->Dispose();
|
||||
|
||||
Free(s_pArenaAllocator);
|
||||
|
||||
s_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,31 +26,55 @@ public unsafe struct MemoryPool<T, TOpts> : IDisposable
|
||||
};
|
||||
}
|
||||
|
||||
private static void* Allocate(void* pAllocator, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||
private static void* Allocate(void* pAllocator, nuint size, nuint alignment, AllocationOption allocationOption
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
{
|
||||
return ((T*)pAllocator)->Allocate(size, alignment, allocationOption);
|
||||
}
|
||||
|
||||
private static void* Reallocate(void* pAllocator, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
|
||||
private static void* Reallocate(void* pAllocator, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle* pHandle
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if (ptr == null)
|
||||
{
|
||||
return Allocate(pAllocator, newSize, alignment, allocationOption, pHandle);
|
||||
return Allocate(pAllocator, newSize, alignment, allocationOption
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, pHandle
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
var newPtr = Allocate(pAllocator, newSize, alignment, allocationOption, pHandle);
|
||||
var newPtr = Allocate(pAllocator, newSize, alignment, allocationOption
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, pHandle
|
||||
#endif
|
||||
);
|
||||
if (newPtr == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
|
||||
Free(pAllocator, ptr, *pHandle);
|
||||
Free(pAllocator, ptr
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, *pHandle
|
||||
#endif
|
||||
);
|
||||
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
private static void Free(void* pAllocator, void* ptr, MemoryHandle handle)
|
||||
private static void Free(void* pAllocator, void* ptr
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
, MemoryHandle handle
|
||||
#endif
|
||||
)
|
||||
{
|
||||
((T*)pAllocator)->Free(ptr);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
|
||||
_allocator = allocator;
|
||||
_handle = handle;
|
||||
_originalOffset = allocator->_offset;
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_allocator->_activeScopeCount++;
|
||||
#endif
|
||||
}
|
||||
@@ -42,7 +42,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
|
||||
if (_allocator != null)
|
||||
{
|
||||
_allocator->_offset = _allocator->_offset > _originalOffset ? _originalOffset : _allocator->_offset;
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_allocator->_activeScopeCount--;
|
||||
#endif
|
||||
}
|
||||
@@ -52,7 +52,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
|
||||
private byte* _buffer;
|
||||
private nuint _size;
|
||||
private nuint _offset;
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
private uint _activeScopeCount;
|
||||
#endif
|
||||
|
||||
@@ -74,7 +74,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
|
||||
_buffer = (byte*)Malloc(size);
|
||||
_size = size;
|
||||
_offset = 0;
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_activeScopeCount = 0;
|
||||
#endif
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
|
||||
/// there is insufficient space in the buffer.</returns>
|
||||
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
|
||||
{
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
if (_activeScopeCount == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Allocations can only be made within an active memory scope.");
|
||||
|
||||
@@ -30,7 +30,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
||||
_allocator = allocator;
|
||||
_handle = handle;
|
||||
_originalOffset = allocator->_allocatedOffset;
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_allocator->_activeScopeCount++;
|
||||
#endif
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
||||
if (_allocator != null)
|
||||
{
|
||||
_allocator->_allocatedOffset = _allocator->_allocatedOffset > _originalOffset ? _originalOffset : _allocator->_allocatedOffset;
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_allocator->_activeScopeCount--;
|
||||
#endif
|
||||
}
|
||||
@@ -51,7 +51,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
||||
private nuint _reserveCapacity;
|
||||
private nuint _committedSize;
|
||||
private nuint _allocatedOffset;
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
private uint _activeScopeCount;
|
||||
#endif
|
||||
|
||||
@@ -70,7 +70,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
||||
|
||||
_baseAddress = (byte*)Mmap(null, _reserveCapacity, VirtualAllocationFlags.Reserve);
|
||||
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
_activeScopeCount = 0;
|
||||
#endif
|
||||
}
|
||||
@@ -96,7 +96,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
|
||||
/// </remarks>
|
||||
public void* Allocate(nuint size, nuint alignment, AllocationOption option = AllocationOption.None)
|
||||
{
|
||||
#if ENABLE_SAFETY_CHECKS
|
||||
#if MHP_ENABLE_SAFETY_CHECKS
|
||||
if (_activeScopeCount == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Allocations can only be made within an active memory scope.");
|
||||
|
||||
Reference in New Issue
Block a user