diff options
| author | Taylan Kammer <taylan.kammer@gmail.com> | 2026-06-08 05:54:07 +0200 |
|---|---|---|
| committer | Taylan Kammer <taylan.kammer@gmail.com> | 2026-06-08 05:54:07 +0200 |
| commit | 627a09b58f404b1f690d27869b15b3197207c0af (patch) | |
| tree | 4e64459ff8462e7f4dfc502158b6ffa629ebb0c8 | |
| parent | 90af1ed17d317435fb56ea041fa0d937f5043726 (diff) | |
Code cleanup and make heap alloc 16-byte aligned.
| -rw-r--r-- | src/zisp/gc/PairPool.zig | 7 | ||||
| -rw-r--r-- | src/zisp/value.zig | 43 | ||||
| -rw-r--r-- | src/zisp/value/array.zig | 20 | ||||
| -rw-r--r-- | src/zisp/value/pair.zig | 2 | ||||
| -rw-r--r-- | src/zisp/value/ptr.zig | 15 |
5 files changed, 52 insertions, 35 deletions
diff --git a/src/zisp/gc/PairPool.zig b/src/zisp/gc/PairPool.zig index 7995098..4a77acc 100644 --- a/src/zisp/gc/PairPool.zig +++ b/src/zisp/gc/PairPool.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Alloc = std.mem.Allocator; +const AlignedPool = std.heap.memory_pool.Aligned; const value = @import("../value.zig"); @@ -10,7 +11,7 @@ const PairPtr = value.pair.PairPtr; const PairPool = @This(); alloc: Alloc, -pair_pool: std.heap.MemoryPool(value.pair.Pair), +pool: AlignedPool(value.pair.Pair, @enumFromInt(@alignOf(value.Zptr))), const default_init_cap = 1024; @@ -21,12 +22,12 @@ pub fn init(alloc: Alloc) !PairPool { pub fn initCustom(alloc: Alloc, init_cap: usize) !PairPool { return .{ .alloc = alloc, - .pair_pool = try .initCapacity(alloc, init_cap), + .pool = try .initCapacity(alloc, init_cap), }; } pub fn cons(self: *PairPool, car: Value, cdr: Value) !PairPtr { - var p = try self.pair_pool.create(self.alloc); + var p = try self.pool.create(self.alloc); p.car = car; p.cdr = cdr; return p; diff --git a/src/zisp/value.zig b/src/zisp/value.zig index 5402b16..50d695c 100644 --- a/src/zisp/value.zig +++ b/src/zisp/value.zig @@ -64,7 +64,7 @@ //! Pointers are further subdivided as follows based on the remaining 51 bits, //! with the first three bits used as a sort of tag: //! -//! 000 :: Regular pointer to Zisp heap object (string, vector, etc.) +//! 000 :: Regular pointer to Zisp heap object //! //! 001 :: Weak pointer to Zisp heap object //! @@ -72,11 +72,11 @@ //! //! 1.. :: Undefined //! -//! This means pointers to the Zisp heap are 48 bits. Of those, we only really -//! need 45, since 64-bit platforms are in practice limited to 48-bit addresses, -//! and Zisp heap allocations happen at 8-byte boundaries, meaning the lowest 3 -//! bits are always unset. Thus, we are able to store yet another 3-bit tag in -//! those 48-bit pointers alongside the actual, multiple-of-8, 48-bit address. +//! This means Zisp heap pointers are 48 bits. This is sufficient, since the +//! address space of user-land applications is effectively 48 bits on 64-bit +//! systems. Further, Zisp heap objects are allocated at 16-byte boundaries, +//! meaning the lowest 4 bits are always zero; this is used for type tagging, +//! providing immediate information about the type of object pointed to. //! //! Forbidden Value #3, Positive cqNaN, is avoided thanks to the fact that a //! regular Zisp heap pointer can never be null. Weak pointers, which can be @@ -205,10 +205,10 @@ pub const undef = Value{ .misc = .{ .value = .undef } }; /// A plain (unpacked, untagged) pointer into the Zisp heap. May point to any /// kind of object. -pub const Zptr = *align(8) anyopaque; +pub const Zptr = *align(16) anyopaque; -/// Values for the lowest 3 bits of a heap pointer, indicating the heap type. -pub const PtrTag = enum(u3) { +/// Values for the lowest 4 bits of a heap pointer, indicating the heap type. +pub const PtrTag = enum(u4) { /// Pair aka cons cell aka *[2]Value pair, /// Interned string buffer (8-bit length, then contents) @@ -218,7 +218,7 @@ pub const PtrTag = enum(u3) { /// Procedure proc, - pub fn ptrType(self: PtrTag) type { + pub fn ptype(self: PtrTag) type { return switch (self) { .pair => pair.PairPtr, .istr => istr.IstrPtr, @@ -324,6 +324,9 @@ pub const Value = packed union { const mask_rest: u64 = max(u51) ; // 51 rest bits of fraction // zig fmt: on + // Mask for pointer type tag bits + const mask_ptr_tag: u48 = max(@typeInfo(PtrTag).@"enum".tag_type); + /// Dumps the value for inspection. pub fn dump(v: Value) void { const sign: u1 = @intCast((v.bits & mask_sign) >> 63); @@ -405,7 +408,7 @@ pub const Value = packed union { /// the "high" pointer tags (49-51) that encode properties like weakness /// don't matter; we only care if it's some kind of valid heap pointer of /// the given type, so we only check the type tag in the low bits (0-2). - pub fn getPtr(v: Value, comptime tag: PtrTag) ?tag.ptrType() { + pub fn getPtr(v: Value, comptime tag: PtrTag) ?tag.ptype() { // Readable version: // // if (v.isPacked() and !v.ieee.sign and v.ieee.quiet ...) @@ -428,14 +431,28 @@ pub const Value = packed union { // that for example bits 50 and 51 must be zero, or 51 must be zero // while 50 is still ignored, and so on, as appropriate. // - const ptr_val: u48 = @intCast(v.bits & (max(u45) << 3)); const hi_bits: u13 = @intCast(v.bits >> 51); - const tag_bits: u3 = @intCast(v.bits & 7); + const ptr_val: u48 = @intCast(v.bits & ~mask_ptr_tag); + const tag_bits: u4 = @intCast(v.bits & mask_ptr_tag); const is_ptr = hi_bits == 0b0111111111111; const is_tag = tag_bits == @intFromEnum(tag); return if (is_ptr and is_tag) @ptrFromInt(ptr_val) else null; } + /// Checks for a pointer and returns the value and tag separately, or null + /// if this isn't a pointer at all. This could be useful for a dispatch + /// table based on type. + pub fn getPtrAny(v: Value) ?struct { Zptr, PtrTag } { + // See last function, which is almost identical. + const hi_bits: u13 = @intCast(v.bits >> 51); + const ptr_val: u48 = @intCast(v.bits & ~mask_ptr_tag); + const tag_bits: u4 = @intCast(v.bits & mask_ptr_tag); + const is_ptr = hi_bits == 0b0111111111111; + const zptr: Zptr = @ptrFromInt(ptr_val); + const tag: PtrTag = @enumFromInt(tag_bits); + return if (is_ptr) .{ zptr, tag } else null; + } + /// Checks whether the value is a pointer with certain property bits, not /// caring about the type tag bits. NOTE: This function doesn't check for /// +cqNaN, which will be mis-identified as a pointer with zero props and diff --git a/src/zisp/value/array.zig b/src/zisp/value/array.zig index 6be3c9c..ad04ce2 100644 --- a/src/zisp/value/array.zig +++ b/src/zisp/value/array.zig @@ -81,22 +81,24 @@ pub const ArrayHeader = packed struct(u64) { }, }, - fn bufU64(self: *@This()) [*]u64 { + const Self = @This(); + + fn bufU64(self: *Self) [*]u64 { return @ptrCast(self); } - fn bufContent(self: *@This()) [*]u8 { + fn bufContent(self: *Self) [*]u8 { std.debug.assert(!self.is_ptr); return @ptrCast(self.bufU64() + 1); } - fn bufPointer(self: *@This()) [*]u8 { + fn bufPointer(self: *Self) [*]u8 { std.debug.assert(self.is_ptr); std.debug.assert(self.len_or_ptr == 0); return @ptrFromInt(self.bufU64()[1]); } - fn eltSize(self: *@This()) u16 { + fn eltSize(self: *Self) u16 { std.debug.assert(!self.is_ptr); return switch (self.type) { .str => 1, @@ -104,18 +106,18 @@ pub const ArrayHeader = packed struct(u64) { }; } - fn size(self: *@This()) usize { + fn size(self: *Self) usize { std.debug.assert(!self.is_ptr); return self.len_or_ptr * self.eltSize(); } - fn arrPointer(self: *@This()) ?ArrayPtr { + fn arrPointer(self: *Self) ?ArrayPtr { std.debug.assert(self.is_ptr); const p = self.len_or_ptr; return if (p != 0) @ptrFromInt(p) else null; } - fn sliceInfo(self: *@This()) [2]u64 { + fn sliceInfo(self: *Self) [2]u64 { std.debug.assert(self.is_slice); const ptr = self.len_or_ptr; const buf = self.bufU64(); @@ -126,7 +128,7 @@ pub const ArrayHeader = packed struct(u64) { } } - pub fn bufU8(self: *@This()) [*]u8 { + pub fn bufU8(self: *Self) [*]u8 { if (self.is_ptr) { if (self.arrPointer()) |a| { return a.bufContent(); @@ -138,7 +140,7 @@ pub const ArrayHeader = packed struct(u64) { } } - pub fn str(self: *@This()) []const u8 { + pub fn str(self: *Self) []const u8 { if (self.is_slice) { const buf = self.bufU8(); const start, const end = self.sliceInfo(); diff --git a/src/zisp/value/pair.zig b/src/zisp/value/pair.zig index 4ae38a3..09e50f2 100644 --- a/src/zisp/value/pair.zig +++ b/src/zisp/value/pair.zig @@ -6,7 +6,7 @@ const ptr = @import("ptr.zig"); const PairPool = gc.PairPool; const Value = value.Value; -pub const PairPtr = *align(8) Pair; +pub const PairPtr = *align(@alignOf(value.Zptr)) Pair; pub const Pair = struct { car: Value, diff --git a/src/zisp/value/ptr.zig b/src/zisp/value/ptr.zig index e6f2639..ef12f79 100644 --- a/src/zisp/value/ptr.zig +++ b/src/zisp/value/ptr.zig @@ -7,6 +7,8 @@ const PtrTag = value.PtrTag; const Value = value.Value; const Zptr = value.Zptr; +const max = std.math.maxInt; + // Zig API pub fn check(v: Value) bool { @@ -31,18 +33,18 @@ pub fn assertWeak(v: Value) void { } } -pub fn _pack(comptime tag: PtrTag, ptr: tag.ptrType(), is_weak: bool) Value { +pub fn _pack(comptime tag: PtrTag, ptr: tag.ptype(), is_weak: bool) Value { const ptr_val: usize = @intFromPtr(ptr); std.debug.assert(ptr_val < std.math.maxInt(u48)); const tagged: u48 = @intCast(ptr_val | @intFromEnum(tag)); return .{ .ptr = .{ .tagged_value = tagged, .is_weak = is_weak } }; } -pub fn pack(comptime tag: PtrTag, ptr: tag.ptrType()) Value { +pub fn pack(comptime tag: PtrTag, ptr: tag.ptype()) Value { return _pack(tag, ptr, false); } -pub fn packWeak(comptime tag: PtrTag, ptr: tag.ptrType()) Value { +pub fn packWeak(comptime tag: PtrTag, ptr: tag.ptype()) Value { return _pack(tag, ptr, true); } @@ -58,12 +60,7 @@ pub fn isWeakNull(v: Value) bool { pub fn unpack(v: Value) struct { Zptr, PtrTag } { assert(v); - const tagged = v.ptr.tagged_value; - const ptr_val: u48 = tagged & ~@as(u48, 7); - const tag_val: u3 = @intCast(tagged & 7); - const ptr: Zptr = @ptrFromInt(ptr_val); - const tag: PtrTag = @enumFromInt(tag_val); - return .{ ptr, tag }; + return v.getPtrAny() orelse unreachable; } // Zisp API |
