feat(core)!: refactor safety/debug defines, remove FreeList in JobSchedular

Refactor to use MHP_ENABLE_SAFETY_CHECKS, MHP_ENABLE_STACKTRACE, and MHP_ENABLE_MIMALLOC for feature toggling. Remove FreeList allocator in JobSchedular and debug-layer code, simplifying memory management. Improve memory leak detection and reporting, update memory allocation API, and guard all safety/debug features with new defines. Update csproj files, README, and code samples to match new API and toggles. Fix and improve collection types for correct behavior with and without safety checks. The codebase is now more modular and easier to configure for different build environments.

BREAKING CHANGE: Old defines (ENABLE_SAFETY_CHECKS, ENABLE_DEBUG_LAYER, ENABLE_MIMALLOC) are replaced with MHP_* equivalents. FreeList allocator and related debug features are removed. Some APIs and behaviors have changed for safety/debug configuration.
This commit is contained in:
2026-03-30 15:21:09 +09:00
parent 8231d6df60
commit aae8e2826f
27 changed files with 310 additions and 573 deletions

View File

@@ -33,6 +33,7 @@ This package focuses on practical image decoding with low-level control over mem
```csharp ```csharp
using Misaki.HighPerformance.Image; using Misaki.HighPerformance.Image;
using var stream = File.OpenRead("image.png"); using var stream = File.OpenRead("image.png");
using ImageResult image = ImageResult.FromStream(stream); using ImageResult image = ImageResult.FromStream(stream);

View File

@@ -1,9 +1,7 @@
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Misaki.HighPerformance.Jobs; namespace Misaki.HighPerformance.Jobs;
@@ -195,8 +193,11 @@ public interface IJobScheduler
/// <summary> /// <summary>
/// Blocks the calling thread until all specified job handles have completed. /// Blocks the calling thread until all specified job handles have completed.
/// </summary> /// </summary>
/// <remarks>
/// The collection handles will be reordered in-place to move completed handles to the front.
/// </remarks>
/// <param name="handles">A collection of job handles to wait for.</param> /// <param name="handles">A collection of job handles to wait for.</param>
void WaitAll(params ReadOnlySpan<JobHandle> handles); void WaitAll(params Span<JobHandle> handles);
/// <summary> /// <summary>
/// Waits until any of the specified job handles has completed and returns the first completed handle. /// Waits until any of the specified job handles has completed and returns the first completed handle.
@@ -206,36 +207,6 @@ public interface IJobScheduler
JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles); JobHandle WaitAny(params ReadOnlySpan<JobHandle> handles);
} }
public unsafe partial class JobScheduler
{
public static int MainThreadIndex => -1;
public static TempJobAllocator* pTempAllocator;
/// <summary>
/// Gets the allocation handle for the temporary job allocator.
/// </summary>
/// <remarks>
/// You must dispose the allocation before the fourth time you call <see cref="TempJobAllocator.AdvanceFrame"/> after obtaining this handle.
/// </remarks>
public static AllocationHandle TempAllocatorHandle => pTempAllocator->Handle;
public static void InitTempAllocator(nuint capacityPerFrame)
{
pTempAllocator = (TempJobAllocator*)MemoryUtility.Malloc((nuint)sizeof(TempJobAllocator));
pTempAllocator->Initialize(capacityPerFrame);
}
public static void ReleaseTempAllocator()
{
if (pTempAllocator != null)
{
pTempAllocator->Dispose();
MemoryUtility.Free(pTempAllocator);
}
}
}
/// <summary> /// <summary>
/// Provides a mechanism for scheduling and executing jobs across multiple worker threads. /// Provides a mechanism for scheduling and executing jobs across multiple worker threads.
/// </summary> /// </summary>
@@ -248,7 +219,6 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
private const int _STATE_MASK = 0xFFFF; private const int _STATE_MASK = 0xFFFF;
private const int _RC_ONE = 0x10000; private const int _RC_ONE = 0x10000;
private FreeList _jobDataAllocator;
private readonly ConcurrentSlotMap<JobInfo> _jobInfoPool; private readonly ConcurrentSlotMap<JobInfo> _jobInfoPool;
private readonly ConcurrentQueue<JobHandle> _jobQueue; private readonly ConcurrentQueue<JobHandle> _jobQueue;
private readonly WorkerThread[] _workerThreads; private readonly WorkerThread[] _workerThreads;
@@ -272,7 +242,6 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
{ {
var workerCount = Math.Max(1, threadCount); var workerCount = Math.Max(1, threadCount);
_jobDataAllocator = new(8, maxConcurrencyLevel: workerCount + 1);
_jobInfoPool = new(); _jobInfoPool = new();
_jobQueue = new(); _jobQueue = new();
@@ -395,7 +364,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
Interlocked.Decrement(ref depJobInfo.dependentCount); Interlocked.Decrement(ref depJobInfo.dependentCount);
// Cleanup and fail // Cleanup and fail
_jobDataAllocator.Free(jobInfo.pJobData); NativeMemory.Free(jobInfo.pJobData);
return JobHandle.Invalid; return JobHandle.Invalid;
} }
@@ -538,7 +507,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
dependentsToNotify[i] = new JobHandle(info.dependentsID[i], info.dependentsGeneration[i]); dependentsToNotify[i] = new JobHandle(info.dependentsID[i], info.dependentsGeneration[i]);
} }
_jobDataAllocator.Free(info.pJobData); NativeMemory.Free(info.pJobData);
_jobInfoPool.Remove(handle.ID, handle.Generation); _jobInfoPool.Remove(handle.ID, handle.Generation);
Interlocked.Decrement(ref _totalJobCount); Interlocked.Decrement(ref _totalJobCount);
@@ -557,16 +526,13 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
public JobHandle Schedule<T>(ref readonly T job, int threadIndex, JobHandle dependency) public JobHandle Schedule<T>(ref readonly T job, int threadIndex, JobHandle dependency)
where T : unmanaged, IJob where T : unmanaged, IJob
{ {
var pJobData = _jobDataAllocator.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>()); var pJobData = NativeMemory.Alloc((nuint)sizeof(T));
if (pJobData == null) if (pJobData == null)
{ {
return JobHandle.Invalid; return JobHandle.Invalid;
} }
fixed (T* pJob = &job) Unsafe.Copy(pJobData, in job);
{
MemoryUtility.MemCpy(pJobData, pJob, MemoryUtility.SizeOf<T>());
}
var jobInfo = new JobInfo var jobInfo = new JobInfo
{ {
@@ -597,7 +563,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency) public JobHandle ScheduleParallelFor<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency)
where T : unmanaged, IJobParallelFor where T : unmanaged, IJobParallelFor
{ {
var pJobData = _jobDataAllocator.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>()); var pJobData = NativeMemory.Alloc((nuint)sizeof(T));
if (pJobData == null) if (pJobData == null)
{ {
return JobHandle.Invalid; return JobHandle.Invalid;
@@ -605,7 +571,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
fixed (T* pJob = &job) fixed (T* pJob = &job)
{ {
MemoryUtility.MemCpy(pJobData, pJob, MemoryUtility.SizeOf<T>()); NativeMemory.Copy(pJobData, pJob, (nuint)sizeof(T));
} }
var optimalBatchSize = Math.Max(1, batchSize); var optimalBatchSize = Math.Max(1, batchSize);
@@ -645,7 +611,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency) public JobHandle ScheduleParallel<T>(ref readonly T job, int totalIteration, int batchSize, int threadIndex, JobHandle dependency)
where T : unmanaged, IJobParallel where T : unmanaged, IJobParallel
{ {
var pJobData = _jobDataAllocator.Allocate(MemoryUtility.SizeOf<T>(), MemoryUtility.AlignOf<T>()); var pJobData = NativeMemory.Alloc((nuint)sizeof(T));
if (pJobData == null) if (pJobData == null)
{ {
return JobHandle.Invalid; return JobHandle.Invalid;
@@ -653,7 +619,7 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
fixed (T* pJob = &job) fixed (T* pJob = &job)
{ {
MemoryUtility.MemCpy(pJobData, pJob, MemoryUtility.SizeOf<T>()); NativeMemory.Copy(pJobData, pJob, (nuint)sizeof(T));
} }
var optimalBatchSize = Math.Max(1, batchSize); var optimalBatchSize = Math.Max(1, batchSize);
@@ -753,30 +719,27 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
} }
} }
public void WaitAll(params ReadOnlySpan<JobHandle> handles) public void WaitAll(params Span<JobHandle> handles)
{ {
if (handles.Length == 0) if (handles.Length == 0)
{ {
return; return;
} }
using var orderedHandles = new UnsafeArray<JobHandle>(handles.Length, Allocator.Temp);
var spin = new SpinWait(); var spin = new SpinWait();
var completedCount = 0; var completedCount = 0;
orderedHandles.CopyFrom(handles);
while (true) while (true)
{ {
for (var i = completedCount; i < orderedHandles.Length; i++) for (var i = completedCount; i < handles.Length; i++)
{ {
var handle = orderedHandles[i]; var handle = handles[i];
if (!_jobInfoPool.Contains(handle.ID, handle.Generation)) if (!_jobInfoPool.Contains(handle.ID, handle.Generation))
{ {
// Move completed handle to the front (completedCount index) to avoid checking it again. // Move completed handle to the front (completedCount index) to avoid checking it again.
var temp = orderedHandles[completedCount]; var temp = handles[completedCount];
orderedHandles[completedCount] = handle; handles[completedCount] = handle;
orderedHandles[i] = temp; handles[i] = temp;
completedCount++; completedCount++;
} }
@@ -825,7 +788,6 @@ public sealed unsafe partial class JobScheduler : IJobScheduler, IDisposable
_jobInfoPool.Clear(); _jobInfoPool.Clear();
_jobQueue.Clear(); _jobQueue.Clear();
_jobDataAllocator.Dispose();
_workSignal.Dispose(); _workSignal.Dispose();
_cts.Dispose(); _cts.Dispose();

View File

@@ -5,14 +5,12 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<AssemblyVersion>1.5.3</AssemblyVersion> <AssemblyVersion>1.5.5</AssemblyVersion>
<Version>$(AssemblyVersion)</Version> <Version>$(AssemblyVersion)</Version>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<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>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>contentFiles</ContentTargetFolders>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -24,15 +22,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Misaki.HighPerformance.LowLevel\Misaki.HighPerformance.LowLevel.csproj" />
<ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" /> <ProjectReference Include="..\Misaki.HighPerformance\Misaki.HighPerformance.csproj" />
</ItemGroup> </ItemGroup>
</Project>
<ItemGroup>
<Content Include="**\*.cs" Exclude="obj\**;bin\**">
<Pack>true</Pack>
<PackagePath>contentFiles\cs\any\Misaki.HighPerformance.Jobs\</PackagePath>
<PackageCopyToOutput>false</PackageCopyToOutput>
<BuildAction>Compile</BuildAction>
</Content>
</ItemGroup></Project>

View File

@@ -37,8 +37,32 @@ This package provides job contracts, scheduling, worker threads, dependency hand
```csharp ```csharp
using Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.Jobs;
// Implement IJob, IJobParallelFor, or IJobParallel and schedule the work through JobScheduler. public struct AddJob : IJob
// The scheduler copies job data internally and tracks completion through JobHandle. {
public int* pA;
public int* pB;
public int* pResult;
public void Execute(ref readonly JobExecutionContext ctx)
{
*pResult = *pA + *pB;
}
}
int a = 5;
int b = 10;
int result = 0;
var job = new AddJob
{
pA = &a,
pB = &b,
pResult = &result
};
JobHandle handle = jobScheduler.Schedule(job);
jobScheduler.Wait(handle);
``` ```
## Package reference ## Package reference

View File

@@ -1,9 +1,6 @@
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Utilities;
using System.Runtime.CompilerServices;
namespace Misaki.HighPerformance.Jobs; namespace Misaki.HighPerformance.Jobs;
#if false
public unsafe struct TempJobAllocator : IAllocator, IDisposable public unsafe struct TempJobAllocator : IAllocator, IDisposable
{ {
private const int _FRAME_LATENCY = 4; private const int _FRAME_LATENCY = 4;
@@ -23,7 +20,7 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
{ {
var memoryHandle = default(MemoryHandle); var memoryHandle = default(MemoryHandle);
_pArena = (VirtualArena*)AllocationManager.HeapAlloc((nuint)(sizeof(VirtualArena) * _FRAME_LATENCY), MemoryUtility.AlignOf<VirtualArena>(), AllocationOption.Clear, &memoryHandle); _pArena = (VirtualArena*)MemoryUtility.Malloc((nuint)(sizeof(VirtualArena) * _FRAME_LATENCY));
_currentFrameCount = 0; _currentFrameCount = 0;
_currentFrameIndex = 0; _currentFrameIndex = 0;
_memoryHandle = memoryHandle; _memoryHandle = memoryHandle;
@@ -40,31 +37,51 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
Alloc = &Allocate, Alloc = &Allocate,
Realloc = &Reallocate, Realloc = &Reallocate,
Free = &Free, Free = &Free,
#if ENABLE_SAFETY_CHECKS
IsValid = &IsValid, IsValid = &IsValid,
#else
IsValid = null,
#endif
}; };
} }
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
#if ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
var pSelf = (TempJobAllocator*)instance; var pSelf = (TempJobAllocator*)instance;
var pCurrentArena = pSelf->_pArena + pSelf->_currentFrameIndex; var pCurrentArena = pSelf->_pArena + pSelf->_currentFrameIndex;
var ptr = pCurrentArena->Allocate(size, alignment, allocationOption); var ptr = pCurrentArena->Allocate(size, alignment, allocationOption);
if (ptr == null) if (ptr == null)
{ {
#if ENABLE_SAFETY_CHECKS
*pHandle = MemoryHandle.Invalid; *pHandle = MemoryHandle.Invalid;
#endif
return null; return null;
} }
Interlocked.Increment(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]); Interlocked.Increment(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
#if ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_MAGIC_ID, pSelf->_currentFrameCount); *pHandle = new MemoryHandle(_MAGIC_ID, pSelf->_currentFrameCount);
#endif
return ptr; return ptr;
} }
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
#if ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
if (ptr == null) if (ptr == null)
{ {
return Allocate(instance, newSize, alignment, allocationOption, pHandle); return Allocate(instance, newSize, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS
, pHandle
#endif
);
} }
var pSelf = (TempJobAllocator*)instance; var pSelf = (TempJobAllocator*)instance;
@@ -80,18 +97,23 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
return newPtr; return newPtr;
} }
private static void Free(void* instance, void* ptr, MemoryHandle handle) private static void Free(void* instance, void* ptr
#if ENABLE_SAFETY_CHECKS
, MemoryHandle handle
#endif
)
{ {
// The arena allocator does not free individual blocks, as it manages memory in chunks.
var pSelf = (TempJobAllocator*)instance; var pSelf = (TempJobAllocator*)instance;
Interlocked.Decrement(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]); Interlocked.Decrement(ref pSelf->_allocationsPerFrame[pSelf->_currentFrameIndex]);
} }
#if ENABLE_SAFETY_CHECKS
private static bool IsValid(void* instance, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
{ {
var pSelf = (TempJobAllocator*)instance; var pSelf = (TempJobAllocator*)instance;
return handle.ID == _MAGIC_ID && handle.Generation > pSelf->_currentFrameCount - _FRAME_LATENCY; return handle.ID == _MAGIC_ID && handle.Generation > pSelf->_currentFrameCount - _FRAME_LATENCY;
} }
#endif
public int AdvanceFrame() public int AdvanceFrame()
{ {
@@ -115,3 +137,4 @@ public unsafe struct TempJobAllocator : IAllocator, IDisposable
AllocationManager.HeapFree(_pArena, _memoryHandle); AllocationManager.HeapFree(_pArena, _memoryHandle);
} }
} }
#endif

View File

@@ -1,22 +1,22 @@
global using static Misaki.HighPerformance.LowLevel.Utilities.MemoryUtility; global using static Misaki.HighPerformance.LowLevel.Utilities.MemoryUtility;
global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption global using unsafe AllocFunc = delegate*<void*, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle* , Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*
#endif #endif
, void*>; , void*>;
global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption global using unsafe ReallocFunc = delegate*<void*, void*, nuint, nuint, nuint, Misaki.HighPerformance.LowLevel.Buffer.AllocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle* , Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle*
#endif #endif
, void*>; , void*>;
global using unsafe FreeFunc = delegate*<void*, void* global using unsafe FreeFunc = delegate*<void*, void*
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle , Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle
#endif #endif
, void>; , void>;
global using unsafe IsValidFunc = delegate*<void* global using unsafe IsValidFunc = delegate*<void*
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle , Misaki.HighPerformance.LowLevel.Buffer.MemoryHandle
#endif #endif
, bool>; , bool>;

View File

@@ -1,11 +1,9 @@
#define ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
#if ENABLE_SAFETY_CHECKS
using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.Collections;
#endif #endif
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_STACKTRACE
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
#endif #endif
@@ -32,11 +30,11 @@ public readonly struct AllocationInfo
get; init; get; init;
} }
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_STACKTRACE
/// <summary> /// <summary>
/// Gets the stack trace at the time of allocation for debugging purposes. /// Gets the stack trace at the time of allocation for debugging purposes.
/// </summary> /// </summary>
public GCHandle StackTrace public StackTrace? StackTrace
{ {
get; init; get; init;
} }
@@ -67,18 +65,6 @@ public readonly struct AllocationManagerInitOpts
/// </summary> /// </summary>
public static unsafe class AllocationManager public static unsafe class AllocationManager
{ {
#if ENABLE_DEBUG_LAYER
[StructLayout(LayoutKind.Sequential)]
private struct AllocationHeader
{
public AllocationHeader* prev;
public AllocationHeader* next;
public void* basePtr; // pointer returned by underlying allocator
public nuint userSize; // requested size from the user
public GCHandle stackHandle; // GCHandle to managed StackTrace
}
#endif
private struct ArenaAllocator : IAllocator, IDisposable private struct ArenaAllocator : IAllocator, IDisposable
{ {
private const int _ARENA_MAGIC_ID = -3941029; private const int _ARENA_MAGIC_ID = -3941029;
@@ -99,7 +85,7 @@ public static unsafe class AllocationManager
Alloc = &Allocate, Alloc = &Allocate,
Realloc = &Reallocate, Realloc = &Reallocate,
Free = null, Free = null,
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
IsValid = &IsValid IsValid = &IsValid
#else #else
IsValid = null IsValid = null
@@ -110,7 +96,7 @@ public static unsafe class AllocationManager
} }
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle , MemoryHandle* pHandle
#endif #endif
) )
@@ -119,20 +105,20 @@ public static unsafe class AllocationManager
var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption); var ptr = selfPtr->_arena.Allocate(size, alignment, allocationOption);
if (ptr == null) if (ptr == null)
{ {
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
*pHandle = MemoryHandle.Invalid; *pHandle = MemoryHandle.Invalid;
#endif #endif
return null; return null;
} }
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick); *pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
#endif #endif
return ptr; return ptr;
} }
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle , MemoryHandle* pHandle
#endif #endif
) )
@@ -140,7 +126,7 @@ public static unsafe class AllocationManager
if (ptr == null) if (ptr == null)
{ {
return Allocate(instance, newSize, alignment, allocationOption return Allocate(instance, newSize, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, pHandle , pHandle
#endif #endif
); );
@@ -155,13 +141,13 @@ public static unsafe class AllocationManager
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick); *pHandle = new MemoryHandle(_ARENA_MAGIC_ID, selfPtr->_currentTick);
#endif #endif
return newPtr; return newPtr;
} }
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
private static bool IsValid(void* instance, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
{ {
var selfPtr = (ArenaAllocator*)instance; var selfPtr = (ArenaAllocator*)instance;
@@ -195,7 +181,7 @@ public static unsafe class AllocationManager
Alloc = &Allocate, Alloc = &Allocate,
Realloc = &Reallocate, Realloc = &Reallocate,
Free = &Free, Free = &Free,
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
IsValid = &IsValid IsValid = &IsValid
#else #else
IsValid = null IsValid = null
@@ -204,13 +190,13 @@ public static unsafe class AllocationManager
} }
private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption private static void* Allocate(void* _, nuint size, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle , MemoryHandle* pHandle
#endif #endif
) )
{ {
return HeapAlloc(size, alignment, allocationOption return HeapAlloc(size, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, pHandle , pHandle
#else #else
, default , default
@@ -219,7 +205,7 @@ public static unsafe class AllocationManager
} }
private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption private static void* Reallocate(void* _, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle , MemoryHandle* pHandle
#endif #endif
) )
@@ -227,7 +213,7 @@ public static unsafe class AllocationManager
if (ptr == null) if (ptr == null)
{ {
return Allocate(null, newSize, alignment, allocationOption return Allocate(null, newSize, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, pHandle , pHandle
#endif #endif
); );
@@ -242,27 +228,27 @@ public static unsafe class AllocationManager
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
HeapFree(ptr HeapFree(ptr
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, *pHandle , *pHandle
#else #else
, default , default
#endif #endif
); );
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
*pHandle = newHandle; *pHandle = newHandle;
#endif #endif
return newPtr; return newPtr;
} }
private static void Free(void* _, void* ptr private static void Free(void* _, void* ptr
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle handle , MemoryHandle handle
#endif #endif
) )
{ {
HeapFree(ptr HeapFree(ptr
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, handle , handle
#else #else
, default , default
@@ -270,7 +256,7 @@ public static unsafe class AllocationManager
); );
} }
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
private static bool IsValid(void* _, MemoryHandle handle) private static bool IsValid(void* _, MemoryHandle handle)
{ {
return ContainsAllocation(handle); return ContainsAllocation(handle);
@@ -301,7 +287,7 @@ public static unsafe class AllocationManager
Alloc = &Allocate, Alloc = &Allocate,
Realloc = &Reallocate, Realloc = &Reallocate,
Free = null, Free = null,
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
IsValid = &IsValid IsValid = &IsValid
#else #else
IsValid = null IsValid = null
@@ -349,7 +335,7 @@ public static unsafe class AllocationManager
} }
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle , MemoryHandle* pHandle
#endif #endif
) )
@@ -359,20 +345,20 @@ public static unsafe class AllocationManager
var ptr = s_stack.Allocate(size, alignment, allocationOption); var ptr = s_stack.Allocate(size, alignment, allocationOption);
if (ptr == null) if (ptr == null)
{ {
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
*pHandle = MemoryHandle.Invalid; *pHandle = MemoryHandle.Invalid;
#endif #endif
return null; return null;
} }
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset); *pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
#endif #endif
return ptr; return ptr;
} }
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle , MemoryHandle* pHandle
#endif #endif
) )
@@ -380,7 +366,7 @@ public static unsafe class AllocationManager
if (ptr == null) if (ptr == null)
{ {
return Allocate(instance, newSize, alignment, allocationOption return Allocate(instance, newSize, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, pHandle , pHandle
#endif #endif
); );
@@ -402,7 +388,7 @@ public static unsafe class AllocationManager
} }
} }
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset); *pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
#endif #endif
return ptr; return ptr;
@@ -416,13 +402,13 @@ public static unsafe class AllocationManager
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
*pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset); *pHandle = new MemoryHandle(_STACK_MAGIC_ID, (int)s_stack.Offset);
#endif #endif
return newPtr; return newPtr;
} }
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
private static bool IsValid(void* instance, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
{ {
return handle.ID == _STACK_MAGIC_ID && handle.Generation <= (int)s_stack.Offset; return handle.ID == _STACK_MAGIC_ID && handle.Generation <= (int)s_stack.Offset;
@@ -465,7 +451,7 @@ public static unsafe class AllocationManager
Alloc = &Allocate, Alloc = &Allocate,
Realloc = &Reallocate, Realloc = &Reallocate,
Free = &Free, Free = &Free,
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
IsValid = &IsValid IsValid = &IsValid
#else #else
IsValid = null IsValid = null
@@ -474,54 +460,26 @@ public static unsafe class AllocationManager
} }
private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption private static void* Allocate(void* instance, nuint size, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle , MemoryHandle* pHandle
#endif #endif
) )
{ {
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, size);
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)
{ {
return null; return null;
} }
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
*pHandle = AddAllocation(ptr, size); *pHandle = AddAllocation(ptr, size);
#endif #endif
return ptr; return ptr;
#endif
} }
private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption private static void* Reallocate(void* instance, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle , MemoryHandle* pHandle
#endif #endif
) )
@@ -529,48 +487,13 @@ public static unsafe class AllocationManager
if (ptr == null) if (ptr == null)
{ {
return Allocate(instance, newSize, alignment, allocationOption return Allocate(instance, newSize, alignment, allocationOption
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, pHandle , pHandle
#endif #endif
); );
} }
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, newSize);
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)
{ {
@@ -581,16 +504,15 @@ public static unsafe class AllocationManager
selfPtr->_freeList.Free(ptr); selfPtr->_freeList.Free(ptr);
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
RemoveAllocation(*pHandle); RemoveAllocation(*pHandle);
*pHandle = AddAllocation(newPtr, newSize); *pHandle = AddAllocation(newPtr, newSize);
#endif #endif
return newPtr; return newPtr;
#endif
} }
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
private static bool IsValid(void* instance, MemoryHandle handle) private static bool IsValid(void* instance, MemoryHandle handle)
{ {
return ContainsAllocation(handle); return ContainsAllocation(handle);
@@ -598,23 +520,15 @@ public static unsafe class AllocationManager
#endif #endif
private static void Free(void* instance, void* ptr private static void Free(void* instance, void* ptr
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle handle , MemoryHandle handle
#endif #endif
) )
{ {
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);
RemoveAllocation(handle);
#else
selfPtr->_freeList.Free(ptr); selfPtr->_freeList.Free(ptr);
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
RemoveAllocation(handle); RemoveAllocation(handle);
#endif
#endif #endif
} }
@@ -629,14 +543,8 @@ public static unsafe class AllocationManager
private static StackAllocator* s_pStackAllocator; private static StackAllocator* s_pStackAllocator;
private static FreeListAllocator* s_pFreeListAllocator; private static FreeListAllocator* s_pFreeListAllocator;
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
private static SpinLock s_liveLock;
private static AllocationHeader* s_pLiveHead;
#endif
#if ENABLE_SAFETY_CHECKS
private static ConcurrentSlotMap<AllocationInfo> s_allocations = null!; private static ConcurrentSlotMap<AllocationInfo> s_allocations = null!;
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.
@@ -644,7 +552,7 @@ public static unsafe class AllocationManager
public static int LiveAllocationCount => s_allocations.Count; public static int LiveAllocationCount => s_allocations.Count;
#endif #endif
private static bool s_initialized; private static volatile bool s_initialized;
private static nuint s_threadLocalStackDefaultSize; private static nuint s_threadLocalStackDefaultSize;
public static void Initialize(AllocationManagerInitOpts opts) public static void Initialize(AllocationManagerInitOpts opts)
@@ -654,11 +562,7 @@ public static unsafe class AllocationManager
return; return;
} }
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
s_liveLock = new SpinLock(false);
s_pLiveHead = null;
#endif
#if ENABLE_SAFETY_CHECKS
s_allocations = new ConcurrentSlotMap<AllocationInfo>(256); s_allocations = new ConcurrentSlotMap<AllocationInfo>(256);
#endif #endif
@@ -679,161 +583,6 @@ public static unsafe class AllocationManager
s_initialized = true; s_initialized = true;
} }
#if ENABLE_DEBUG_LAYER
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte* AlignUp(byte* p, nuint alignment)
{
var a = alignment == 0 ? (nuint)IntPtr.Size : alignment;
return (byte*)(((nuint)p + (a - 1)) & ~(a - 1));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static GCHandle HeaderGetHandle(AllocationHeader* header)
{
return header->stackHandle;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void HeaderSetHandle(AllocationHeader* header, GCHandle handle)
{
header->stackHandle = handle;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void HeaderFreeHandle(AllocationHeader* header)
{
if (header->stackHandle.IsAllocated)
{
header->stackHandle.Free();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LinkHeader(AllocationHeader* header)
{
var taken = false;
try
{
s_liveLock.Enter(ref taken);
header->prev = null;
header->next = s_pLiveHead;
if (s_pLiveHead != null)
{
s_pLiveHead->prev = header;
}
s_pLiveHead = header;
}
finally
{
if (taken)
{
s_liveLock.Exit();
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void UnlinkHeader(AllocationHeader* header)
{
var taken = false;
try
{
s_liveLock.Enter(ref taken);
var prev = header->prev;
var next = header->next;
if (prev != null)
{
prev->next = next;
}
else
{
s_pLiveHead = next;
}
if (next != null)
{
next->prev = prev;
}
header->prev = header->next = null;
}
finally
{
if (taken)
{
s_liveLock.Exit();
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void* DebugAllocate(nuint size, nuint alignment)
{
// Over-allocate to fit header + alignment padding; we align the user pointer, header is placed just before it.
var pad = alignment == 0 ? (nuint)IntPtr.Size : alignment;
var total = size + (nuint)sizeof(AllocationHeader) + (pad - 1);
var basePtr = AlignedAlloc(total, pad);
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);
return user;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void DebugFree(void* userPtr)
{
var header = (AllocationHeader*)((byte*)userPtr - (nuint)sizeof(AllocationHeader));
UnlinkHeader(header);
HeaderFreeHandle(header);
AlignedFree(header->basePtr);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void* DebugReallocate(void* userPtr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption)
{
if (userPtr == null)
{
return DebugAllocate(newSize, alignment);
}
var oldHeader = (AllocationHeader*)((byte*)userPtr - (nuint)sizeof(AllocationHeader));
var handle = HeaderGetHandle(oldHeader); // preserve original allocation StackTrace
var pad = alignment == 0 ? (nuint)IntPtr.Size : alignment;
var total = newSize + (nuint)sizeof(AllocationHeader) + (pad - 1);
var newBase = AlignedAlloc(total, pad);
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); // transfer ownership to the new header
LinkHeader(newHeader);
// Mirror original behavior: copy newSize bytes
MemCpy(newUser, userPtr, newSize);
if (allocationOption.HasFlag(AllocationOption.Clear) && newSize > oldSize)
{
MemClear(newUser + oldSize, newSize - oldSize);
}
// Unlink and free the old block (without freeing the StackTrace pHandle again)
//oldHeader->stackHandle = GCHandle.FromIntPtr(0);
UnlinkHeader(oldHeader);
AlignedFree(oldHeader->basePtr);
return newUser;
}
#endif
/// <summary> /// <summary>
/// Gets a reference to the allocation pHandle for the specified allocator type. /// Gets a reference to the allocation pHandle for the specified allocator type.
/// </summary> /// </summary>
@@ -867,12 +616,7 @@ public static unsafe class AllocationManager
{ {
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
#if ENABLE_DEBUG_LAYER
var ptr = DebugAllocate(size, alignment);
#else
var ptr = AlignedAlloc(size, alignment); var ptr = AlignedAlloc(size, alignment);
#endif
if (ptr == null) if (ptr == null)
{ {
*pHandle = MemoryHandle.Invalid; *pHandle = MemoryHandle.Invalid;
@@ -884,7 +628,9 @@ public static unsafe class AllocationManager
MemClear(ptr, size); MemClear(ptr, size);
} }
#if MHP_ENABLE_SAFETY_CHECKS
*pHandle = AddAllocation(ptr, size); *pHandle = AddAllocation(ptr, size);
#endif
return ptr; return ptr;
} }
@@ -898,18 +644,9 @@ public static unsafe class AllocationManager
{ {
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
#if ENABLE_DEBUG_LAYER
if (handle != MagicHandle)
{
DebugFree(ptr);
}
else
#endif
{
AlignedFree(ptr); AlignedFree(ptr);
}
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
RemoveAllocation(handle); RemoveAllocation(handle);
#endif #endif
} }
@@ -922,9 +659,9 @@ public static unsafe class AllocationManager
{ {
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
if (TryGetAllocation(handle, out var ptr)) if (TryGetAllocation(handle, out var info))
{ {
HeapFree((void*)ptr, handle); HeapFree((void*)info.Address, handle);
} }
} }
@@ -953,22 +690,23 @@ public static unsafe class AllocationManager
/// Registers a memory allocation and returns a handle that can be used to manage or reference the allocated memory. /// Registers a memory allocation and returns a handle that can be used to manage or reference the allocated memory.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Always returns an invalid handle if ENABLE_SAFETY_CHECKS is disabled. /// Always returns an invalid handle if MHP_ENABLE_SAFETY_CHECKS is disabled.
/// </remarks> /// </remarks>
/// <param name="ptr">A pointer to the memory block to be registered. The pointer must reference a valid, allocated memory region.</param> /// <param name="ptr">A pointer to the memory block to be registered. The pointer must reference a valid, allocated memory region.</param>
/// <param name="size">The size of the memory block to be registered.</param>
/// <returns>A MemoryHandle representing the registered allocation.</returns> /// <returns>A MemoryHandle representing the registered allocation.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MemoryHandle AddAllocation(void* ptr, nuint size) public static MemoryHandle AddAllocation(void* ptr, nuint size)
{ {
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
var info = new AllocationInfo var info = new AllocationInfo
{ {
Address = (IntPtr)ptr, Address = (IntPtr)ptr,
Size = size, Size = size,
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_STACKTRACE
StackTrace = GCHandle.Alloc(new StackTrace(1, true)) StackTrace = new StackTrace(1, true)
#endif #endif
}; };
@@ -990,9 +728,9 @@ public static unsafe class AllocationManager
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool RemoveAllocation(MemoryHandle handle) public static bool RemoveAllocation(MemoryHandle handle)
{ {
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
return s_allocations.Remove(handle.ID, handle.Generation); return s_allocations.Remove(handle.ID, handle.Generation, out var info);
#else #else
return false; return false;
#endif #endif
@@ -1005,16 +743,16 @@ public static unsafe class AllocationManager
/// Always returns false if debug layer is disabled, and the output pointer will be set to <see cref="IntPtr.Zero"/>. /// Always returns false if debug layer is disabled, and the output pointer will be set to <see cref="IntPtr.Zero"/>.
/// </remarks> /// </remarks>
/// <param name="handle">The memory handle identifying the allocation to retrieve allocation.</param> /// <param name="handle">The memory handle identifying the allocation to retrieve allocation.</param>
/// <param name="ptr">When this method returns, contains the pointer to the memory allocation if found; otherwise, <see cref="IntPtr.Zero"/>.</param> /// <param name="info">When this method returns, contains the allocation information associated with the specified handle, if the allocation was found; otherwise, an uninitialized value.</param>
/// <returns>true if the allocation was found and <paramref name="ptr"/> contains a valid pointer; otherwise, false.</returns> /// <returns>true if the allocation was found and <paramref name="info"/> contains valid allocation information; otherwise, false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetAllocation(MemoryHandle handle, out IntPtr ptr) public static bool TryGetAllocation(MemoryHandle handle, out AllocationInfo info)
{ {
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
return s_allocations.TryGetElement(handle.ID, handle.Generation, out ptr); return s_allocations.TryGetElement(handle.ID, handle.Generation, out info);
#else #else
ptr = IntPtr.Zero; info = default;
return false; return false;
#endif #endif
} }
@@ -1032,14 +770,8 @@ public static unsafe class AllocationManager
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsAllocation(MemoryHandle handle) public static bool ContainsAllocation(MemoryHandle handle)
{ {
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
Debug.Assert(s_initialized, "AllocationManager is not initialized."); Debug.Assert(s_initialized, "AllocationManager is not initialized.");
if (handle == MagicHandle)
{
return true;
}
return s_allocations.Contains(handle.ID, handle.Generation); return s_allocations.Contains(handle.ID, handle.Generation);
#else #else
return false; return false;
@@ -1056,51 +788,12 @@ public static unsafe class AllocationManager
return; return;
} }
#if ENABLE_DEBUG_LAYER s_initialized = false;
// In debug mode, walk the intrusive list to surface any leaks.
var snapshot = new List<AllocationInfo>();
var taken = false;
try
{
s_liveLock.Enter(ref taken);
if (s_pLiveHead != null)
{
snapshot.Capacity = 128;
for (var p = s_pLiveHead; p != null; p = p->next)
{
var trace = (StackTrace)HeaderGetHandle(p).Target!;
snapshot.Add(new AllocationInfo
{
Size = p->userSize,
StackTrace = trace
});
}
}
}
finally
{
if (taken)
{
s_liveLock.Exit();
}
}
nuint unfreeBytes = 0u; #if MHP_ENABLE_SAFETY_CHECKS
foreach (var info in snapshot) if (s_allocations.Count > 0)
{ {
unfreeBytes += info.Size; throw new MemoryLeakException(s_allocations);
}
if (unfreeBytes > 0u)
{
throw new MemoryLeakException(CollectionsMarshal.AsSpan(snapshot));
}
#endif
#if ENABLE_SAFETY_CHECKS
if (s_allocations.Count != 0)
{
throw new InvalidOperationException($"There are still {s_allocations.Count} live tracked allocations.");
} }
#endif #endif
@@ -1109,7 +802,5 @@ public static unsafe class AllocationManager
s_pFreeListAllocator->Dispose(); s_pFreeListAllocator->Dispose();
Free(s_pArenaAllocator); Free(s_pArenaAllocator);
s_initialized = false;
} }
} }

View File

@@ -26,31 +26,55 @@ public unsafe struct MemoryPool<T, TOpts> : IDisposable
}; };
} }
private static void* Allocate(void* pAllocator, nuint size, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Allocate(void* pAllocator, nuint size, nuint alignment, AllocationOption allocationOption
#if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
return ((T*)pAllocator)->Allocate(size, alignment, allocationOption); return ((T*)pAllocator)->Allocate(size, alignment, allocationOption);
} }
private static void* Reallocate(void* pAllocator, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption, MemoryHandle* pHandle) private static void* Reallocate(void* pAllocator, void* ptr, nuint oldSize, nuint newSize, nuint alignment, AllocationOption allocationOption
#if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle* pHandle
#endif
)
{ {
if (ptr == null) if (ptr == null)
{ {
return Allocate(pAllocator, newSize, alignment, allocationOption, pHandle); return Allocate(pAllocator, newSize, alignment, allocationOption
#if MHP_ENABLE_SAFETY_CHECKS
, pHandle
#endif
);
} }
var newPtr = Allocate(pAllocator, newSize, alignment, allocationOption, pHandle); var newPtr = Allocate(pAllocator, newSize, alignment, allocationOption
#if MHP_ENABLE_SAFETY_CHECKS
, pHandle
#endif
);
if (newPtr == null) if (newPtr == null)
{ {
return null; return null;
} }
MemCpy(newPtr, ptr, Math.Min(oldSize, newSize)); MemCpy(newPtr, ptr, Math.Min(oldSize, newSize));
Free(pAllocator, ptr, *pHandle); Free(pAllocator, ptr
#if MHP_ENABLE_SAFETY_CHECKS
, *pHandle
#endif
);
return newPtr; return newPtr;
} }
private static void Free(void* pAllocator, void* ptr, MemoryHandle handle) private static void Free(void* pAllocator, void* ptr
#if MHP_ENABLE_SAFETY_CHECKS
, MemoryHandle handle
#endif
)
{ {
((T*)pAllocator)->Free(ptr); ((T*)pAllocator)->Free(ptr);
} }

View File

@@ -32,7 +32,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
_allocator = allocator; _allocator = allocator;
_handle = handle; _handle = handle;
_originalOffset = allocator->_offset; _originalOffset = allocator->_offset;
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount++; _allocator->_activeScopeCount++;
#endif #endif
} }
@@ -42,7 +42,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
if (_allocator != null) if (_allocator != null)
{ {
_allocator->_offset = _allocator->_offset > _originalOffset ? _originalOffset : _allocator->_offset; _allocator->_offset = _allocator->_offset > _originalOffset ? _originalOffset : _allocator->_offset;
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount--; _allocator->_activeScopeCount--;
#endif #endif
} }
@@ -52,7 +52,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
private byte* _buffer; private byte* _buffer;
private nuint _size; private nuint _size;
private nuint _offset; private nuint _offset;
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
private uint _activeScopeCount; private uint _activeScopeCount;
#endif #endif
@@ -74,7 +74,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
_buffer = (byte*)Malloc(size); _buffer = (byte*)Malloc(size);
_size = size; _size = size;
_offset = 0; _offset = 0;
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
_activeScopeCount = 0; _activeScopeCount = 0;
#endif #endif
} }
@@ -103,7 +103,7 @@ public unsafe partial struct Stack : IMemoryAllocator<Stack, Stack.CreationOpts>
/// there is insufficient space in the buffer.</returns> /// there is insufficient space in the buffer.</returns>
public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None) public void* Allocate(nuint size, nuint alignment, AllocationOption allocationOption = AllocationOption.None)
{ {
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
if (_activeScopeCount == 0) if (_activeScopeCount == 0)
{ {
throw new InvalidOperationException("Allocations can only be made within an active memory scope."); throw new InvalidOperationException("Allocations can only be made within an active memory scope.");

View File

@@ -30,7 +30,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
_allocator = allocator; _allocator = allocator;
_handle = handle; _handle = handle;
_originalOffset = allocator->_allocatedOffset; _originalOffset = allocator->_allocatedOffset;
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount++; _allocator->_activeScopeCount++;
#endif #endif
} }
@@ -40,7 +40,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
if (_allocator != null) if (_allocator != null)
{ {
_allocator->_allocatedOffset = _allocator->_allocatedOffset > _originalOffset ? _originalOffset : _allocator->_allocatedOffset; _allocator->_allocatedOffset = _allocator->_allocatedOffset > _originalOffset ? _originalOffset : _allocator->_allocatedOffset;
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
_allocator->_activeScopeCount--; _allocator->_activeScopeCount--;
#endif #endif
} }
@@ -51,7 +51,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
private nuint _reserveCapacity; private nuint _reserveCapacity;
private nuint _committedSize; private nuint _committedSize;
private nuint _allocatedOffset; private nuint _allocatedOffset;
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
private uint _activeScopeCount; private uint _activeScopeCount;
#endif #endif
@@ -70,7 +70,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
_baseAddress = (byte*)Mmap(null, _reserveCapacity, VirtualAllocationFlags.Reserve); _baseAddress = (byte*)Mmap(null, _reserveCapacity, VirtualAllocationFlags.Reserve);
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
_activeScopeCount = 0; _activeScopeCount = 0;
#endif #endif
} }
@@ -96,7 +96,7 @@ public unsafe struct VirtualStack : IMemoryAllocator<VirtualStack, VirtualStack.
/// </remarks> /// </remarks>
public void* Allocate(nuint size, nuint alignment, AllocationOption option = AllocationOption.None) public void* Allocate(nuint size, nuint alignment, AllocationOption option = AllocationOption.None)
{ {
#if ENABLE_SAFETY_CHECKS #if MHP_ENABLE_SAFETY_CHECKS
if (_activeScopeCount == 0) if (_activeScopeCount == 0)
{ {
throw new InvalidOperationException("Allocations can only be made within an active memory scope."); throw new InvalidOperationException("Allocations can only be made within an active memory scope.");

View File

@@ -8,7 +8,7 @@ public unsafe interface IUnsafeCollection : IDisposable
/// Indicates whether the object has been created. Returns true if the object is created, otherwise false. /// Indicates whether the object has been created. Returns true if the object is created, otherwise false.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// If ENABLE_DEBUG_LAYER is not defined, this property will only check if the underlying pointer is not null, which may not be sufficient to determine if the collection is fully initialized and ready for use. /// If MHP_ENABLE_STACKTRACE is not defined, this property will only check if the underlying pointer is not null, which may not be sufficient to determine if the collection is fully initialized and ready for use.
/// </remarks> /// </remarks>
bool IsCreated bool IsCreated
{ {

View File

@@ -77,7 +77,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
private readonly int _sizeOfTValue; private readonly int _sizeOfTValue;
private readonly int _log2MinGrowth; private readonly int _log2MinGrowth;
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
private MemoryHandle _memoryHandle; private MemoryHandle _memoryHandle;
#endif #endif
private AllocationHandle _allocationHandle; private AllocationHandle _allocationHandle;
@@ -96,7 +96,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
{ {
get get
{ {
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
if (_buffer != null) if (_buffer != null)
{ {
if (_allocationHandle.IsValid != null) if (_allocationHandle.IsValid != null)
@@ -161,7 +161,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_SAFETY_CHECKS")] [Conditional("MHP_ENABLE_SAFETY_CHECKS")]
private readonly void ThrowIfNotCreated() private readonly void ThrowIfNotCreated()
{ {
if (!IsCreated) if (!IsCreated)
@@ -256,11 +256,11 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
throw new InvalidOperationException("Target allocation handle does not support allocation."); throw new InvalidOperationException("Target allocation handle does not support allocation.");
} }
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
MemoryHandle memHandle; MemoryHandle memHandle;
#endif #endif
var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption var buf = (byte*)_allocationHandle.Alloc(_allocationHandle.State, (uint)totalSize, (nuint)_alignment, allocationOption
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
, &memHandle , &memHandle
#endif #endif
); );
@@ -269,7 +269,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
_keys = (TKey*)(_buffer + keyOffset); _keys = (TKey*)(_buffer + keyOffset);
_next = (int*)(_buffer + nextOffset); _next = (int*)(_buffer + nextOffset);
_buckets = (int*)(_buffer + bucketOffset); _buckets = (int*)(_buffer + bucketOffset);
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
_memoryHandle = memHandle; _memoryHandle = memHandle;
#endif #endif
} }
@@ -284,7 +284,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
var oldNext = _next; var oldNext = _next;
var oldBuckets = _buckets; var oldBuckets = _buckets;
var oldBucketCapacity = _bucketCapacity; var oldBucketCapacity = _bucketCapacity;
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
var oldMemoryHandle = _memoryHandle; var oldMemoryHandle = _memoryHandle;
#endif #endif
@@ -306,7 +306,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
if (_allocationHandle.Free != null) if (_allocationHandle.Free != null)
{ {
_allocationHandle.Free(_allocationHandle.State, oldBuffer _allocationHandle.Free(_allocationHandle.State, oldBuffer
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
, oldMemoryHandle , oldMemoryHandle
#endif #endif
); );
@@ -725,7 +725,7 @@ public unsafe struct HashMapHelper<TKey> : IDisposable
if (_allocationHandle.Free != null) if (_allocationHandle.Free != null)
{ {
_allocationHandle.Free(_allocationHandle.State, _buffer _allocationHandle.Free(_allocationHandle.State, _buffer
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
, _memoryHandle , _memoryHandle
#endif #endif
); );

View File

@@ -96,7 +96,7 @@ public readonly unsafe struct ReadOnlyUnsafeCollection<T> : IEnumerable<T>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_SAFETY_CHECKS")] [Conditional("MHP_ENABLE_SAFETY_CHECKS")]
private readonly void CheckIndexBounds(int index) private readonly void CheckIndexBounds(int index)
{ {
if (index >= _count) if (index >= _count)

View File

@@ -21,6 +21,7 @@ public unsafe struct UnTypedArray : IUnTypedCollection
{ {
get get
{ {
#if MHP_ENABLE_SAFETY_CHECKS
if (_buffer != null) if (_buffer != null)
{ {
if (_allocationHandle.IsValid != null) if (_allocationHandle.IsValid != null)
@@ -34,6 +35,9 @@ public unsafe struct UnTypedArray : IUnTypedCollection
} }
return false; return false;
#else
return _buffer != null;
#endif
} }
} }
@@ -57,12 +61,20 @@ public unsafe struct UnTypedArray : IUnTypedCollection
throw new InvalidOperationException("Target allocation handle does not support allocation."); throw new InvalidOperationException("Target allocation handle does not support allocation.");
} }
#if MHP_ENABLE_SAFETY_CHECKS
MemoryHandle memHandle; MemoryHandle memHandle;
_buffer = handle.Alloc(_allocationHandle.State, size, alignment, allocationOption, &memHandle); #endif
var buff = handle.Alloc(handle.State, size, alignment, allocationOption
#if MHP_ENABLE_SAFETY_CHECKS
, &memHandle
#endif
);
_size = size; _size = size;
_alignment = alignment; _alignment = alignment;
#if MHP_ENABLE_SAFETY_CHECKS
_memoryHandle = memHandle; _memoryHandle = memHandle;
#endif
_allocationHandle = handle; _allocationHandle = handle;
} }
@@ -109,10 +121,18 @@ public unsafe struct UnTypedArray : IUnTypedCollection
return; return;
} }
#if MHP_ENABLE_SAFETY_CHECKS
var memHandle = _memoryHandle; var memHandle = _memoryHandle;
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option, &memHandle); #endif
_buffer = _allocationHandle.Realloc(_allocationHandle.State, _buffer, _size, newSize, _alignment, option
#if MHP_ENABLE_SAFETY_CHECKS
, &memHandle
#endif
);
_size = newSize; _size = newSize;
#if MHP_ENABLE_SAFETY_CHECKS
_memoryHandle = memHandle; _memoryHandle = memHandle;
#endif
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -257,7 +277,11 @@ public unsafe struct UnTypedArray : IUnTypedCollection
if (_allocationHandle.Free != null) if (_allocationHandle.Free != null)
{ {
_allocationHandle.Free(_allocationHandle.State, _buffer, _memoryHandle); _allocationHandle.Free(_allocationHandle.State, _buffer
#if MHP_ENABLE_SAFETY_CHECKS
, _memoryHandle
#endif
);
} }
_buffer = null; _buffer = null;

View File

@@ -76,7 +76,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
private T* _buffer; private T* _buffer;
private int _count; private int _count;
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
private MemoryHandle _memoryHandle; private MemoryHandle _memoryHandle;
#endif #endif
private AllocationHandle _allocationHandle; private AllocationHandle _allocationHandle;
@@ -108,7 +108,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
{ {
get get
{ {
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
if (_buffer != null) if (_buffer != null)
{ {
if (_allocationHandle.IsValid != null) if (_allocationHandle.IsValid != null)
@@ -167,17 +167,17 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
throw new InvalidOperationException("Target allocation handle does not support allocation."); throw new InvalidOperationException("Target allocation handle does not support allocation.");
} }
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
MemoryHandle memHandle; MemoryHandle memHandle;
#endif #endif
var buff = handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption var buff = handle.Alloc(handle.State, (nuint)(count * sizeof(T)), AlignOf<T>(), allocationOption
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
, &memHandle , &memHandle
#endif #endif
); );
_buffer = (T*)buff; _buffer = (T*)buff;
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
_memoryHandle = memHandle; _memoryHandle = memHandle;
#endif #endif
_allocationHandle = handle; _allocationHandle = handle;
@@ -213,7 +213,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_SAFETY_CHECKS")] [Conditional("MHP_ENABLE_SAFETY_CHECKS")]
private readonly void ThrowIfNotCreated() private readonly void ThrowIfNotCreated()
{ {
if (!IsCreated) if (!IsCreated)
@@ -223,7 +223,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_SAFETY_CHECKS")] [Conditional("MHP_ENABLE_SAFETY_CHECKS")]
private readonly void CheckIndexBounds(int index) private readonly void CheckIndexBounds(int index)
{ {
ThrowIfNotCreated(); ThrowIfNotCreated();
@@ -258,16 +258,16 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
return; return;
} }
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
MemoryHandle memHandle = _memoryHandle; MemoryHandle memHandle = _memoryHandle;
#endif #endif
var elemSize = SizeOf<T>(); var elemSize = SizeOf<T>();
_buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option _buffer = (T*)_allocationHandle.Realloc(_allocationHandle.State, _buffer, (nuint)Count * elemSize, (nuint)newSize * elemSize, AlignOf<T>(), option
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
, &memHandle , &memHandle
#endif #endif
); );
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
_memoryHandle = memHandle; _memoryHandle = memHandle;
#endif #endif
_count = newSize; _count = newSize;
@@ -424,7 +424,7 @@ public unsafe struct UnsafeArray<T> : IUnsafeCollection<T>
if (_allocationHandle.Free != null) if (_allocationHandle.Free != null)
{ {
_allocationHandle.Free(_allocationHandle.State, _buffer _allocationHandle.Free(_allocationHandle.State, _buffer
#if ENABLE_DEBUG_LAYER #if MHP_ENABLE_SAFETY_CHECKS
, _memoryHandle , _memoryHandle
#endif #endif
); );

View File

@@ -207,14 +207,14 @@ public unsafe struct UnsafeList<T> : IUnsafeCollection<T>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_SAFETY_CHECKS")] [Conditional("MHP_ENABLE_SAFETY_CHECKS")]
private readonly void CheckNoResizeCapacity(int count) private readonly void CheckNoResizeCapacity(int count)
{ {
CheckNoResizeCapacity(count, Count); CheckNoResizeCapacity(count, Count);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[Conditional("ENABLE_SAFETY_CHECKS")] [Conditional("MHP_ENABLE_SAFETY_CHECKS")]
private readonly void CheckNoResizeCapacity(int index, int count) private readonly void CheckNoResizeCapacity(int index, int count)
{ {
if (index + count > Capacity) if (index + count > Capacity)

View File

@@ -14,15 +14,21 @@ public class MemoryLeakException : Exception
public override string Message => _message; public override string Message => _message;
public MemoryLeakException(ReadOnlySpan<AllocationInfo> infos) public MemoryLeakException(IEnumerable<AllocationInfo> infos)
{ {
var stringBuilder = new StringBuilder(); var stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"Found {infos.Length} memory lakes!"); stringBuilder.AppendLine($"Found {infos.Count()} memory lakes!");
#if MHP_ENABLE_STACKTRACE
stringBuilder.AppendLine();
foreach (var info in infos) foreach (var info in infos)
{ {
stringBuilder.AppendLine(GetMessage(info.StackTrace)); GetMessage(stringBuilder, info.StackTrace);
} }
#else
stringBuilder.AppendLine("No stack trace information available. Please enable MHP_ENABLE_STACKTRACE for detailed leak information.");
#endif
_message = stringBuilder.ToString(); _message = stringBuilder.ToString();
} }
@@ -32,14 +38,14 @@ public class MemoryLeakException : Exception
_message = message; _message = message;
} }
private static string GetMessage(StackTrace? stackTrace) private static void GetMessage(StringBuilder stringBuilder, StackTrace? stackTrace)
{ {
if (stackTrace == null) if (stackTrace == null)
{ {
return "No stack trace available."; stringBuilder.AppendLine("No stack trace available.");
return;
} }
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("Memory leak detected at: "); stringBuilder.AppendLine("Memory leak detected at: ");
for (var i = 0; i < stackTrace.FrameCount; i++) for (var i = 0; i < stackTrace.FrameCount; i++)
@@ -49,10 +55,9 @@ public class MemoryLeakException : Exception
if (frame != null) if (frame != null)
{ {
stringBuilder.AppendLine($"File: {fileName}, Method: {DiagnosticMethodInfo.Create(frame)?.Name}, Line: {frame.GetFileLineNumber()}"); var methodInfo = DiagnosticMethodInfo.Create(frame);
stringBuilder.AppendLine($"File: {fileName}, Type: {methodInfo?.DeclaringTypeName}, Method: {methodInfo?.Name}, Line: {frame.GetFileLineNumber()}");
} }
} }
return stringBuilder.ToString();
} }
} }

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
@@ -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.4</AssemblyVersion> <AssemblyVersion>1.6.0</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>
@@ -17,7 +17,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible> <IsAotCompatible>True</IsAotCompatible>
<DefineConstants>$(DefineConstants);ENABLE_SAFETY_CHECKS</DefineConstants> <!--<DefineConstants>$(DefineConstants);MHP_ENABLE_SAFETY_CHECKS;MHP_ENABLE_STACKTRACE</DefineConstants>-->
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@@ -59,12 +59,12 @@ arr.Dispose();
AllocationManager.Dispose(); AllocationManager.Dispose();
``` ```
You can enable debug features for leak detection and use-after-free checks by defining `ENABLE_SAFETY_CHECKS` in your project. And define `ENABLE_DEBUG_LAYER` to enable additional debug features such as tracking allocations and providing detailed error messages. You can enable debug features for leak detection and use-after-free checks by defining `MHP_ENABLE_SAFETY_CHECKS` in your project. And define `MHP_ENABLE_STACKTRACE` to enable additional debug features such as tracking allocations and providing detailed error messages.
> Which means if you disable the safety checks, the library will not perform any safety checks and provide the maximum performance, and it will be your responsibility to ensure correct usage to avoid memory leaks and undefined behavior. > Which means if you disable the safety checks, the library will not perform any safety checks and provide the maximum performance, and it will be your responsibility to ensure correct usage to avoid memory leaks and undefined behavior.
> Even `IUnsafeCollection.IsCreated` will only check if the internal pointer is non-null, without verifying the actual validity of the memory. > Even `IUnsafeCollection.IsCreated` will only check if the internal pointer is non-null, without verifying the actual validity of the memory.
You can also define `ENABLE_MIMALLOC` to use mimalloc as the underlying allocator instead of the default C allocator. You can also define `MHP_ENABLE_MIMALLOC` to use mimalloc as the underlying allocator instead of the default C allocator.
> Using mimalloc requires to install the TerraFX.Interop.Mimalloc package and ensure the native mimalloc library is available at runtime. > Using mimalloc requires to install the `TerraFX.Interop.Mimalloc` package.
## Package reference ## Package reference

View File

@@ -1,7 +1,7 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning; using System.Runtime.Versioning;
#if ENABLE_MIMALLOC #if MHP_ENABLE_MIMALLOC
using TerraFX.Interop.Mimalloc; using TerraFX.Interop.Mimalloc;
#endif #endif
@@ -69,7 +69,7 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Malloc(nuint size) public static void* Malloc(nuint size)
{ {
#if ENABLE_MIMALLOC #if MHP_ENABLE_MIMALLOC
return Mimalloc.mi_malloc(size); return Mimalloc.mi_malloc(size);
#elif NET6_0_OR_GREATER #elif NET6_0_OR_GREATER
return NativeMemory.Alloc(size); return NativeMemory.Alloc(size);
@@ -86,7 +86,7 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Calloc(nuint size) public static void* Calloc(nuint size)
{ {
#if ENABLE_MIMALLOC #if MHP_ENABLE_MIMALLOC
return Mimalloc.mi_zalloc(size); return Mimalloc.mi_zalloc(size);
#elif NET6_0_OR_GREATER #elif NET6_0_OR_GREATER
return NativeMemory.AllocZeroed(size); return NativeMemory.AllocZeroed(size);
@@ -106,7 +106,7 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AlignedAlloc(nuint size, nuint alignment) public static void* AlignedAlloc(nuint size, nuint alignment)
{ {
#if ENABLE_MIMALLOC #if MHP_ENABLE_MIMALLOC
return Mimalloc.mi_aligned_alloc(alignment, size); return Mimalloc.mi_aligned_alloc(alignment, size);
#elif NET6_0_OR_GREATER #elif NET6_0_OR_GREATER
return NativeMemory.AlignedAlloc(size, alignment); return NativeMemory.AlignedAlloc(size, alignment);
@@ -124,7 +124,7 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* Realloc(void* ptr, nuint size) public static void* Realloc(void* ptr, nuint size)
{ {
#if ENABLE_MIMALLOC #if MHP_ENABLE_MIMALLOC
return Mimalloc.mi_realloc(ptr, size); return Mimalloc.mi_realloc(ptr, size);
#elif NET6_0_OR_GREATER #elif NET6_0_OR_GREATER
return NativeMemory.Realloc(ptr, size); return NativeMemory.Realloc(ptr, size);
@@ -144,7 +144,7 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment) public static void* AlignedRealloc(void* ptr, nuint size, nuint alignment)
{ {
#if ENABLE_MIMALLOC #if MHP_ENABLE_MIMALLOC
return Mimalloc.mi_realloc_aligned(ptr, size, alignment); return Mimalloc.mi_realloc_aligned(ptr, size, alignment);
#elif NET6_0_OR_GREATER #elif NET6_0_OR_GREATER
return NativeMemory.AlignedRealloc(ptr, size, alignment); return NativeMemory.AlignedRealloc(ptr, size, alignment);
@@ -172,7 +172,7 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Free(void* ptr) public static void Free(void* ptr)
{ {
#if ENABLE_MIMALLOC #if MHP_ENABLE_MIMALLOC
Mimalloc.mi_free(ptr); Mimalloc.mi_free(ptr);
#elif NET6_0_OR_GREATER #elif NET6_0_OR_GREATER
NativeMemory.Free(ptr); NativeMemory.Free(ptr);
@@ -189,7 +189,7 @@ public static unsafe partial class MemoryUtility
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AlignedFree(void* ptr) public static void AlignedFree(void* ptr)
{ {
#if ENABLE_MIMALLOC #if MHP_ENABLE_MIMALLOC
Mimalloc.mi_free(ptr); Mimalloc.mi_free(ptr);
#elif NET6_0_OR_GREATER #elif NET6_0_OR_GREATER
NativeMemory.AlignedFree(ptr); NativeMemory.AlignedFree(ptr);

View File

@@ -44,6 +44,10 @@ using Misaki.HighPerformance.Mathematics;
float radians = math.radians(90f); float radians = math.radians(90f);
float degrees = math.degrees(radians); float degrees = math.degrees(radians);
float tau = math.TAU; float tau = math.TAU;
float3 a = new float3(1f, 2f, 3f);
float3 b = new float3(4f, 5f, 6f);
float3 c = math.cross(a, b);
``` ```
## Package reference ## Package reference

View File

@@ -1,4 +1,4 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using System.Numerics; using System.Numerics;
namespace Misaki.HighPerformance.Test.Benchmark; namespace Misaki.HighPerformance.Test.Benchmark;
@@ -31,8 +31,6 @@ public class HashCodeBenchmark
private Dictionary<Type, int> _hashCache = new(); private Dictionary<Type, int> _hashCache = new();
//private UnsafeHashMap<Guid, int> _hashMap = new(16); //private UnsafeHashMap<Guid, int> _hashMap = new(16);
private bool _disposed;
//~HashCodeBenchmark() //~HashCodeBenchmark()
//{ //{
// Dispose(); // Dispose();

View File

@@ -2,35 +2,6 @@ using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Collections;
//BenchmarkRunner.Run<SPMDBenchmark>(); //BenchmarkRunner.Run<SPMDBenchmark>();
//var hashMap = new UnsafeHashMap<int, int>(10, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
//hashMap[0] = 5;
//hashMap[1] = 6;
//Console.WriteLine(hashMap[1]);
//ref var v = ref hashMap.GetValueRefOrAddDefault(1, out var exists);
//Console.WriteLine(exists);
//v = 10;
//Console.WriteLine(hashMap[1]);
//hashMap.Dispose();
//class TestClass
//{
// private UnsafeHashMap<int, int> _map;
// ref int Test()
// {
// ref var x = ref _map.GetValueRef(9, out var exists);
// if (exists)
// {
// return ref x;
// }
// return ref Unsafe.NullRef<int>();
// }
//}
var opts = new AllocationManagerInitOpts var opts = new AllocationManagerInitOpts
{ {
@@ -41,8 +12,11 @@ var opts = new AllocationManagerInitOpts
AllocationManager.Initialize(opts); AllocationManager.Initialize(opts);
using var arr = new UnsafeArray<int>(10, Allocator.FreeList); var arr = new UnsafeArray<int>(10, Allocator.Persistent);
var marr = new int[10]; var arrcpy = arr;
arr.CopyTo(marr); arr.Dispose();
Console.WriteLine(arr.IsCreated);
Console.WriteLine(arrcpy.IsCreated);
AllocationManager.Dispose(); AllocationManager.Dispose();

View File

@@ -22,8 +22,10 @@ public class TestAllocationManager
} }
finally finally
{ {
#if MHP_ENABLE_SAFETY_CHECKS
var leaks = AllocationManager.LiveAllocationCount; var leaks = AllocationManager.LiveAllocationCount;
Assert.AreEqual(0, leaks); Assert.AreEqual(0, leaks);
#endif
} }
} }
@@ -39,8 +41,10 @@ public class TestAllocationManager
} }
catch (MemoryLeakException) catch (MemoryLeakException)
{ {
#if MHP_ENABLE_SAFETY_CHECKS
var leaks = AllocationManager.LiveAllocationCount; var leaks = AllocationManager.LiveAllocationCount;
Assert.AreEqual(2, leaks); Assert.AreEqual(2, leaks);
#endif
return; return;
} }
@@ -50,6 +54,8 @@ public class TestAllocationManager
array2.Dispose(); array2.Dispose();
} }
#if ENABLE_SAFETY_CHECKS
Assert.Fail("Expected MemoryLeakException was not thrown."); Assert.Fail("Expected MemoryLeakException was not thrown.");
#endif
} }
} }

View File

@@ -9,7 +9,7 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
{ {
private struct SlotEntry private struct SlotEntry
{ {
public T? value; public T value;
public int generation; public int generation;
public int isValid; public int isValid;
} }
@@ -22,7 +22,7 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
public Enumerator(ConcurrentSlotMap<T> slotMap) public Enumerator(ConcurrentSlotMap<T> slotMap)
{ {
_slotMap = slotMap; _slotMap = slotMap;
_currentIndex = 0; _currentIndex = -1;
} }
public readonly T Current => _slotMap._data[_currentIndex].value!; public readonly T Current => _slotMap._data[_currentIndex].value!;
@@ -42,7 +42,10 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
return false; return false;
} }
public void Reset() => _currentIndex = 0; public void Reset()
{
_currentIndex = -1;
}
public void Dispose() public void Dispose()
{ {
@@ -177,11 +180,17 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
} }
public bool Remove(int slotIndex, int generation) public bool Remove(int slotIndex, int generation)
{
return Remove(slotIndex, generation, out _);
}
public bool Remove(int slotIndex, int generation, [MaybeNullWhen(false)] out T value)
{ {
var capacity = Volatile.Read(ref _capacity); var capacity = Volatile.Read(ref _capacity);
if (slotIndex < 0 || slotIndex >= capacity) if (slotIndex < 0 || slotIndex >= capacity)
{ {
value = default;
return false; return false;
} }
@@ -190,6 +199,7 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
// Check if slot is valid and generation matches // Check if slot is valid and generation matches
if (Volatile.Read(ref slot.isValid) == 0 || Volatile.Read(ref slot.generation) != generation) if (Volatile.Read(ref slot.isValid) == 0 || Volatile.Read(ref slot.generation) != generation)
{ {
value = default;
return false; return false;
} }
@@ -197,13 +207,15 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
if (Interlocked.CompareExchange(ref slot.isValid, 0, 1) == 1) if (Interlocked.CompareExchange(ref slot.isValid, 0, 1) == 1)
{ {
Interlocked.Increment(ref slot.generation); Interlocked.Increment(ref slot.generation);
slot.value = default; value = slot.value;
slot.value = default!;
_freeSlots.Enqueue(slotIndex); _freeSlots.Enqueue(slotIndex);
Interlocked.Decrement(ref _count); Interlocked.Decrement(ref _count);
return true; return true;
} }
value = default;
return false; // Another thread already removed it return false; // Another thread already removed it
} }
@@ -288,11 +300,9 @@ public class ConcurrentSlotMap<T> : IEnumerable<T>
ref var slot = ref _data[i]; ref var slot = ref _data[i];
Volatile.Write(ref slot.isValid, 0); Volatile.Write(ref slot.isValid, 0);
slot.generation = 0; slot.generation = 0;
slot.value = default; slot.value = default!;
} }
_freeSlots.Clear(); _freeSlots.Clear();
Add(default!, out _);
} }
} }

View File

@@ -5,9 +5,9 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Authors>Misaki</Authors> <Authors>Misaki</Authors>
<AssemblyVersion>1.0.5</AssemblyVersion> <AssemblyVersion>1.0.6</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

@@ -38,6 +38,8 @@ values.Add(10);
values.Add(20); values.Add(20);
values.Add(30); values.Add(30);
ref int firstValue = ref values[0];
Span<int> span = values.AsSpan(); Span<int> span = values.AsSpan();
``` ```