Files
Misaki.HighPerformance/Misaki.HighPerformance.LowLevel/Buffer/VirtualStack.cs
Misaki 69b054e81d feat(lowlevel): add VirtualStack, update allocators, docs
Introduce VirtualStack allocator, refactor memory management to use virtual memory stacks, and update documentation.

Added VirtualStack as a new stack allocator using virtual memory, replaced Stack with VirtualStack in allocation manager and related APIs, and updated TempJobAllocator to use VirtualArena. Introduced AllocationManagerInitOpts for allocator configuration. Replaced ENABLE_COLLECTION_CHECKS with ENABLE_SAFETY_CHECKS for safety checks. Removed Result.cs and updated project files and examples. Added comprehensive README files for all major packages and improved root documentation.

BREAKING CHANGE: Stack allocator replaced by VirtualStack; TempJobAllocator and AllocationManager initialization signatures changed; Result types removed.
2026-03-19 15:38:23 +09:00

181 lines
5.2 KiB
C#

using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.LowLevel.Buffer;
public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.CreationOpts>
{
private const nuint _PAGE_SIZE = 64 * 1024;
public struct CreationOpts
{
public nuint reserveCapacity;
}
public static VirtualStack Create(in CreationOpts opts)
{
return new VirtualStack(opts.reserveCapacity);
}
public readonly ref struct Scope : IDisposable
{
private readonly VirtualStack* _allocator;
private readonly AllocationHandle _handle;
private readonly nuint _originalOffset;
public readonly AllocationHandle AllocationHandle => _handle;
internal Scope(VirtualStack* allocator, AllocationHandle handle)
{
_allocator = allocator;
_handle = handle;
_originalOffset = allocator->_allocatedOffset;
#if ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount++;
#endif
}
public void Dispose()
{
if (_allocator != null)
{
_allocator->_allocatedOffset = _allocator->_allocatedOffset > _originalOffset ? _originalOffset : _allocator->_allocatedOffset;
#if ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount--;
#endif
}
}
}
private byte* _baseAddress;
private nuint _reserveCapacity;
private nuint _committedSize;
private nuint _allocatedOffset;
#if ENABLE_SAFETY_CHECKS
private uint _activeScopeCount;
#endif
internal readonly byte* Buffer => _baseAddress;
internal nuint Offset
{
readonly get => _allocatedOffset;
set => _allocatedOffset = value;
}
public VirtualStack(nuint reserveCapacity)
{
_reserveCapacity = (reserveCapacity + _PAGE_SIZE - 1) & ~(_PAGE_SIZE - 1);
_committedSize = 0;
_allocatedOffset = 0;
_baseAddress = (byte*)Mmap(null, _reserveCapacity, VirtualAllocationFlags.Reserve);
#if ENABLE_SAFETY_CHECKS
_activeScopeCount = 0;
#endif
}
/// <summary>
/// Creates a new scope instance associated with the current stack context.
/// </summary>
/// <remarks>
/// The instance of <see cref="VirtualStack"/> must be pinned or allocated on the native heap to ensure that the pointer remains valid for the lifetime of the scope.
/// </remarks>
/// <returns>A <see cref="Scope"/> object that represents a scope tied to this stack.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Scope CreateScope(AllocationHandle handle)
{
return new Scope((VirtualStack*)Unsafe.AsPointer(ref this), handle);
}
/// <summary>
/// Allocates a block of memory of the specified size and alignment.
/// </summary>
/// <remarks>
/// This is not thread-safe. It is designed for single-threaded or thread-local contexts.
/// </remarks>
public void* Allocate(nuint size, nuint alignment, AllocationOption option = AllocationOption.None)
{
#if ENABLE_SAFETY_CHECKS
if (_activeScopeCount == 0)
{
throw new InvalidOperationException("Allocations can only be made within an active memory scope.");
}
#endif
if (size == 0)
{
return null;
}
if ((alignment & (alignment - 1)) != 0)
{
throw new ArgumentException("Alignment must be a power of two.", nameof(alignment));
}
// Align the requested offset
var alignedOffset = (_allocatedOffset + alignment - 1) & ~(alignment - 1);
var newAllocatedOffset = alignedOffset + size;
if (newAllocatedOffset > _reserveCapacity)
{
return null; // Out of reserved space
}
if (newAllocatedOffset > _committedSize)
{
var sizeToCommit = newAllocatedOffset - _committedSize;
// Align the commit size to the 64KB OS Page Size
sizeToCommit = (sizeToCommit + _PAGE_SIZE - 1) & ~(_PAGE_SIZE - 1);
var commitAddress = _baseAddress + _committedSize;
var result = Mmap(commitAddress, sizeToCommit, VirtualAllocationFlags.Commit);
if (result == null)
{
return null; // Out of physical RAM
}
_committedSize += sizeToCommit;
}
var userPtr = _baseAddress + alignedOffset;
_allocatedOffset = newAllocatedOffset;
if (option.HasFlag(AllocationOption.Clear))
{
MemClear(userPtr, size);
}
return userPtr;
}
/// <summary>
/// Resets the internal offset to its initial position, keeping the committed physical memory intact for future reuse.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset()
{
_allocatedOffset = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly void Free(void* ptr)
{
}
public void Dispose()
{
if (_baseAddress != null)
{
Munmap(_baseAddress, _reserveCapacity);
_baseAddress = null;
_reserveCapacity = 0;
_committedSize = 0;
_allocatedOffset = 0;
}
}
}