#if MHP_ENABLE_SAFETY_CHECKS
using Misaki.HighPerformance.Collections;
#endif
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
namespace Misaki.HighPerformance.LowLevel.Buffer;
///
/// Holds information about a memory allocation.
///
public readonly struct AllocationInfo
{
///
/// Gets the address of the allocated memory block.
///
public IntPtr Address
{
get; init;
}
///
/// Gets the newSize of the allocation in bytes.
///
public nuint Size
{
get; init;
}
#if MHP_ENABLE_STACKTRACE
///
/// Gets the stack trace at the time of allocation for debugging purposes.
///
public StackTrace? StackTrace
{
get; init;
}
#endif
}
public readonly struct AllocationManagerInitOpts
{
public nuint ArenaCapacity
{
get; init;
}
public nuint StackCapacity
{
get; init;
}
public nuint FreeListChunkSize
{
get; init;
}
public nuint FreeListDefaultAlignment
{
get; init;
}
public int FreeListConcurrencyLevel
{
get; init;
}
public static AllocationManagerInitOpts Default => new AllocationManagerInitOpts
{
ArenaCapacity = 1024 * 1024 * 1024, // 1 GB
StackCapacity = 16 * 1024 * 1024, // 16 MB per thread
FreeListChunkSize = 64 * 1024 * 1024,
FreeListDefaultAlignment = 8,
FreeListConcurrencyLevel = Environment.ProcessorCount
};
}
///
/// Provides memory allocation management for native memory allocations, with support for tracking,
/// debugging, and custom allocation strategies.
///
public static unsafe class AllocationManager
{
internal struct HeapAllocator : IAllocator
{
private AllocationHandle _handle;
public readonly AllocationHandle Handle => _handle;
public void Init()
{
_handle = new AllocationHandle
{
State = null,
Alloc = &Allocate,
Realloc = &Reallocate,
Free = &Free
};
}
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption)
{
var ptr = AlignedAlloc(size, alignment);
if (ptr == null)
{
return null;
}
if (allocationOption.HasFlag(AllocationOption.Clear))
{
MemClear(ptr, size);
}
return ptr;
}
private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
{
var newPtr = AlignedRealloc(ptr, newSize, alignment);
if (newPtr == null)
{
return null;
}
if (allocationOption.HasFlag(AllocationOption.Clear) && newSize > oldSize)
{
var offset = (byte*)newPtr + oldSize;
var clearSize = newSize - oldSize;
MemClear(offset, clearSize);
}
return newPtr;
}
private static void Free(void* _, void* ptr)
{
AlignedFree(ptr);
}
#if MHP_ENABLE_SAFETY_CHECKS
private static bool IsValid(void* _, MemoryHandle handle)
{
return ContainsAllocation(handle);
}
#endif
}
internal static MemoryPool s_arenaAllocator;
internal static MemoryPool s_freeListAllocator;
internal static HeapAllocator* s_pHeapAllocator;
[ThreadStatic]
private static MemoryPool t_stackAllocator;
#if MHP_ENABLE_SAFETY_CHECKS
private static ConcurrentSlotMap s_allocations = null!;
#endif
///
/// Gets the number of live tracked heap allocations. Always returns 0 if MHP_ENABLE_SAFETY_CHECKS is disabled.
///
public static int LiveAllocationCount =>
#if MHP_ENABLE_SAFETY_CHECKS
s_allocations.Count;
#else
0;
#endif
private static volatile bool s_initialized;
private static nuint s_threadLocalStackSize;
private static SpinLock s_stackLocker = new SpinLock(false);
private static VirtualStack** s_ppStack;
private static int s_ppStackCount;
private static int s_ppStackCapacity;
private static void EnsureThreadLocalStackInitialize()
{
if (Unsafe.IsNullRef(ref t_stackAllocator.Allocator))
{
t_stackAllocator = new MemoryPool(new VirtualStack.CreationOptions
{
reserveCapacity = s_threadLocalStackSize
});
var token = false;
try
{
s_stackLocker.Enter(ref token);
if (s_ppStack == null)
{
s_ppStack = (VirtualStack**)Malloc((nuint)(sizeof(VirtualStack*) * Environment.ProcessorCount));
s_ppStackCapacity = Environment.ProcessorCount;
}
if (s_ppStackCount >= s_ppStackCapacity)
{
var pOld = s_ppStack;
var newCapacity = s_ppStackCapacity * 2;
var pNew = (VirtualStack**)Realloc(pOld, (nuint)(sizeof(VirtualStack*) * newCapacity));
s_ppStack = pNew;
s_ppStackCapacity = newCapacity;
}
s_ppStack[s_ppStackCount] = (VirtualStack*)Unsafe.AsPointer(ref t_stackAllocator.Allocator);
var test = s_ppStack[s_ppStackCount];
s_ppStackCount++;
}
finally
{
if (token)
{
s_stackLocker.Exit();
}
}
}
}
public static void Initialize(AllocationManagerInitOpts opts)
{
if (s_initialized)
{
return;
}
#if MHP_ENABLE_SAFETY_CHECKS
s_allocations = new ConcurrentSlotMap(256);
#endif
s_arenaAllocator = new MemoryPool(new VirtualArena.CreationOptions
{
reserveCapacity = opts.ArenaCapacity
});
s_freeListAllocator = new MemoryPool(new FreeList.CreationOptions
{
alignment = opts.FreeListDefaultAlignment,
chunkSize = opts.FreeListChunkSize,
maxConcurrencyLevel = opts.FreeListConcurrencyLevel
});
s_pHeapAllocator = (HeapAllocator*)Malloc((nuint)(sizeof(HeapAllocator)));
s_pHeapAllocator->Init();
s_threadLocalStackSize = opts.StackCapacity;
s_initialized = true;
}
///
/// Gets a reference to the allocation pHandle for the specified allocator type.
///
/// The allocator type for which to retrieve the allocation pHandle.
/// A reference to the allocation pHandle associated with the specified allocator type.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use AllocationHandle instead.")]
public static AllocationHandle GetAllocationHandle(Allocator allocator)
{
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
return allocator switch
{
Allocator.Temp => s_arenaAllocator.AllocationHandle,
Allocator.Persistent => s_pHeapAllocator->Handle,
Allocator.FreeList => s_freeListAllocator.AllocationHandle,
_ => throw new ArgumentException("Target allocator type does not support custom allocation.", nameof(allocator)),
};
}
///
/// Resets the temporary memory allocator, clearing all allocated memory.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ResetTempAllocator()
{
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
s_arenaAllocator.Allocator.Reset();
}
///
/// Creates a new thread local stack scope for managing temporary allocations within the current context.
///
/// A instance representing the newly created stack scope. The scope must be disposed when no longer needed to release allocated resources.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static VirtualStack.Scope CreateStackScope()
{
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
EnsureThreadLocalStackInitialize();
return t_stackAllocator.Allocator.CreateScope(t_stackAllocator.AllocationHandle);
}
///
/// Registers a memory allocation and returns a handle that can be used to manage or reference the allocated memory.
///
///
/// Always returns an invalid handle if MHP_ENABLE_SAFETY_CHECKS is disabled.
///
/// A pointer to the memory block to be registered. The pointer must reference a valid, allocated memory region.
/// The newSize of the memory block to be registered.
/// A MemoryHandle representing the registered allocation.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle AddAllocation(void* ptr, nuint size)
{
#if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
var info = new AllocationInfo
{
Address = (IntPtr)ptr,
Size = size,
#if MHP_ENABLE_STACKTRACE
StackTrace = new StackTrace(1, true)
#endif
};
var id = s_allocations.Add(info, out var generation);
return new MemoryHandle(id, generation);
#else
return MemoryHandle.Invalid;
#endif
}
public static void UpdateAllocation(MemoryHandle handle, void* newPtr, nuint newSize)
{
#if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
if (newPtr == null)
{
s_allocations.Remove(handle.ID, handle.Generation);
}
else
{
if (s_allocations.TryGetElement(handle.ID, handle.Generation, out var oldInfo))
{
var newInfo = oldInfo with
{
Address = (IntPtr)newPtr,
Size = newSize
};
s_allocations.UpdateElement(handle.ID, handle.Generation, newInfo);
}
}
#endif
}
///
/// Removes the memory allocation associated with the specified handle.
///
///
/// Always returns false if debug layer is disabled.
///
/// The handle representing the memory allocation to remove. The handle must be valid and previously allocated.
/// true if the allocation was successfully removed; otherwise, false.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool RemoveAllocation(MemoryHandle handle)
{
#if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
return s_allocations.Remove(handle.ID, handle.Generation, out var info);
#else
return false;
#endif
}
///
/// Attempts to retrieve the memory allocation pointer associated with the specified handle.
///
///
/// Always returns false if debug layer is disabled, and the output pointer will be set to .
///
/// The memory handle identifying the allocation to retrieve allocation.
/// When this method returns, contains the allocation information associated with the specified handle, if the allocation was found; otherwise, an uninitialized value.
/// true if the allocation was found and contains valid allocation information; otherwise, false.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetAllocation(MemoryHandle handle, out AllocationInfo info)
{
#if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
return s_allocations.TryGetElement(handle.ID, handle.Generation, out info);
#else
info = default;
return false;
#endif
}
///
/// Determines whether the specified memory handle refers to a currently tracked allocation.
///
///
/// This only validates the memory when you added the allocation via .
/// For validating memory from , use instead.
/// Always returns false if debug layer is disabled.
///
/// The memory handle to check for an associated allocation.
/// true if the allocation corresponding to the handle exists; otherwise, false.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsAllocation(MemoryHandle handle)
{
#if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
return s_allocations.Contains(handle.ID, handle.Generation);
#else
return false;
#endif
}
///
/// Gets the total newSize of all currently tracked allocations.
///
///
/// Always returns 0 if MHP_ENABLE_SAFETY_CHECKS is disabled.
///
/// The total newSize of all currently tracked allocations.
public static nuint GetTotalAllocatedMemory()
{
#if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized.");
nuint total = 0;
foreach (var allocation in s_allocations)
{
total += allocation.Size;
}
return total;
#else
return 0;
#endif
}
///
/// Disposes of the AllocationManager, freeing all allocated memory and resources.
///
public static void Dispose()
{
if (!s_initialized)
{
return;
}
s_initialized = false;
#if MHP_ENABLE_SAFETY_CHECKS
if (s_allocations.Count > 0)
{
throw new MemoryLeakException(s_allocations);
}
#endif
s_arenaAllocator.Dispose();
s_freeListAllocator.Dispose();
if (s_ppStack != null)
{
for (var i = 0; i < s_ppStackCount; i++)
{
var pStack = s_ppStack[i];
if (pStack != null)
{
pStack->Dispose();
Free(pStack);
}
}
Free(s_ppStack);
s_ppStack = null;
}
if (s_pHeapAllocator != null)
{
Free(s_pHeapAllocator);
s_pHeapAllocator = null;
}
}
}