commit ccee14b0d2855d4b35f1e6fdabe8e44974b6d902
parent acdb947a6e36ec1d3f6cda2f2d1d2ee46367e697
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date: Fri, 2 Oct 2026 21:27:08 +0200
Fancy nanbox
Diffstat:
3 files changed, 194 insertions(+), 92 deletions(-)
diff --git a/zlox/src/lib::nanbox.zig b/zlox/src/lib::nanbox.zig
@@ -1,18 +1,75 @@
const std = @import("std");
const utils = @import("lib::utils.zig");
-pub fn pun(Ret: type, val: anytype) Ret {
- if (@alignOf(@TypeOf(val)) < @alignOf(Ret)) {
- var ret: Ret = undefined;
- @as(*@TypeOf(val), @ptrCast(&ret)).* = val;
- return ret;
- } else {
- return @as(*const Ret, @ptrCast(&val)).*;
+const Flag = struct {
+ flag: u64,
+ mask: u64,
+ true: bool = true,
+ shift: u6 = 0,
+
+ pub fn check(self: *const @This(), val: u64) bool {
+ return self.true == ((val & self.mask) == self.flag);
}
-}
-fn expand(tag: u64) u64 {
- return ((0b100 & tag) << 61) | ((0b11 & tag) << 48);
+ pub fn decode(self: *const @This(), val: u64) u64 {
+ return if (self.true) (val & ~self.mask) << self.shift else val;
+ }
+
+ pub fn encode(self: *const @This(), val: u64) u64 {
+ return if (self.true) ((val >> self.shift) & ~self.mask) | self.flag else val;
+ }
+};
+
+pub fn Packer(comptime MAX: u6) type {
+ const MAXU64: u64 = std.math.maxInt(u64);
+ const MASK: u64 = ~(MAXU64 << MAX);
+
+ return struct {
+ matrix: [MAX]?u64,
+
+ pub fn init() @This() {
+ return .{ .matrix = @splat(0) };
+ }
+
+ fn mask(idx: u6) u64 {
+ return MASK & (MAXU64 << idx);
+ }
+
+ fn inc(val: u64, idx: u6) ?u64 {
+ return if ((val & mask(idx)) == mask(idx))
+ null
+ else
+ val + (@as(u64, 1) << idx);
+ }
+
+ pub fn next(self: *@This(), idx: u6) ?u64 {
+ if (self.matrix[idx]) |ret| {
+ var i: u6 = idx;
+ while (i < MAX) : (i += 1) {
+ if (self.matrix[i]) |val| {
+ if (val == (ret & mask(i)))
+ self.matrix[i] = inc(val, i)
+ else
+ break;
+ } else break;
+ }
+
+ i = idx;
+ while (i > 0) {
+ i -= 1;
+ if (self.matrix[i]) |val| {
+ if (val == (ret & mask(i)))
+ self.matrix[i] = self.matrix[idx]
+ else
+ break;
+ } else @panic("The Matrix has been breached!");
+ }
+
+ return ret;
+ }
+ return null;
+ }
+ };
}
pub fn NaNBox(comptime fields: anytype) type {
@@ -21,117 +78,86 @@ pub fn NaNBox(comptime fields: anytype) type {
if (!utils.is_type(Fields, "struct"))
@compileError("The fields have to be a struct of types");
- const info = @typeInfo(Fields).@"struct";
- const enum_f64 = 0xcafe;
+ const flags = blk: {
+ const info = @typeInfo(Fields).@"struct";
+
+ var flags: [info.field_names.len]Flag = undefined;
+
+ var float = false;
- const enums, const HAS_PTR = blk: {
- var enums: [info.field_names.len]u64 = undefined;
- var floated = false;
+ const QNAN: u64 = 0x7ffc000000000000;
+ const SIGN: u64 = 0x8000000000000000;
- var e_val: u64 = 0;
- var e_ptr: u64 = 0;
+ const Pack = Packer(51);
+ var packer = Pack.init();
- inline for (info.field_names, info.field_types, &enums) |nam, typ, *enm| {
- if (typ != type)
- @compileError("The fields have to be types, but field " ++ nam ++ " is " ++ @typeName(typ));
+ inline for (info.field_names, info.field_types, &flags) |name, ftype, *flag| {
+ if (ftype != type)
+ @compileError("The fields have to be types, but field " ++ name ++ " is " ++ @typeName(ftype));
- const field = @field(fields, nam);
- const bitsz = @bitSizeOf(field);
+ const field = @field(fields, name);
+ const is_ptr = utils.is_type(field, "pointer");
if (field == f64) {
- if (floated)
+ if (float)
@compileError("There can only be one f64 field");
- enm.* = enum_f64;
- floated = true;
- } else if (utils.is_type(field, "pointer")) {
- if (e_ptr == 0 and e_val == 8)
- @compileError("This NaNBox already has 8 non-pointer types");
- if (e_ptr == 8)
- @compileError("This NaNBox already has 8 pointer types");
- enm.* = expand(0b111) | e_ptr;
- e_ptr += 1;
- } else if (bitsz < 48) {
- if (e_val == 8)
- @compileError("This NaNBox already has 8 non-pointer types");
- if (e_ptr > 0 and e_val == 7)
- @compileError("This NaNBox already has 7 non-pointer types and at least 1 pointer type");
- enm.* = expand(e_val);
- e_val += 1;
+
+ flag.* = .{ .flag = QNAN, .mask = QNAN, .true = false };
+ float = true;
+ } else if (is_ptr or @bitSizeOf(field) < 51) {
+ const bitsz: u6 = if (is_ptr) 45 else @bitSizeOf(field);
+
+ if (packer.next(bitsz)) |pack| {
+ flag.* = Flag{
+ .flag = (SIGN & (pack << 13)) | QNAN | pack,
+ .mask = SIGN | QNAN | Pack.mask(bitsz),
+ .shift = if (is_ptr) 3 else 0,
+ };
+ } else {
+ @compileError("Too many fields to fit");
+ }
} else {
- @compileError("The field has to fit in 48bits but field " ++ nam ++ " is " ++ @typeName(field));
+ @compileError("The field " ++ name ++ " has more than 50 bits");
}
}
- if (!floated)
- @compileError("At least one f64 is required");
+ if (!float)
+ @compileError("At least one f64 field is required");
- break :blk .{ enums, e_ptr > 0 };
+ break :blk flags;
};
return struct {
const Self = @This();
- const QNAN: u64 = 0x7ffc000000000000;
- const TAG: u64 = 0x8003000000000000;
- const PTR_TAG: u64 = 0b111;
- const VALUE: u64 = ~(QNAN | TAG);
- const PTR_VALUE: u64 = ~(QNAN | TAG | PTR_TAG);
-
- pub const Type = @Enum(
- u64,
- std.lang.Type.Enum.Mode.exhaustive,
- info.field_names,
- &enums,
- );
-
+ pub const Type = utils.enumFromStruct(Fields, usize);
pub const Error = error{WrongTag};
value: u64,
- fn from(comptime tag: Type) type {
- return @field(fields, @tagName(tag));
+ pub fn flag(comptime tag: Type) Flag {
+ return flags[@backingInt(tag)];
}
- fn unmask(m: u64) u64 {
- const ret = (m & 0x8000000000000000) | (m & 0x3000000000000);
- return if (HAS_PTR and ret == TAG) ret | (m & PTR_TAG) else ret;
+ pub fn typeof(comptime tag: Type) type {
+ return @field(fields, @tagName(tag));
}
- pub fn of(comptime tag: Type, val: from(tag)) Self {
+ pub fn of(comptime tag: Type, val: typeof(tag)) Self {
return Self{
- .value = if (from(tag) == f64)
- pun(u64, val)
- else
- QNAN | @backingInt(tag) | (pun(u64, val) & VALUE),
+ .value = flag(tag).encode(utils.typepun(u64, val)),
};
}
- pub fn to(self: *const Self, comptime tag: Type) !from(tag) {
- std.debug.print("Msk {b:064} {s}\n", .{ @backingInt(tag), @tagName(tag) });
- std.debug.print("U64 {b:064}\nTag {b:064}\nVal {b:064}\n", .{ self.value, unmask(self.value), self.value & VALUE });
-
- return if (self.is() == tag)
- self.as(from(tag))
+ pub fn to(self: *const Self, comptime tag: Type) !typeof(tag) {
+ return if (self.is(tag))
+ utils.typepun(typeof(tag), flag(tag).decode(self.value))
else
Error.WrongTag;
}
- pub fn is(self: *const Self) Type {
- return if ((self.value & QNAN) != QNAN)
- @fromBackingInt(enum_f64)
- else
- @fromBackingInt(unmask(self.value));
- }
-
- fn as(self: *const Self, typ: type) typ {
- return if (typ == f64)
- pun(f64, self.value)
- else if (typ == void)
- @as(void, {})
- else if (utils.is_type(typ, "pointer"))
- pun(typ, self.value & PTR_VALUE)
- else
- pun(typ, self.value & VALUE);
+ pub fn is(self: *const Self, comptime tag: Type) bool {
+ return flag(tag).check(self.value);
}
};
}
diff --git a/zlox/src/lib::utils.zig b/zlox/src/lib::utils.zig
@@ -154,3 +154,13 @@ pub fn enumFromStruct(str: type, backing: type) type {
&field_values,
);
}
+
+pub fn typepun(Ret: type, val: anytype) Ret {
+ if (@alignOf(@TypeOf(val)) < @alignOf(Ret)) {
+ var ret: Ret = undefined;
+ @as(*@TypeOf(val), @ptrCast(&ret)).* = val;
+ return ret;
+ } else {
+ return @as(*const Ret, @ptrCast(&val)).*;
+ }
+}
diff --git a/zlox/typun.zig b/zlox/typun.zig
@@ -1,20 +1,86 @@
const std = @import("std");
+const utils = @import("src/lib::utils.zig");
+const assert = std.debug.assert;
const NaNBox = @import("src/lib::nanbox.zig").NaNBox;
+const Packer = @import("src/lib::nanbox.zig").Packer;
-pub fn main(_: std.process.Init) anyerror!u8 {
+fn typetypeof(Box: type, comptime tag: Box.Type) type {
+ const T = Box.typeof(tag);
+
+ return if (utils.is_type(T, "pointer"))
+ @typeInfo(T).pointer.child
+ else
+ T;
+}
+
+pub fn dbg(box: anytype, comptime tag: @TypeOf(box).Type) @TypeOf(box) {
+ const Box = @TypeOf(box);
+
+ std.debug.print("U64: {b:064}\n", .{box.value});
+ std.debug.print("Flg: {b:064}\n", .{Box.flag(tag).flag});
+ std.debug.print("Msk: {b:064}\n", .{Box.flag(tag).mask});
+ std.debug.print("Dec: {b:064}\n", .{Box.flag(tag).decode(box.value)});
+ std.debug.print("Tru: {any}\n", .{Box.flag(tag).true});
+ std.debug.print("Chk: {any}\n", .{Box.flag(tag).check(box.value)});
+
+ return box;
+}
+
+pub fn tst(Box: type, comptime tag: Box.Type, val: typetypeof(Box, tag)) !void {
+ const T = Box.typeof(tag);
+
+ std.debug.print("Test tag {s}, type {s}, value {any}\n", .{ @tagName(tag), @typeName(T), val });
+
+ const ret = if (comptime utils.is_type(T, "pointer"))
+ (try dbg(Box.of(tag, &val), tag).to(tag)).*
+ else
+ try dbg(Box.of(tag, val), tag).to(tag);
+
+ assert(ret == val);
+}
+
+pub fn test1() !void {
const Box = NaNBox(.{
.u32 = u32,
+ .i24 = i24,
.void = void,
.pf64 = *const f64,
.f64 = f64,
.pf32 = *const f32,
+ .true = void,
+ .false = void,
});
- const p: f32 = 123;
+ try tst(Box, .u32, 123);
+ try tst(Box, .pf32, 123);
+ try tst(Box, .i24, -13);
+ try tst(Box, .f64, 14.123);
+ try tst(Box, .void, {});
+ try tst(Box, .true, {});
+ try tst(Box, .false, {});
+}
+
+pub fn printpack(comptime max: u6, packer: Packer(max)) void {
+ for (packer.matrix) |val|
+ if (val) |v|
+ std.debug.print("{b:0" ++ std.fmt.comptimePrint("{d}", .{max}) ++ "} ", .{v})
+ else
+ std.debug.print("{s:" ++ std.fmt.comptimePrint("{d}", .{max}) ++ "} ", .{"null"});
+ std.debug.print("\n", .{});
+}
+
+pub fn main(_: std.process.Init) anyerror!u8 {
+ var packer = Packer(5).init();
- const box = Box.of(.pf32, &p);
+ printpack(5, packer);
+ _ = packer.next(4);
+ printpack(5, packer);
+ _ = packer.next(2);
+ printpack(5, packer);
+ _ = packer.next(3);
+ printpack(5, packer);
- std.debug.print("Box contains: {any}\n", .{(try box.to(.pf32)).*});
+ try test1();
return 0;
}