feat(allocation): add debug layer for allocation tracking

Adds a debug layer for memory allocation tracking, enabled in DEBUG builds via ENABLE_DEBUG_LAYER. When active, allocations and reallocations in FreeListAllocator store stack traces and maintain linked lists of allocation headers for enhanced debugging. Memory is optionally cleared on allocation, and debug metadata is properly cleaned up on free. Updates allocation logic to support debug and non-debug modes. Also updates AssemblyVersion to 1.5.4 and revises Program.cs to use the new allocation manager initialization and FreeList allocator.
This commit is contained in:
2026-03-25 18:15:04 +09:00
parent a32b0668de
commit e1c9a3781b
3 changed files with 88 additions and 13 deletions

View File

@@ -1,3 +1,7 @@
#if DEBUG
#define ENABLE_DEBUG_LAYER
#endif
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -338,7 +342,7 @@ public static unsafe class AllocationManager
for (var i = 0; i < s_stackCount; i++) for (var i = 0; i < s_stackCount; i++)
{ {
Free(s_pStackBuffers[i]); Munmap(s_pStackBuffers[i], s_threadLocalStackDefaultSize);
} }
} }
} }
@@ -366,6 +370,33 @@ public static unsafe class AllocationManager
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
{ {
var selfPtr = (FreeListAllocator*)instance; 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);
return user;
#else
var ptr = selfPtr->_freeList.Allocate(size, alignment, allocationOption); var ptr = selfPtr->_freeList.Allocate(size, alignment, allocationOption);
if (ptr == null) if (ptr == null)
{ {
@@ -375,6 +406,7 @@ public static unsafe class AllocationManager
*pHandle = AddAllocation(ptr); *pHandle = AddAllocation(ptr);
return ptr; return ptr;
#endif
} }
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle)
@@ -385,6 +417,41 @@ public static unsafe class AllocationManager
} }
var selfPtr = (FreeListAllocator*)instance; 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);
return newUser;
#else
var newPtr = selfPtr->_freeList.Allocate(newSize, alignment, allocationOption); var newPtr = selfPtr->_freeList.Allocate(newSize, alignment, allocationOption);
if (newPtr == null) if (newPtr == null)
{ {
@@ -398,6 +465,7 @@ public static unsafe class AllocationManager
*pHandle = AddAllocation(newPtr); *pHandle = AddAllocation(newPtr);
return newPtr; return newPtr;
#endif
} }
private static bool IsValid(void* instance, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
@@ -408,7 +476,14 @@ public static unsafe class AllocationManager
private static void Free(void* instance, void* ptr, MemoryHandle handle) private static void Free(void* instance, void* ptr, MemoryHandle handle)
{ {
var selfPtr = (FreeListAllocator*)instance; 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);
#else
selfPtr->_freeList.Free(ptr); selfPtr->_freeList.Free(ptr);
#endif
RemoveAllocation(handle); RemoveAllocation(handle);
} }
@@ -430,11 +505,10 @@ public static unsafe class AllocationManager
private static ConcurrentSlotMap<IntPtr> s_allocations = null!; private static ConcurrentSlotMap<IntPtr> s_allocations = null!;
public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue);
private static bool s_initialized; private static bool s_initialized;
private static nuint s_threadLocalStackDefaultSize;
internal static nuint s_threadLocalStackDefaultSize; public static readonly MemoryHandle MagicHandle = new MemoryHandle(int.MinValue, int.MinValue);
/// <summary> /// <summary>
/// Gets the number of live tracked heap allocations. /// Gets the number of live tracked heap allocations.

View File

@@ -7,7 +7,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<AssemblyVersion>1.5.3</AssemblyVersion> <AssemblyVersion>1.5.4</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl> <PackageProjectUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</PackageProjectUrl>
<RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl> <RepositoryUrl>https://git.personalnas.com/Misaki/Misaki.HighPerformance.git</RepositoryUrl>

View File

@@ -32,13 +32,14 @@ using Misaki.HighPerformance.LowLevel.Collections;
// } // }
//} //}
using var pool = new MemoryPool<VirtualStack, VirtualStack.CreationOpts>(new VirtualStack.CreationOpts() { reserveCapacity = 1024 * 1024 }); var opts = new AllocationManagerInitOpts
using var scope = pool.Allocator.CreateScope(pool.AllocationHandle);
var arr = new UnsafeArray<int>(1000, scope.AllocationHandle);
for (var i = 0; i < arr.Length; i++)
{ {
Console.WriteLine(arr[i]); ArenaCapacity = 1024 * 1024,
} StackCapacity = 1024 * 1024,
FreeListConcurrencyLevel = 1
};
arr.Dispose(); AllocationManager.Initialize(opts);
var arr = new UnsafeArray<int>(10, Allocator.FreeList);
AllocationManager.Dispose();