#if MHP_ENABLE_SAFETY_CHECKS
using Misaki.HighPerformance.Collections;
#endif
using System.Diagnostics;
using System.Runtime.CompilerServices;
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 AllocationManagerDesc
{
public required nuint ArenaCapacity
{
get; init;
}
public required nuint StackCapacity
{
get; init;
}
public required nuint FreeListChunkSize
{
get; init;
}
public required nuint FreeListDefaultAlignment
{
get; init;
}
public required int FreeListConcurrencyLevel
{
get; init;
}
public static AllocationManagerDesc Default => new AllocationManagerDesc
{
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);
}
}
private class ThreadLocalStackPool
{
public MemoryPool pool;
public ThreadLocalStackPool(nuint stackCapacity)
{
pool = new MemoryPool(new VirtualStack.CreationOptions
{
reserveCapacity = stackCapacity
});
}
~ThreadLocalStackPool()
{
pool.Dispose();
}
}
internal static MemoryPool s_arenaAllocator;
internal static MemoryPool s_freeListAllocator;
internal static HeapAllocator* s_pHeapAllocator;
[ThreadStatic]
private static ThreadLocalStackPool? t_stackAllocator;
#if MHP_ENABLE_SAFETY_CHECKS
private static ConcurrentSlotMap s_allocations = null!;
private static long s_totalAllocatedMemory;
#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
public static nuint TotalAllocatedMemory =>
#if MHP_ENABLE_SAFETY_CHECKS
(nuint)s_totalAllocatedMemory;
#else
0;
#endif
private static volatile bool s_initialized;
private static nuint s_threadLocalStackSize;
private static SpinLock s_stackLocker = new SpinLock(false);
public static void Initialize(AllocationManagerDesc 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.");
t_stackAllocator ??= new ThreadLocalStackPool(s_threadLocalStackSize);
return t_stackAllocator.pool.Allocator.CreateScope(t_stackAllocator.pool.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
};
Interlocked.Add(ref s_totalAllocatedMemory, (long)size);
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, out var info);
Interlocked.Add(ref s_totalAllocatedMemory, -(long)info.Size);
}
else
{
if (s_allocations.TryGetElement(handle.ID, handle.Generation, out var oldInfo))
{
var diff = newSize - oldInfo.Size;
Interlocked.Add(ref s_totalAllocatedMemory, (long)diff);
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.");
if (s_allocations.Remove(handle.ID, handle.Generation, out var info))
{
Interlocked.Add(ref s_totalAllocatedMemory, -(long)info.Size);
return true;
}
return false;
#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
}
///
/// 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_pHeapAllocator != null)
{
Free(s_pHeapAllocator);
s_pHeapAllocator = null;
}
}
}