Skip to content

Commit d6fe1c9

Browse files
author
LoneWandererProductions
committed
implement canary
1 parent 14c406c commit d6fe1c9

9 files changed

Lines changed: 268 additions & 100 deletions

File tree

MemoryManager.Core/BlobManager.cs

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ public BlobManager(nint buffer, int capacity)
7575
/// <inheritdoc />
7676
public bool CanAllocate(int size)
7777
{
78-
return _nextFreeOffset + size <= _capacity;
78+
int physicalSize = MemoryCanary.GetPhysicalSize(size);
79+
return _nextFreeOffset + physicalSize <= _capacity;
7980
}
8081

8182
/// <inheritdoc />
@@ -86,24 +87,32 @@ public MemoryHandle Allocate(
8687
string? debugName = null,
8788
int currentFrame = 0)
8889
{
89-
if (!CanAllocate(size))
90+
int physicalSizeNeeded = MemoryCanary.GetPhysicalSize(size);
91+
if (_nextFreeOffset + physicalSizeNeeded > _capacity)
9092
throw new OutOfMemoryException(
9193
$"BlobManager: Out of memory. Requested {size} bytes, but only {FreeSpace()} available.");
9294

9395
if (_nextId == int.MinValue)
9496
throw new OutOfMemoryException("BlobManager: ID exhaustion. Cannot allocate more IDs.");
9597

9698
var id = _nextId--;
97-
int index = StartingId - id; // Maps sequential negative IDs cleanly to 0, 1, 2...
99+
int index = StartingId - id;
98100

99101
EnsureCapacity(index);
100102

101103
byte version = 1;
102104

105+
var physicalOffset = _nextFreeOffset;
106+
_nextFreeOffset += physicalSizeNeeded; // Bump advanced by complete physical footprint block size
107+
108+
// Map user pointer past the pre-canary guard band and stamp the signature bounds
109+
var userOffset = MemoryCanary.GetUserOffset(physicalOffset);
110+
MemoryCanary.WriteGuardBands(_buffer, userOffset, size);
111+
103112
_entries[index] = new BlobEntry
104113
{
105114
Id = id,
106-
Offset = _nextFreeOffset,
115+
Offset = userOffset, // Resolve() continues to target this instantly
107116
Size = size,
108117
AllocationFrame = currentFrame,
109118
Version = version
@@ -116,10 +125,7 @@ public MemoryHandle Allocate(
116125
}
117126
#endif
118127

119-
// Bump the allocator forward
120-
_nextFreeOffset += size;
121128
EntryCount++;
122-
123129
return new MemoryHandle(id, version, this);
124130
}
125131

@@ -131,6 +137,11 @@ public void Free(MemoryHandle handle)
131137
if (index < 0 || index >= _entries.Length || _entries[index].Size == 0)
132138
throw new InvalidOperationException($"BlobManager: Invalid or double-freed handle {handle.Id}");
133139

140+
ref readonly var entry = ref _entries[index];
141+
142+
// Assert safety guard signatures are perfectly intact before deletion sweeps
143+
MemoryCanary.Validate(_buffer, entry.Offset, entry.Size, handle.Id);
144+
134145
_entries[index] = default;
135146
EntryCount--;
136147

@@ -237,23 +248,29 @@ public void Compact()
237248
{
238249
ref readonly var entry = ref validSpan[i];
239250

240-
if (entry.Offset > currentOffset)
251+
// Reconstruct exact physical positions and block dimensions
252+
int srcPhysicalOffset = MemoryCanary.GetPhysicalOffset(entry.Offset);
253+
int destPhysicalOffset = currentOffset;
254+
int physicalSize = MemoryCanary.GetPhysicalSize(entry.Size);
255+
256+
if (srcPhysicalOffset > destPhysicalOffset)
241257
{
242258
unsafe
243259
{
260+
// Copy complete physical layout block footprints concurrently
244261
System.Buffer.MemoryCopy(
245-
(void*)(_buffer + entry.Offset),
246-
(void*)(_buffer + currentOffset),
247-
entry.Size,
248-
entry.Size);
262+
(void*)(_buffer + srcPhysicalOffset),
263+
(void*)(_buffer + destPhysicalOffset),
264+
physicalSize,
265+
physicalSize);
249266
}
250267
}
251268

252-
// Update the offset inside our primary dense track tracking array slot directly
269+
// Sync user data offsets based on newly modified position tracking indices
253270
int index = StartingId - entry.Id;
254-
_entries[index].Offset = currentOffset;
271+
_entries[index].Offset = MemoryCanary.GetUserOffset(destPhysicalOffset);
255272

256-
currentOffset += entry.Size;
273+
currentOffset += physicalSize;
257274
}
258275

259276
_nextFreeOffset = currentOffset;
@@ -287,7 +304,8 @@ public int EstimateFragmentation()
287304
{
288305
if (_entries[i].Size > 0)
289306
{
290-
livingBytes += _entries[i].Size;
307+
// Evaluate tracking states using total physical block footprints
308+
livingBytes += MemoryCanary.GetPhysicalSize(_entries[i].Size);
291309
}
292310
}
293311

@@ -316,6 +334,7 @@ public string DebugDump()
316334
return $"BlobManager Dump\nAllocations: {EntryCount}\nUsed: {_nextFreeOffset}/{_capacity} bytes";
317335
}
318336

337+
/// <inheritdoc />
319338
/// <inheritdoc />
320339
public string DebugVisualMap()
321340
{
@@ -332,21 +351,28 @@ public string DebugVisualMap()
332351

333352
if (startByte >= _nextFreeOffset)
334353
{
335-
map[i] = '░';
354+
map[i] = '░'; // Totally unallocated space past the bump pointer
336355
continue;
337356
}
338357

339358
var isLiving = false;
340359
for (var j = 0; j < _entries.Length; j++)
341360
{
342-
if (_entries[j].Size > 0 && _entries[j].Offset < endByte && _entries[j].Offset + _entries[j].Size > startByte)
361+
if (_entries[j].Size > 0)
343362
{
344-
isLiving = true;
345-
break;
363+
// Extract absolute physical footprints so boundaries match raw heap tracking properties
364+
int physicalStart = MemoryCanary.GetPhysicalOffset(_entries[j].Offset);
365+
int physicalEnd = physicalStart + MemoryCanary.GetPhysicalSize(_entries[j].Size);
366+
367+
if (physicalStart < endByte && physicalEnd > startByte)
368+
{
369+
isLiving = true;
370+
break;
371+
}
346372
}
347373
}
348374

349-
map[i] = isLiving ? '█' : '-';
375+
map[i] = isLiving ? '█' : '-'; // Render '█' for allocated memory blocks and '-' for active gaps
350376
}
351377

352378
return $"Blob Map: [{new string(map)}]\nLegend: █=Used, -=Gap, ░=Free";
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
/*
22
* COPYRIGHT: See COPYING in the top level directory
3-
* PROJECT: MemoryManager.Lanes
3+
* PROJECT: MemoryManager.Core
44
* FILE: MemoryCanary.cs
55
* PURPOSE: Centralized unmanaged guard band validation with zero RELEASE overhead.
66
* PROGRAMMER: Peter Geinitz (Wayfarer)
77
*/
88

99
using System.Runtime.CompilerServices;
1010

11-
namespace MemoryManager.Lanes
11+
namespace MemoryManager.Core
1212
{
1313
/// <summary>
1414
/// Canary class provides a simple mechanism for detecting buffer overruns and underruns in debug builds by placing known "canary" values before and after user allocations.
1515
/// In release builds, the canary logic is completely stripped out to ensure zero overhead,
1616
/// making it an effective tool for catching memory corruption issues during development without impacting performance in production.
1717
/// </summary>
18-
internal static class MemoryCanary
18+
public static class MemoryCanary
1919
{
2020
/// <summary>
2121
/// The size

MemoryManager.Lanes/FastLane.cs

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -177,24 +177,26 @@ public unsafe MemoryHandle Allocate(
177177
{
178178
if (_entries == null) throw new InvalidOperationException("FastLane: Memory not reserved");
179179

180-
var offset = MemoryLaneUtils.FindFreeSpot(size, ref _freeBlocks, ref _freeBlockCount);
180+
// Calculate total layout footprint including canary guards
181+
int physicalSizeNeeded = MemoryCanary.GetPhysicalSize(size);
182+
var physicalOffset = MemoryLaneUtils.FindFreeSpot(physicalSizeNeeded, ref _freeBlocks, ref _freeBlockCount);
181183

182-
if (offset == -1)
184+
if (physicalOffset == -1)
183185
throw new OutOfMemoryException("FastLane: Cannot allocate - No contiguous block large enough.");
184186

187+
// Position the tracked user data offset cleanly past the pre-canary boundary
188+
var userOffset = MemoryCanary.GetUserOffset(physicalOffset);
189+
MemoryCanary.WriteGuardBands(Buffer, userOffset, size);
190+
185191
EnsureEntryCapacity(EntryCount);
186192

187-
//So we reuse freed handles here
188-
// So we reuse freed handles here
189193
var id = MemoryLaneUtils.GetNextId(_freeIds, ref _nextHandleId);
190194

191-
// Ensure our flat array is large enough for this ID
192195
if (id >= _versionsCapacity)
193196
{
194197
GrowVersions(id + 1);
195198
}
196199

197-
// True O(1) Version Bump
198200
uint version = ++_versions[id];
199201

200202
#if DEBUG
@@ -206,7 +208,7 @@ public unsafe MemoryHandle Allocate(
206208

207209
_entries[EntryCount] = new AllocationEntry
208210
{
209-
Offset = offset,
211+
Offset = userOffset, // Target user data directly for O(1) pointer resolution
210212
Size = size,
211213
HandleId = id,
212214
Priority = priority,
@@ -275,39 +277,37 @@ public nint Resolve(MemoryHandle handle)
275277
[MethodImpl(MethodImplOptions.AggressiveInlining)]
276278
public void Free(MemoryHandle handle)
277279
{
278-
// 1. Remove from index first
279280
if (!_handleIndex.TryRemove(handle.Id, out var index))
280281
throw new InvalidOperationException($"FastLane: Invalid handle {handle.Id}");
281282

282283
#if DEBUG
283284
_debugNames.Remove(handle.Id);
284285
#endif
285286

286-
// 2. Handle SlowLane/Redirects (This is your cold path)
287287
var entry = _entries[index];
288-
MemoryLaneUtils.ReturnFreeSpace(entry.Offset, entry.Size, ref _freeBlocks, ref _freeBlockCount);
288+
289+
// Assert safety markers are intact before deallocation operations
290+
MemoryCanary.Validate(Buffer, entry.Offset, entry.Size, handle.Id);
291+
292+
// Map user boundaries back to complete layout blocks for the free-list
293+
int physicalOffset = MemoryCanary.GetPhysicalOffset(entry.Offset);
294+
int physicalSize = MemoryCanary.GetPhysicalSize(entry.Size);
295+
MemoryLaneUtils.ReturnFreeSpace(physicalOffset, physicalSize, ref _freeBlocks, ref _freeBlockCount);
289296

290297
if (entry.IsStub && entry.RedirectToId != 0)
291298
{
292299
var slowHandle = new MemoryHandle(entry.RedirectToId, entry.RedirectVersion, _slowLane);
293300
_slowLane.Free(slowHandle);
294301
}
295302

296-
// 3. Swap-with-tail removal
297-
var lastIdx = --EntryCount; // Decrement first to get the last valid index
303+
var lastIdx = --EntryCount;
298304
if (index != lastIdx)
299305
{
300-
// Move the last entry into the hole
301306
var movedEntry = _entries[lastIdx];
302307
_entries[index] = movedEntry;
303-
304-
// Update the map to point to the new location
305-
// OPTIMIZATION: Use a direct 'Set' or 'Update' if your map allows
306-
// to avoid tombstone buildup during updates.
307308
_handleIndex[movedEntry.HandleId] = index;
308309
}
309310

310-
// 4. Return ID to pool
311311
_freeIds.Push(handle.Id);
312312
}
313313

@@ -346,24 +346,27 @@ public unsafe void Compact(int currentFrame, MemoryManagerConfig config)
346346
var newBuffer = Marshal.AllocHGlobal(Capacity);
347347
var currentOffset = 0;
348348

349-
// Now take your snapshot for sorting, but ONLY for survivors (non-stubs)
350349
var survivors = _entries.Take(EntryCount)
351350
.Where(e => !e.IsStub)
352351
.OrderBy(e => e.Offset)
353352
.ToList();
354353

355354
foreach (var survivor in survivors)
356355
{
357-
// 1. Move the bytes
358-
void* source = (byte*)Buffer + survivor.Offset;
356+
// Extract base tracking coordinates for the entire block footprint
357+
int srcPhysicalOffset = MemoryCanary.GetPhysicalOffset(survivor.Offset);
358+
int physicalSize = MemoryCanary.GetPhysicalSize(survivor.Size);
359+
360+
// 1. Move the complete physical block (Canary + User Data + Canary)
361+
void* source = (byte*)Buffer + srcPhysicalOffset;
359362
void* target = (byte*)newBuffer + currentOffset;
360-
System.Buffer.MemoryCopy(source, target, Capacity - currentOffset, survivor.Size);
363+
System.Buffer.MemoryCopy(source, target, Capacity - currentOffset, physicalSize);
361364

362-
// 2. Update the OFFSET ONLY in the real metadata
365+
// 2. Map user offset coordinates relative to the new layout alignment
363366
var index = _handleIndex[survivor.HandleId];
364-
_entries[index].Offset = currentOffset;
367+
_entries[index].Offset = MemoryCanary.GetUserOffset(currentOffset);
365368

366-
currentOffset += survivor.Size;
369+
currentOffset += physicalSize;
367370
}
368371

369372
// --- PASS 3: CLEANUP ---
@@ -463,15 +466,17 @@ public void ReplaceWithStub(MemoryHandle fastHandle, MemoryHandle slowHandle)
463466
if (!_handleIndex.TryGetValue(fastHandle.Id, out var index))
464467
throw new InvalidOperationException("FastLane: Invalid handle");
465468

466-
// Grab the existing entry
467469
var entry = _entries[index];
468470

469471
entry.IsStub = true;
470472
entry.RedirectToId = slowHandle.Id;
471473
entry.RedirectVersion = slowHandle.Version;
472474

473-
// Cleanup FastLane stats
474-
MemoryLaneUtils.ReturnFreeSpace(entry.Offset, entry.Size, ref _freeBlocks, ref _freeBlockCount);
475+
// Map boundaries to clean up the underlying block pool space cleanly
476+
int physicalOffset = MemoryCanary.GetPhysicalOffset(entry.Offset);
477+
int physicalSize = MemoryCanary.GetPhysicalSize(entry.Size);
478+
MemoryLaneUtils.ReturnFreeSpace(physicalOffset, physicalSize, ref _freeBlocks, ref _freeBlockCount);
479+
475480
entry.Offset = 0;
476481
entry.Size = 0;
477482

0 commit comments

Comments
 (0)