summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaylan Kammer <taylan.kammer@gmail.com>2026-06-08 05:54:07 +0200
committerTaylan Kammer <taylan.kammer@gmail.com>2026-06-08 05:54:07 +0200
commit627a09b58f404b1f690d27869b15b3197207c0af (patch)
tree4e64459ff8462e7f4dfc502158b6ffa629ebb0c8
parent90af1ed17d317435fb56ea041fa0d937f5043726 (diff)
Code cleanup and make heap alloc 16-byte aligned.
-rw-r--r--src/zisp/gc/PairPool.zig7
-rw-r--r--src/zisp/value.zig43
-rw-r--r--src/zisp/value/array.zig20
-rw-r--r--src/zisp/value/pair.zig2
-rw-r--r--src/zisp/value/ptr.zig15
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