diff options
| author | Taylan Kammer <taylan.kammer@gmail.com> | 2026-08-19 22:54:45 +0200 |
|---|---|---|
| committer | Taylan Kammer <taylan.kammer@gmail.com> | 2026-08-19 22:54:45 +0200 |
| commit | a9b415866c12b2672091573721c5bdb01db8a7d0 (patch) | |
| tree | 07ed3c5da107ead73016a22d5b418bae2058b2d9 | |
| parent | 12996d4334b89c2e2e2203ca626787a079ed84b3 (diff) | |
Meta Alloc improvement and doc fix.
| -rw-r--r-- | MetaAlloc.ods | bin | 31148 -> 31520 bytes | |||
| -rw-r--r-- | doc/0/A-meta_alloc.md | 5 | ||||
| -rw-r--r-- | src/zisp/gc/meta_alloc.zig | 39 |
3 files changed, 34 insertions, 10 deletions
diff --git a/MetaAlloc.ods b/MetaAlloc.ods Binary files differindex dcc1c02..4da4cab 100644 --- a/MetaAlloc.ods +++ b/MetaAlloc.ods diff --git a/doc/0/A-meta_alloc.md b/doc/0/A-meta_alloc.md index 577f066..e339db7 100644 --- a/doc/0/A-meta_alloc.md +++ b/doc/0/A-meta_alloc.md @@ -210,12 +210,11 @@ values used by the implementation are as follows: * Maximum count: 16 indexes -* Smallest size class: 256 bytes +* Smallest size class: 128 bytes The extra index array starts after 64 bytes, and requires 64 bytes, since it stores up to 16 32-bit integers; that's a total size of 128, -which fits within 256, and means we could decrease the smallest size -to 128 as well if deemed more appropriate. +which fits exactly into the smallest size. The 64-byte padding is to allow for efficient bulk memory transfer using up to 512-bit SIMD instructions on modern processors. diff --git a/src/zisp/gc/meta_alloc.zig b/src/zisp/gc/meta_alloc.zig index 28494ee..e8502df 100644 --- a/src/zisp/gc/meta_alloc.zig +++ b/src/zisp/gc/meta_alloc.zig @@ -18,9 +18,13 @@ // that this means the slabs are limited to 16 GiB each (2^31 units because i32; // 8 bytes each) for a 256 GiB total, since we have 16 size classes. // -// Using 8-byte units means free conversion to byte-based addresses thanks to -// instructions like LEA or the ARM barrel shifter. Using size class sized -// units would require additional shift instructions and seems unnecessary. +// Using 8-byte units means that many (but not all) of the conversions to byte +// based addresses are effectively free thanks to instructions like LEA or the +// ARM barrel shifter. Using size class sized units would only require a few +// more shift instructions here and there, but seems unnecessary. +// +// Downsizing units to one byte would shave off an instruction here and there, +// and decrease vmem to 32 GiB (2 per slab), but that may be too restrictive. // // The global slab watermark of a size class is bumped in chunks, reserving a // number of slots for the thread that performed the bump. This way, threads @@ -204,7 +208,7 @@ export fn alloc(size: usize) [*]u8 { } } -/// Free memory that was returned by alloc(). +/// Free memory of a specific size that was returned by alloc(). export fn free(size: usize, ptr: [*]u8) void { const sci = getSizeClassIndex(size); if (sci < SIZES.len) { @@ -215,6 +219,27 @@ export fn free(size: usize, ptr: [*]u8) void { } } +/// Free memory that was returned by alloc(), calculating the size class from +/// the pointer address, which is only possible if it lies within one of the +/// managed slabs. If this pointer came from a call to alloc() with a size +/// greater than the largest size class, this function will panic. +export fn free_auto(ptr: [*]u8) void { + const paddr = @intFromPtr(ptr); + const saddr = @intFromPtr(slabs); + if (paddr < saddr) { + @panic("Invalid pointer passed to free_auto()."); + } + + const uaddr = paddr - saddr; + const uidx = uaddr / @sizeOf(Unit); + const sci = uidx / SLAB_LEN; + if (sci >= SIZES.len) { + @panic("Invalid pointer passed to free_auto()."); + } + + free_size_class(@intCast(sci), @ptrCast(@alignCast(ptr))); +} + fn alloc_size_class(sci: u8, size: usize) SlotPtr { std.debug.assert(sci < SIZES.len); @@ -260,10 +285,10 @@ fn alloc_from_fl( if (fl_head.idx >= SLAB_LEN) return null; ptr = slab[fl_head.idx..].ptr; - arr = @ptrCast(ptr); + arr = @ptrCast(@alignCast(ptr)); // Load this atomically since it's still globally accessible memory; - // unordered is fine since we discard it if the following CAS fails. + // unordered is fine since we discard it if the cmpxchg below fails. const next_head = @atomicLoad(UnitIdx, &arr[0], .unordered); fl_head = @cmpxchgWeak( @@ -342,7 +367,7 @@ fn free_into_fl( // Note: n = 0 is valid and must work. std.debug.assert(n <= FSC_MAX / 2); - const arr: [*]UnitIdx = @ptrCast(ptr); + const arr: [*]UnitIdx = @ptrCast(@alignCast(ptr)); const split = tl.fsc_count - n; tl.fsc_count = split; |
