namespace UnmanagedMMU { using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; using UnmanagedMMU.Allocators; using UnmanagedMMU.Diagnostics; using UnmanagedMMU.Handles; using UnmanagedMMU.Handles.Internal; /// /// Represents configurable alignment requirements for memory segments and allocations. /// Values are powers of 2 and reflect common hardware requirements (SIMD, cache lines, native pointer size). /// public enum SegmentAlignment { /// /// 8-byte alignment. Minimum for 64-bit pointers and primitives (long, double). /// Aligned8 = 8, /// /// 16-byte alignment. Required for Vector128 (SSE/NEON). /// Common default for general-purpose SIMD workloads. /// Aligned16 = 16, /// /// 32-byte alignment. Required for Vector256 (AVX). /// Recommended default for SIMD-heavy applications. /// Aligned32 = 32, /// /// 64-byte alignment. Matches standard CPU cache-line size. /// Ensures segment bases align to cache line boundaries, minimizing cache-line splits. /// Aligned64 = 64, /// /// 128-byte alignment. /// Advanced optimization for specific cache-aware algorithms or AVX-512 contexts. /// Aligned128 = 128 } /// /// Implementation of segmented Bump-Allocator. /// /// This implementation manages fixed-sized unmanaged memory segments. /// This ensures allocations are fast and contiguous within a segment. /// Once a segment is full, a new one is automatically allocated /// public unsafe sealed class SegmentedPool : IDisposable, IUnmanagedMemoryOwner { /// /// The default size for a /// private const nuint _defaultSegmentSize = 4194304; // 4 MiB /// /// The size that a new should be /// private nuint _currentSegmentSize; /// /// The alignment that each new should be on /// private readonly nuint _segmentAlignment; /// /// Queue of free segments /// private readonly Stack _freeSegments = new(); /// /// List of active memory segments /// private readonly List _activeSegments = []; /// /// Tracks the total bytes of memory reserved from the provided /// private nuint _totalReserved = 0; /// /// Tracks the total amount of allocated bytes currently in use /// private nuint _totalUsed = 0; /// /// Internal lock, ensures thread safety while maintaining a simple interface /// private readonly Lock _lock = new(); /// /// Indicates whether this has been disposed. /// private volatile bool _disposed; /// /// Pointer to the currently in use /// private Segment* _current; /// /// Allocator interface used for all underlying unmanaged memory operations. /// private readonly IUnmanagedAllocator _allocator; /// /// Indicates if allocations from the pool should be zeroed. /// private readonly bool _zeroMemory; /// /// Represents a memory segment in the . /// /// /// /// Pointer to the start of the unmanaged memory block /// indicates the current allocation position within the segment. Each new allocation advances this offset by the number of bytes allocated. /// is the total size, in bytes, of the segment. /// Segments are managed internally by the and should not be modified directly outside of the pool. /// A is a contiguous block of unmanaged memory from which allocations are served sequentially via bump allocation. /// /// private struct Segment { /// /// Pointer to the start of the unmanaged memory block. /// public byte* Ptr; /// /// The current offset into the where the next allocation will occur. /// public nuint Offset; /// /// Total size of the in bytes. /// public nuint Size; } /// /// Readonly snapshot of the current pool state for diagnostics. /// public readonly struct PoolState { /// /// The configured segment alignment setting for this pool instance. /// This is the MINIMUM alignment requirement for the segment base. /// public nuint SegmentAlignment { get; init; } /// /// The size, in bytes, of each segment allocated by the pool. /// public nuint SegmentSize { get; init; } /// /// Total bytes currently reserved from the system. /// public nuint TotalReserved { get; init; } /// /// Total bytes currently used by allocations. /// public nuint TotalUsed { get; init; } /// /// Current active segment base address. /// public nuint CurrentBase { get; init; } /// /// Current offset within the segment. /// public nuint CurrentOffset { get; init; } /// /// Checks if the current segment base is aligned to the configured setting. /// public bool BaseAligned { get; init; } /// /// Number of active segments currently being used. /// public int ActiveSegmentCount { get; init; } /// /// Number of recycled segments available for reuse. /// public int FreeSegmentCount { get; init; } /// /// Total number of segments in the pool (Active + Free). /// public int TotalSegmentCount { get; init; } /// /// Bytes lost to alignment padding vs actual data in current segment. /// Computed as: CurrentOffset - TotalUsedBytes. /// public nuint PaddingBytes { get; init; } /// /// Memory that could be freed if Trim() is called with default args (minFreeSegments: 16). /// public nuint PotentialSavings { get; init; } public string Suggestion { get; init; } } /// /// Readonly snapshot of a specific segment. /// public readonly struct SegmentInfo { /// /// Segment index within the active list. /// public int Index { get; init; } /// /// Base address of the segment (actual unmanaged memory pointer). /// public nuint BaseAddress { get; init; } /// /// Current offset usage (bytes used since segment reset). /// public nuint UsedBytes { get; init; } /// /// Total capacity of the segment. /// public nuint Size { get; init; } /// /// Indicates if this is the currently active segment. /// public bool IsActive { get; init; } /// /// The alignment requirement for the segment base itself. /// public nuint AlignmentRequirement { get; init; } /// /// True if is a multiple of . /// public bool IsAligned { get; init; } public override string ToString() { bool aligned = IsAligned; string status = IsActive ? "CURRENT" : "INACTIVE"; double usagePercent = Size > 0 ? ((double)UsedBytes / Size) * 100 : 0; string alignStatus = aligned ? "Aligned" : "Misaligned"; return $" Segment #{Index} [{status}]\n" + $" Base Address: 0x{(nuint)BaseAddress:x}\n" + $" Size: {FormatBytes(Size)}\n" + $" Used: {FormatBytes(UsedBytes)} ({usagePercent:F2}%)\n" + $" Base Alignment: {AlignmentRequirement} bytes - {alignStatus}"; } private static string FormatBytes(nuint bytes) { if (bytes >= 1073741824) return $"{bytes / 1073741824} GiB"; if (bytes >= 1048576) return $"{bytes / 1048576} MiB"; if (bytes >= 1024) return $"{bytes / 1024} KiB"; return $"{bytes} B"; } } /// /// Initializes a new with the specified default and count /// /// Size of each segment in bytes (default 4 MiB) /// Alignment requirement for each allocated segment and . Must be a power of 2 (Default 32) /// Number of segments to pre-allocate to the pool /// When true, memory returned from the pool is zero-initialized. /// When false, memory may contain previously used data and it is the caller's responsibility to clear it if required. /// /// Thrown if is zero, or if is less than 1. /// public SegmentedPool(nuint segmentSize = _defaultSegmentSize, SegmentAlignment segmentAlignment = SegmentAlignment.Aligned32, int initialSegments = 4, bool zeroMemory = false) : this(segmentSize, segmentAlignment, initialSegments, zeroMemory, new DefaultUnmanagedAllocator()) { } /// /// Initializes a new with the specified default and count /// /// Size of each segment in bytes (default 4 MiB) /// Alignment requirement for each allocated segment. Must be a power of 2 (Default 32) /// Number of segments to pre-allocate to the pool /// When true, memory returned from the pool is zero-initialized. /// When false, memory may contain previously used data and it is the caller's responsibility to clear it if required. /// IUnmanagedAllocator instance that implements the allocator /// /// Thrown if is zero, or if is less than 1. /// internal SegmentedPool(nuint segmentSize, SegmentAlignment segmentAlignment, int initialSegments, bool zeroMemory, IUnmanagedAllocator allocator) { ArgumentOutOfRangeException.ThrowIfNegativeOrZero(segmentSize); ArgumentOutOfRangeException.ThrowIfLessThan(initialSegments, 1); _allocator = allocator; _currentSegmentSize = segmentSize; _segmentAlignment = (nuint)segmentAlignment; _zeroMemory = zeroMemory; Segment* seg; // Pre-allocate segments for (int i = 0; i < initialSegments; i++) { seg = AllocateNewSegment(_currentSegmentSize); ZeroSegment(seg); _freeSegments.Push((IntPtr)seg); } _current = (Segment*)_freeSegments.Pop(); _activeSegments.Add((IntPtr)_current); } /// /// Gets the number of free segments available for use in the pool. /// /// The number of free segments. public int FreeSegmentCount { get { lock (_lock) { return _freeSegments.Count; } } } /// /// Gets the number of segments currently in use in the pool. /// /// The number of currently active Segments. public int ActiveSegmentCount { get { lock (_lock) { return _activeSegments.Count; } } } /// /// Returns the total number of bytes that have currently been allocated. /// /// The total number of bytes that have been allocated. public nuint TotalAllocatedBytes { get { lock (_lock) { return _totalReserved; } } } /// /// Gets the total number of bytes currently in use across all active segments. /// /// The total number of bytes that are in use. public nuint TotalUsedBytes { get { lock (_lock) { return _totalUsed; } } } /// /// Gets the size, in bytes, used when allocating new instances. /// /// /// This reflects the most recently configured size and affects only future allocations. /// public nuint CurrentSegmentSize { get { return _currentSegmentSize; } } /// /// Gets a value indicating whether this instance has been disposed. /// /// True, if the current instance has been disposed of, false otherwise. /// /// Once disposed, further calls to allocation or reset methods will throw . /// public bool IsDisposed { get { return _disposed; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static nuint AlignUp(nuint value, nuint alignment) { return (value + alignment - 1) & ~(alignment - 1); } /// /// Zeroes the memory if the is configured to do so. /// Called whenever a segment becomes active for use (new or reused). /// /// Pointer to the Segment struct to initialize. [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ZeroSegment(Segment* segment) { if (_zeroMemory) { Unsafe.InitBlockUnaligned(segment->Ptr, 0, (uint)segment->Size); } } /// /// Allocates a block of unmanaged memory of size for elements of type and returns a pointer to the allocated memory. /// /// The unmanaged value type to store in the allocated memory. Must be a struct or primitive type. /// The number of elements of type to allocate. /// A pointer to the first element of the allocated unmanaged memory block. The memory is valid until the is reset or disposed. /// /// /// This allocation is performed in unmanaged memory and bypasses the .NET garbage collector. /// Accessing the memory after or has been called is undefined behavior and may lead to crashes. /// /// /// /// Thrown if is less than or equal to zero. /// /// /// Thrown if the total allocation size (count * sizeof(T)) exceeds the maximum allowable size. /// private T* Alloc(int count) where T : unmanaged { ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count); if ((nuint)count > nuint.MaxValue / (nuint)(sizeof(T))) { throw new OverflowException($"Requested allocation of {count} elements of type {typeof(T)} exceeds allowable maximum memory size."); } nuint bytes = (nuint)(count * sizeof(T)); nuint alignment = _segmentAlignment > (nuint)sizeof(T) ? _segmentAlignment : (nuint)sizeof(T); lock (_lock) { ThrowIfDisposed(); nuint currentPtr = (nuint)_current->Ptr + _current->Offset; nuint alignedPtr = AlignUp(currentPtr, alignment); nuint alignedOffset = alignedPtr - (nuint)_current->Ptr; // Check space INCLUDING padding if (alignedOffset + bytes > _current->Size) { SwitchSegment(bytes); // Recalcuate from new the base of the new segment currentPtr = (nuint)_current->Ptr; alignedPtr = AlignUp(currentPtr, alignment); alignedOffset = alignedPtr - (nuint)_current->Ptr; } T* ptr = (T*)(_current->Ptr + alignedOffset); _current->Offset = alignedOffset + bytes; _totalUsed += bytes; return ptr; } } private T* AllocateWithAlignment(int count, nuint alignment) where T : unmanaged { ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count); if ((nuint)count > nuint.MaxValue / (nuint)(sizeof(T))) { throw new OverflowException($"Requested allocation of {count} elements of type {typeof(T)} exceeds allowable maximum memory size."); } nuint bytes = (nuint)(count * sizeof(T)); lock (_lock) { ThrowIfDisposed(); nuint currentPtr = (nuint)_current->Ptr + _current->Offset; nuint alignedPtr = AlignUp(currentPtr, alignment); nuint alignedOffset = alignedPtr - (nuint)_current->Ptr; if (alignedOffset + bytes > _current->Size) { SwitchSegment(bytes); currentPtr = (nuint)_current->Ptr; alignedPtr = AlignUp(currentPtr, alignment); alignedOffset = alignedPtr - (nuint)_current->Ptr; } T* ptr = (T*)(_current->Ptr + alignedOffset); _current->Offset = alignedOffset + bytes; _totalUsed += bytes; return ptr; } } /// /// Switches to a new when the current is full. /// /// The number of bytes required for the upcoming allocation. If the current does not have enough free space, a new will be used. private void SwitchSegment(nuint requiredBytes) { Segment* segment; // Allocate fresh Segment if needed if (_freeSegments.Count == 0 || requiredBytes > _currentSegmentSize) { segment = AllocateNewSegment(requiredBytes > _currentSegmentSize ? requiredBytes : _currentSegmentSize); } else { segment = (Segment*)_freeSegments.Pop(); segment->Offset = 0; } ZeroSegment(segment); _activeSegments.Add((IntPtr)segment); _current = segment; } /// /// Allocates a new /// /// Size, in bytes, for the new . /// A pointer to the newly allocated private Segment* AllocateNewSegment(nuint size) { byte* ptr = (byte*)_allocator.AllocAligned(size, _segmentAlignment); // Allocate metadata struct with its natural alignment (8 bytes for 64-bit nuint) Segment* segment = (Segment*)_allocator.AllocAligned((nuint)sizeof(Segment), 8); segment->Ptr = ptr; segment->Offset = 0; segment->Size = size; _totalReserved += size; return segment; } /// /// Sets the current size used for subsequent allocations. /// /// /// The new segment size, in bytes, that will be used when allocating future instances. /// Must be greater than zero. /// /// /// This does not affect any that have already been allocated or are currently active. /// Only new created after calling this method will use the updated size. /// /// /// Thrown if is zero. /// public void SetSegmentSize(nuint newSize) { ThrowIfDisposed(); if (newSize == 0) { throw new ArgumentOutOfRangeException(nameof(newSize), "Segment size must be greater than zero."); } _currentSegmentSize = newSize; } /// /// Resets the current size back to the default 4 MiB /// /// /// Future allocations will revert to using this default size /// Active and free are not modified. /// public void ResetSegmentSize() { ThrowIfDisposed(); _currentSegmentSize = _defaultSegmentSize; } /// /// Allocates a block of unmanaged memory of size for elements of type and returns a handle representing the allocation. /// /// The unmanaged value type to store in the allocated memory. Must be a struct or primitive type. /// The number of elements of type to allocate. /// A representing the allocated memory. The handle is valid until either or is called on this . /// /// /// This allocation is performed in unmanaged memory and bypasses the .NET garbage collector. /// Accessing the memory after or has been called is undefined behavior. /// /// /// /// Thrown if is less than or equal to zero. /// /// /// Thrown if the total allocation size (count * sizeof(T)) exceeds the maximum allowable size. /// public IMemoryHandle Allocate(int count) where T : unmanaged { T* ptr = Alloc(count); nuint byteLength = (nuint)count * (nuint)sizeof(T); return new SegmentedMemoryHandle(ptr, byteLength, this); } /// /// Allocates a block of unmanaged memory of size for elements of type with the specified and returns a handle representing the allocation. /// /// The unmanaged value type to store in the allocated memory. Must be a struct or primitive type. /// The number of elements of type to allocate. /// The alignment to aliign the allocation to inside of the currently active /// A representing the allocated memory. The handle is valid until either or is called on this . /// /// /// This allocation is performed in unmanaged memory and bypasses the .NET garbage collector. /// Accessing the memory after or has been called is undefined behavior. /// /// /// /// Thrown if is less than or equal to zero. /// /// /// Thrown if the total allocation size (count * sizeof(T)) exceeds the maximum allowable size. /// public IMemoryHandle AllocateAligned(int count, SegmentAlignment alignment) where T : unmanaged { nuint requestedAlignment = (nuint)alignment; nuint typeSize = (nuint)sizeof(T); // Ensure alignment is at least the size of T (never under-align for types) nuint effectiveAlignment = requestedAlignment < typeSize ? typeSize : requestedAlignment; T* ptr = AllocateWithAlignment(count, effectiveAlignment); nuint byteLength = (nuint)count * (nuint)sizeof(T); return new SegmentedMemoryHandle(ptr, byteLength, this); } /// /// Frees unused in the free pool, reducing unmanaged memory usage. /// /// Minimum number of free segments to retain for future allocations. Defaults to 16. /// /// Segments beyond the retained count will have their unmanaged memory released back to the system. /// /// /// Thrown if is negative. /// public void Trim(int minFreeSegments = 16) { ThrowIfDisposed(); ArgumentOutOfRangeException.ThrowIfNegative(minFreeSegments, nameof(minFreeSegments)); lock (_lock) { while (_freeSegments.Count > minFreeSegments) { var ip = _freeSegments.Pop(); Segment* segment = (Segment*)ip; // Free the unmanaged memory _allocator.FreeAligned(segment->Ptr, _segmentAlignment); _totalReserved -= segment->Size; _allocator.FreeAligned(segment, 8); } } } /// /// Resets the allocator, returning all active segments to the free pool. /// /// If true, trims the in the free pool. public void Reset(bool trim = false) { ThrowIfDisposed(); lock (_lock) { foreach (var ip in _activeSegments) { Segment* segment = (Segment*)ip; segment->Offset = 0; _freeSegments.Push(ip); // Zero memory if requested ZeroSegment(segment); } _activeSegments.Clear(); _totalUsed = 0; if (_freeSegments.Count > 0) { _current = (Segment*)_freeSegments.Pop(); _activeSegments.Add((IntPtr)_current); } else { // This should not be hit in normal circumstances as we always have _current _current = AllocateNewSegment(_currentSegmentSize); _activeSegments.Add((IntPtr)_current); // Zero newly allocated segment if requested ZeroSegment(_current); } // Optionally trim excess free segments after reset if (trim) { Trim(); } } } /// /// Frees /// /// /// void IUnmanagedMemoryOwner.Free(IOwnedHandle handle) { ThrowIfDisposed(); if (handle.Pointer == null) { return; } if (handle.GetOwner() != this) { throw new InvalidOperationException( "Attempted to free a handle from a different allocator pool."); } // } /// /// Releases all unmanaged memory allocated by the and clears internal state. /// After calling this method, the pool can no longer be used for allocations. /// /// /// After disposal, all allocations become invalid and any further operations will throw . /// This method is thread-safe and may be called multiple times safely. /// public void Dispose() { lock (_lock) { if (_disposed) { return; } // Free active segments foreach (var ip in _activeSegments) { Segment* segment = (Segment*)ip; _allocator.FreeAligned(segment->Ptr, _segmentAlignment); _allocator.FreeAligned(segment, 8); } // Free free segments foreach (var ip in _freeSegments) { Segment* segment = (Segment*)ip; _allocator.FreeAligned(segment->Ptr, _segmentAlignment); _allocator.FreeAligned(segment, 8); } _activeSegments.Clear(); _freeSegments.Clear(); _current = null; _totalReserved = 0; _totalUsed = 0; _disposed = true; } } /// /// Throws an if the has already been disposed. /// /// /// Thrown when this instance is no longer valid for use. /// private void ThrowIfDisposed() { ObjectDisposedException.ThrowIf(_disposed, this); } /// /// Gets a snapshot of the current pool state for diagnostics. /// Thread-safe and produces no garbage. /// /// A containing current pool metrics. public PoolState GetPoolState() { ThrowIfDisposed(); lock (_lock) { nuint alignment = _segmentAlignment; nuint basePtr = (nuint)_current->Ptr; int active = _activeSegments.Count; int free = _freeSegments.Count; int total = active + free; // 1. Calculate Padding (Alignment Overhead) for Current Segment // This is dynamic: Offset tracks total bytes written, TotalUsed tracks actual allocation bytes nuint padding = _current->Offset - _totalUsed; // 2. Calculate Potential Savings (Trim Projection) nuint potentialSavings = 0; if (free > 16) { int excess = free - 16; potentialSavings = (nuint)excess * _currentSegmentSize; } return new PoolState { SegmentAlignment = alignment, SegmentSize = _currentSegmentSize, TotalReserved = _totalReserved, TotalUsed = _totalUsed, CurrentBase = basePtr, CurrentOffset = _current->Offset, BaseAligned = (basePtr & (alignment - 1)) == 0, ActiveSegmentCount = active, FreeSegmentCount = free, TotalSegmentCount = total, PaddingBytes = padding, PotentialSavings = potentialSavings }; } } /// /// Generates a diagnostic report for the pool. /// Thread-safe and produces no garbage. /// /// A formatted diagnostic string. public string GetDiagnosticReport() { PoolState state = GetPoolState(); DiagnosticConfig config = new() { SegmentSize = _currentSegmentSize, TotalReserved = _totalReserved }; string suggestion = SegmentedPoolDiagnostics.GenerateSuggestions(state, config); state = state with { Suggestion = suggestion }; return SegmentedPoolDiagnostics.GenerateReport(state); } /// /// Helper method to construct a from raw segment data. /// /// Pointer to the segment to inspect. /// Pointer to the currently active segment for comparison. /// The logical index of the segment in the list. /// A populated struct. private SegmentInfo CreateSegmentInfo(Segment* segment, Segment* current, int index) { nuint alignment = _segmentAlignment; nuint ptr = (nuint)segment->Ptr; bool isAligned = (ptr & (alignment - 1)) == 0; return new SegmentInfo { Index = index, BaseAddress = ptr, UsedBytes = segment->Offset, Size = segment->Size, IsActive = (segment == current), AlignmentRequirement = alignment, IsAligned = isAligned }; } /// /// Gets information about the currently active segment. /// This is the primary diagnostic view for memory usage within the active segment. /// /// A for the current segment, or null if disposed. public SegmentInfo GetCurrentSegmentInfo() { ThrowIfDisposed(); lock (_lock) { return CreateSegmentInfo(_current, _current, 0); } } /// /// Gets a list of all segment details for deep diagnostics. /// Includes both active and free segments. /// /// A list of containing all segments. public List GetAllSegmentInfos() { ThrowIfDisposed(); lock (_lock) { var totalSegments = _activeSegments.Count + _freeSegments.Count; var result = new List(totalSegments); int currentIndex = 0; for (int i = 0; i < _activeSegments.Count; i++) { IntPtr ip = _activeSegments[i]; Segment* segment = (Segment*)ip; result.Add(CreateSegmentInfo(segment, _current, currentIndex)); currentIndex++; } foreach (var ip in _freeSegments) { Segment* segment = (Segment*)ip; result.Add(CreateSegmentInfo(segment, _current, currentIndex++)); } return result; } } } }