DzLox

:)
git clone https://git.sr.ht/~ashymad/DzLox
Log | Files | Refs | Submodules | LICENSE

commit b7e7127585b30cb5b1f3148c3462e4958616a10c
parent 11a446bd9f6769c66417fde56bdbbbd5fb11c607
Author: Szymon Mikulicz <szymon.mikulicz@posteo.net>
Date:   Tue, 25 Aug 2026 22:12:13 +0200

GC with heap tracking

Diffstat:
Mzlox/src/gc.zig | 83++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mzlox/src/obj.zig | 16+++++++++++++++-
Mzlox/src/obj::string.zig | 117+++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
3 files changed, 129 insertions(+), 87 deletions(-)

diff --git a/zlox/src/gc.zig b/zlox/src/gc.zig @@ -27,15 +27,18 @@ pub const GC = struct { } }; - const DBG_STRESS = true; + const DBG_STRESS = false; const DBG_LOG = true; + const GC_HEAP_GROW_FACTOR = 2; allocator: std.mem.Allocator, io: std.Io, - table: Obj.String.Table, + pool: Obj.String.Pool, objs: ObjList, callbacks: CallbackList, greys: GreyList, + allocated: usize, + next: usize, fn dbg_print(comptime fmt: []const u8, args: anytype) void { if (DBG_LOG) { @@ -47,10 +50,12 @@ pub const GC = struct { return Self{ .allocator = allocator, .io = io, - .table = Obj.String.Table.init(allocator), + .pool = Obj.String.Pool.init(allocator), .objs = ObjList.init(allocator), .callbacks = CallbackList.init(allocator), .greys = GreyList.init(allocator), + .allocated = 0, + .next = 1024 * 1024, }; } @@ -60,6 +65,7 @@ pub const GC = struct { self.trace_references(); self.table_remove_white(); self.sweep(); + self.next = self.allocated * GC_HEAP_GROW_FACTOR; } } @@ -119,12 +125,14 @@ pub const GC = struct { } fn table_remove_white(self: *Self) void { - const tbl = &self.table; - tbl.for_each(tbl, struct { - pub fn fun(table: *Obj.String.Table, key: Obj.String.Table.Key, _: Obj.String.Table.Value) void { + const Table = Obj.String.Pool.Table; + const table = &self.pool.table; + + table.for_each(table, struct { + pub fn fun(tbl: *Table, key: Table.Key, _: Table.Value) void { const obj = key.cast(); if (obj.fields.gc and !obj.fields.mark) - _ = table.delete(key); + _ = tbl.delete(key); } }.fun); } @@ -137,6 +145,9 @@ pub const GC = struct { iter.pop(); dbg_obj("O", "free", obj, false); obj.free(self.allocator); + switch (obj.type) { + inline else => |tp| self.allocated -= @sizeOf(tp.get()) + if (tp == .String) (obj.cast(tp) catch unreachable).len else 0, + } } else { obj.fields.mark = false; } @@ -175,47 +186,37 @@ pub const GC = struct { } } - pub fn emplace(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) (ObjList.Error || tp.get().Error)!*tp.get() { - if (DBG_STRESS) { + pub fn emplace(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) (ObjList.Error || tp.get().Error || Obj.String.Pool.Error)!*tp.get() { + if (tp == .String) + if (self.pool.find(arg)) |obj| + return obj; + + const chd = try tp.get().init(arg, self.allocator); + + self.allocated += @sizeOf(tp.get()) + if (tp == .String) chd.len else 0; + if (DBG_STRESS or self.allocated > self.next) { self.collect(); } - var newObj = true; - const obj = switch (tp) { - .String => try Obj.String.intern(arg, &self.table, &newObj, self.allocator), - else => try tp.get().init(arg, self.allocator), - }; + if (tp == .String) + try self.pool.put(chd); - if (newObj) { - const obj_p = obj.cast(); + const obj = chd.cast(); + dbg_obj("O", "new", obj, true); + dbg_print("{d}/{d}\n", .{ self.allocated, self.next }); - dbg_obj("O", "new", obj_p, true); - try self.objs.push(0, obj_p); - } + try self.objs.push(0, obj); - return obj; + return chd; } pub fn mark(self: *Self, msg: []const u8, arg: anytype) void { - const T = @TypeOf(arg); - - switch (T) { - Value => switch (arg) { - .obj => |o| { - self.mark(msg, o); - }, - else => {}, - }, - *Obj => if (arg.fields.gc and !arg.fields.mark) { - dbg_obj(msg, "mark", arg, true); - arg.fields.mark = true; - self.greys.push(-1, arg) catch @panic("Grey stack overflow"); - }, - else => if (comptime Obj.is_child(T)) { - self.mark(msg, arg.cast()); - } else { - @compileError("Unable to mark " ++ @typeName(T)); - }, + if (Obj.from(arg)) |obj| { + if (obj.fields.gc and !obj.fields.mark) { + dbg_obj(msg, "mark", obj, true); + obj.fields.mark = true; + self.greys.push(-1, obj) catch @panic("Grey stack overflow"); + } } } @@ -223,7 +224,7 @@ pub const GC = struct { obj.fields.gc = false; } - pub fn emplace_cast(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) (ObjList.Error || tp.get().Error)!*Obj { + pub fn emplace_cast(self: *Self, comptime tp: Obj.Type, arg: tp.get().Arg) !*Obj { return (try self.emplace(tp, arg)).cast(); } @@ -236,6 +237,6 @@ pub const GC = struct { el.free(self.allocator); } self.objs.free(); - self.table.deinit(); + self.pool.free(); } }; diff --git a/zlox/src/obj.zig b/zlox/src/obj.zig @@ -1,6 +1,7 @@ const std = @import("std"); const utils = @import("lib::utils.zig"); +const Value = @import("value.zig").Value; pub fn Obj(fields: anytype) type { return packed struct { @@ -28,7 +29,7 @@ pub fn Obj(fields: anytype) type { Closure, Upvalue, - pub fn get(comptime self: @This()) type { + pub fn get(self: @This()) type { return @field(Self, @tagName(self)); } }; @@ -76,6 +77,19 @@ pub fn Obj(fields: anytype) type { }; } + pub fn from(arg: anytype) ?*Self { + const T = @TypeOf(arg); + + return switch (T) { + Value => switch (arg) { + .obj => |o| Self.from(o), + else => null, + }, + *Self => arg, + else => if (comptime Self.is_child(T)) arg.cast() else null, + }; + } + pub fn is(self: *const Self, tp: Type) bool { return self.type == tp; } diff --git a/zlox/src/obj::string.zig b/zlox/src/obj::string.zig @@ -12,9 +12,63 @@ pub fn String(fields: anytype) type { return packed struct { const Self = @This(); - pub const Table = table.Table(*Self, void, hash.hash_t(*const Self), Self.eql); + pub const Arg = []const []const u8; - pub const Error = error{ OutOfMemory, IndexOutOfBounds } || Table.Error; + pub const Error = error{ OutOfMemory, IndexOutOfBounds }; + + pub const Pool = struct { + pub const Table = table.Table(*Self, void, hash.hash_t(*const Self), Self.eql); + pub const Error = Table.Error; + + table: Table, + + pub fn init(allocator: std.mem.Allocator) Pool { + return .{ .table = Table.init(allocator) }; + } + + fn check(arg: Arg, len: usize, hsh: u32) struct { + arg: Arg, + len: usize, + hash: u32, + + pub fn check(self: *const @This(), other: *const Self) bool { + if (other.hash == self.hash and other.len == self.len) { + var idx: usize = 0; + for (self.arg) |el| { + if (!std.mem.eql(u8, other.data()[idx .. idx + el.len], el)) + return false; + idx += el.len; + } + return true; + } + return false; + } + } { + return @TypeOf(check(arg, len, hsh)){ + .arg = arg, + .len = len, + .hash = hsh, + }; + } + + pub fn put(self: *Pool, str: *Self) !void { + _ = try self.table.set(str, {}); + } + + pub fn find(self: *Pool, arg: Arg) ?*Self { + if (self.table.count == 0) return null; + + const pre = Self.prehash(arg); + + const entry = Table.find_(self.table.entries, pre.hash, check(arg, pre.len, pre.hash)); + + return if (entry.* != .some) null else entry.some.key; + } + + pub fn free(self: *Pool) void { + self.table.deinit(); + } + }; obj: Super, len: usize = 0, @@ -25,11 +79,11 @@ pub fn String(fields: anytype) type { return p + @sizeOf(Self); } - fn new(arg: Arg, params: ArgParams, allocator: std.mem.Allocator) Error!*Self { - const ret: *Self = @ptrCast(try allocator.alignedAlloc(u8, std.mem.Alignment.of(Self), @sizeOf(Self) + params.len)); + fn new(arg: Arg, len: usize, hsh: u32, allocator: std.mem.Allocator) Error!*Self { + const ret: *Self = @ptrCast(try allocator.alignedAlloc(u8, std.mem.Alignment.of(Self), @sizeOf(Self) + len)); ret.* = Self{ .obj = Super.make(Self), - .hash = params.hash, + .hash = hsh, }; for (arg) |el| { @memcpy(ret.data() + ret.len, el); @@ -53,57 +107,30 @@ pub fn String(fields: anytype) type { return @intFromPtr(self) == @intFromPtr(other); } pub fn get(self: *const Self, index: value.Value) Error!value.Value { - if (!index.is(value.Value.number) or index.number >= @as(value.Value.tagType(value.Value.number), @floatFromInt(self.len)) or index.number < 0) { + if (!index.is(value.Value.number) or index.number >= @as( + value.Value.tagType(value.Value.number), + @floatFromInt(self.len), + ) or index.number < 0) { return Error.IndexOutOfBounds; } return value.Value.init(self.data()[@intFromFloat(index.number)]); } - const ArgParams = struct { len: usize, hash: u32 }; - - fn table_check(m_arg: Arg, m_params: ArgParams) struct { - arg: Arg, - params: ArgParams, - pub fn check(self: *const @This(), k2: *const Self) bool { - if (k2.hash == self.params.hash and k2.len == self.params.len) { - var idx: usize = 0; - for (self.arg) |el| { - if (!std.mem.eql(u8, k2.data()[idx .. idx + el.len], el)) - return false; - idx += el.len; - } - return true; - } - return false; - } - } { - return @TypeOf(table_check(m_arg, m_params)){ .arg = m_arg, .params = m_params }; - } - - fn arg_params(arg: Arg) ArgParams { - var ret = ArgParams{ .len = 0, .hash = hash.hash_t([]const u8)(&.{}) }; + fn prehash(arg: Arg) struct { len: usize, hash: u32 } { + var len: usize = 0; + var hsh = hash.hash_t([]const u8)(&.{}); for (arg) |el| { - ret.len += el.len; - ret.hash = hash.hash_append(ret.hash, el); + len += el.len; + hsh = hash.hash_append(hsh, el); } - return ret; + return .{ .len = len, .hash = hsh }; } - pub fn intern(arg: Arg, tabl: *Self.Table, isNewKey: *bool, allocator: std.mem.Allocator) Error!*Self { - const params = arg_params(arg); - - try tabl.checkCapacity(); - const entry = Self.Table.find_(tabl.entries, params.hash, table_check(arg, params)); - isNewKey.* = entry.* != Self.Table.Entry.some; - if (isNewKey.*) { - _ = tabl.set_(entry, try new(arg, params, allocator), {}); - } - return entry.some.key; - } + pub fn init(arg: Arg, allocator: std.mem.Allocator) Error!*Self { + const pre = Self.prehash(arg); - pub fn init(_: Arg, _: std.mem.Allocator) Error!*Self { - @compileError("The String Obj has to be interned"); + return new(arg, pre.len, pre.hash, allocator); } pub fn free(self: *const Self, allocator: std.mem.Allocator) void {