DzLox

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

commit f97fe0c55e92b6097a11fa61b07c66935baa6fc3
parent 97c253e1bb720ec2b6c5a44bd973dbb62557c8df
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date:   Thu, 29 Aug 2024 10:07:01 +0200

Closures finished

Diffstat:
Mzlox/src/chunk.zig | 1+
Mzlox/src/compiler.zig | 26++++++++++++++++----------
Mzlox/src/debug.zig | 35+++++++++++++++++++++--------------
Mzlox/src/hash.zig | 3+--
Mzlox/src/list.zig | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mzlox/src/main.zig | 2+-
Mzlox/src/obj/table.zig | 5-----
Mzlox/src/obj/upvalue.zig | 23+++++++++++++++++++----
Mzlox/src/vm.zig | 56++++++++++++++++++++++++++++++++++++++++++++++++--------
9 files changed, 168 insertions(+), 44 deletions(-)

diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig @@ -36,6 +36,7 @@ pub const OP = enum(u8) { GET_INDEX, CALL, CLOSURE, + CLOSE_UPVALUE }; pub const Chunk = struct { diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig @@ -60,9 +60,10 @@ pub fn Compiler(size: comptime_int) type { const upvalues_size = std.math.maxInt(u8); const Local = struct { - name: scanner.Token, - depth: ?usize, - con: bool, + name: scanner.Token = scanner.Token.Empty, + depth: ?usize = null, + con: bool = true, + captured: bool = false, }; const ParseFn = *const fn (*Self, bool) void; @@ -310,6 +311,7 @@ pub fn Compiler(size: comptime_int) type { } while(!self.match(Token.RIGHT_BRACKET)) { self.consume(Token.COMMA, "Expect ',' between expressions"); + if (self.match(Token.RIGHT_BRACKET)) break; self.expression(); argCount += 1; if(!isList) { @@ -364,8 +366,8 @@ pub fn Compiler(size: comptime_int) type { fn resolveUpvalue(self: *Self, name: scanner.Token) ?u8 { if (self.enclosing) |enclosing| { - std.debug.print("Resolving '{s}' in '{s}'\n", .{name.lexeme, enclosing.currentFunction}); if (enclosing.resolveLocal(name)) |local| { + enclosing.locals[local].captured = true; return self.addUpvalue(local, true) catch null; } else if (enclosing.resolveUpvalue(name)) |upvalue| { return self.addUpvalue(upvalue, false) catch null; @@ -379,7 +381,6 @@ pub fn Compiler(size: comptime_int) type { for (self.upvalues[0..count], 0..) |upvalue, i| { if (upvalue.index == idx and upvalue.isLocal == isLocal) { - std.debug.print("Found existing at {d}\n", .{i}); return @intCast(i); } } @@ -390,7 +391,6 @@ pub fn Compiler(size: comptime_int) type { return self.lastError; } - std.debug.print("New {s} upvalue in {s} idx {d} at {d} \n", .{if (isLocal) "local" else "upvalue", self.currentFunction, idx, count}); self.upvalues[count] = .{.index = idx, .isLocal = isLocal}; self.currentFunction.upvalue_count += 1; return count; @@ -639,7 +639,7 @@ pub fn Compiler(size: comptime_int) type { self.errorAt(name, "Too many variables in function"); return; } - self.locals[self.localCount] = Local {.name = name, .depth = null, .con = con}; + self.locals[self.localCount] = Local {.name = name, .con = con}; self.localCount += 1; } @@ -912,7 +912,11 @@ pub fn Compiler(size: comptime_int) type { } else { self.errorAt(self.locals[self.localCount-1].name, "Unitialized variable at scope end"); } - self.emitOP(OP.POP); + if (self.locals[self.localCount-1].captured) { + self.emitOP(OP.CLOSE_UPVALUE); + } else { + self.emitOP(OP.POP); + } self.localCount -= 1; } } @@ -940,7 +944,7 @@ pub fn Compiler(size: comptime_int) type { } fn init(scan: *scanner.Scanner, objects: *GC, fun: *Obj.Function) Self { - return Self{ + var self = Self{ .scanner = scan, .current = scanner.Token.Empty, .previous = scanner.Token.Empty, @@ -949,12 +953,14 @@ pub fn Compiler(size: comptime_int) type { .lastError = scanner.ScannerError.EmptyToken, .currentFunction = fun, .objects = objects, - .locals = [_]Local{Local{.name = scanner.Token.Empty, .depth = 0, .con = true}} ** size, + .locals = [_]Local{Local{}} ** size, .localCount = 1, .scopeDepth = 0, .enclosing = null, .upvalues = [_]Upvalue{Upvalue{.index = 0, .isLocal = false}} ** upvalues_size, }; + self.locals[0].depth = 0; + return self; } fn init_enclosed(enclosing: *Self, fun: *Obj.Function) Self { diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig @@ -12,7 +12,7 @@ pub fn disassembleChunk(ch: *const chunk.Chunk, name: []const u8) Error!void { var offset: usize = 0; while (offset < ch.code.len) { - offset = try disassembleInstruction(ch, offset); + offset = try _disassembleInstruction(ch, offset, true); } print("\\= {s} =/\n", .{name}); } @@ -27,6 +27,10 @@ pub fn print_offset(ch: *const chunk.Chunk, offset: usize) !void { } pub fn disassembleInstruction(ch: *const chunk.Chunk, offset: usize) Error!usize { + return _disassembleInstruction(ch, offset, false); +} + +fn _disassembleInstruction(ch: *const chunk.Chunk, offset: usize, print_fn: bool) Error!usize { try print_offset(ch, offset); const OP = chunk.OP; @@ -47,11 +51,11 @@ pub fn disassembleInstruction(ch: *const chunk.Chunk, offset: usize) Error!usize @intFromEnum(OP.GREATER) => simpleInstruction(name, offset), @intFromEnum(OP.NIL) => simpleInstruction(name, offset), @intFromEnum(OP.NOT) => simpleInstruction(name, offset), - @intFromEnum(OP.CONSTANT) => try constantInstruction(name, ch, offset), - @intFromEnum(OP.DEFINE_GLOBAL) => try constantInstruction(name, ch, offset), - @intFromEnum(OP.DEFINE_GLOBAL_CONSTANT) => try constantInstruction(name, ch, offset), - @intFromEnum(OP.GET_GLOBAL) => try constantInstruction(name, ch, offset), - @intFromEnum(OP.SET_GLOBAL) => try constantInstruction(name, ch, offset), + @intFromEnum(OP.CONSTANT) => try constantInstruction(name, ch, offset, print_fn), + @intFromEnum(OP.DEFINE_GLOBAL) => try constantInstruction(name, ch, offset, print_fn), + @intFromEnum(OP.DEFINE_GLOBAL_CONSTANT) => try constantInstruction(name, ch, offset, print_fn), + @intFromEnum(OP.GET_GLOBAL) => try constantInstruction(name, ch, offset, print_fn), + @intFromEnum(OP.SET_GLOBAL) => try constantInstruction(name, ch, offset, print_fn), @intFromEnum(OP.PRINT) => simpleInstruction(name, offset), @intFromEnum(OP.POP) => simpleInstruction(name, offset), @intFromEnum(OP.GET_LOCAL) => try byteInstruction(name, ch, offset), @@ -65,7 +69,8 @@ pub fn disassembleInstruction(ch: *const chunk.Chunk, offset: usize) Error!usize @intFromEnum(OP.SET_INDEX) => simpleInstruction(name, offset), @intFromEnum(OP.GET_INDEX) => simpleInstruction(name, offset), @intFromEnum(OP.CALL) => try byteInstruction(name, ch, offset), - @intFromEnum(OP.CLOSURE) => try closureInstruction(name, ch, offset), + @intFromEnum(OP.CLOSURE) => try closureInstruction(name, ch, offset, print_fn), + @intFromEnum(OP.CLOSE_UPVALUE) => simpleInstruction(name, offset), else => blk: { print("Unknown opcode {d} {s}\n", .{op, name}); break :blk offset + 1; @@ -78,11 +83,11 @@ fn simpleInstruction(name: []const u8, offset: usize) usize { return offset + 1; } -fn constantInstruction(name: []const u8, ch: *const chunk.Chunk, offset: usize) Error!usize { +fn constantInstruction(name: []const u8, ch: *const chunk.Chunk, offset: usize, print_fn: bool) Error!usize { const constant = try ch.code.get(offset + 1); const constval = try ch.constants.get(constant); print("{s:<32} {d:4} '{s}'\n", .{ name, constant, constval}); - if (constval.is(Obj.Type.Function)) { + if (print_fn and constval.is(Obj.Type.Function)) { const function = constval.obj.cast(.Function) catch unreachable; if (function.name) |str| { try disassembleChunk(function.chunk, str.slice()); @@ -108,7 +113,7 @@ fn jumpInstruction(name: []const u8, sign: bool, ch: *const chunk.Chunk, offset: return offset + 3; } -fn closureInstruction(name: []const u8, ch: *const chunk.Chunk, offset: usize) Error!usize { +fn closureInstruction(name: []const u8, ch: *const chunk.Chunk, offset: usize, print_fn: bool) Error!usize { var off = offset + 1; const constant = try ch.code.get(off); const val = try ch.constants.get(constant); @@ -121,10 +126,12 @@ fn closureInstruction(name: []const u8, ch: *const chunk.Chunk, offset: usize) E print("{s:<38}|-> {s} {d}\n", .{ "", if (isLocal == 1) "local" else "upvalue", idx}); off += 2; } - if (function.name) |str| { - try disassembleChunk(function.chunk, str.slice()); - } else { - try disassembleChunk(function.chunk, "<anon>"); + if (print_fn) { + if (function.name) |str| { + try disassembleChunk(function.chunk, str.slice()); + } else { + try disassembleChunk(function.chunk, "<anon>"); + } } return off + 1; diff --git a/zlox/src/hash.zig b/zlox/src/hash.zig @@ -35,7 +35,7 @@ pub fn hash_append(ret: u32, val: anytype) u32 { pub fn hash_t(T: type) fn (T) u32 { return switch (T) { - *Obj.Table, *const Obj.Table, *Obj.String, *const Obj.String => struct { + *Obj.String, *const Obj.String => struct { pub fn fun(val: T) u32 { return val.hash; } @@ -44,7 +44,6 @@ pub fn hash_t(T: type) fn (T) u32 { pub fn fun(val: T) u32 { return switch (val.type) { .String => hash_append_t([]const u8)(hash(val.cast(.String) catch unreachable), "\x01"), - .Table => hash_append_t([]const u8)(hash(val.cast(.Table) catch unreachable), "\x02"), inline else => |tag| @panic("hash_t(Obj." ++ @tagName(tag) ++ "): Unsupported type"), }; } diff --git a/zlox/src/list.zig b/zlox/src/list.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const utils = @import("comptime_utils.zig"); pub fn List(T: type) type { return struct { @@ -112,6 +113,49 @@ pub fn List(T: type) type { } } + pub fn insert_before(self: *Self, element: ?*Element, val: T) Error!void { + if(element) |el| { + if(el.prev) |prev| { + const new = try self.allocator.create(Element); + new.* = .{.val = val, .next = el, .prev = prev}; + prev.next = new; + el.prev = new; + self.len += 1; + return; + } + } + try self.push(val); + } + + pub fn insert_after(self: *Self, element: ?*Element, val: T) Error!void { + if(element) |el| { + if(el.next) |next| { + const new = try self.allocator.create(Element); + new.* = .{.val = val, .next = next, .prev = el}; + next.prev = new; + el.next = new; + self.len += 1; + return; + } + } + try self.push_end(val); + } + + pub fn format(self: *const Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) utils.fn_error(@TypeOf(writer).write)!void { + _ = try writer.write("["); + var end = self.end; + while (end) |el| : (end = el.prev) { + if (el.val) |v| { + try v.format(fmt, options, writer); + } else { + _ = try writer.write("-"); + } + if (el.prev) |_| _ = try writer.write(", "); + } + _ = try writer.writeAll("]"); + } + + fn _pop(self: *Self) Error!?T { if (self.tip) |tip| { if (tip.next) |next| { @@ -154,5 +198,22 @@ pub fn List(T: type) type { pub fn push(self: *Self, val: T) Error!void { return self._push(val); } + + fn _push_end(self: *Self, val: ?T) Error!void { + const new_end = try self.allocator.create(Element); + if (self.tip == null) { + self.tip = new_end; + } + if (self.end) |old_end| { + old_end.next = new_end; + } + new_end.* = Element{.val = val, .prev = self.end, .next = null}; + self.end = new_end; + self.len += 1; + } + + pub fn push_end(self: *Self, val: T) Error!void { + return self._push_end(val); + } }; } diff --git a/zlox/src/main.zig b/zlox/src/main.zig @@ -39,7 +39,7 @@ pub fn runFile(allocator: std.mem.Allocator, path: []const u8) anyerror!void { const text = try file.reader().readAllAlloc(allocator, 999999); defer allocator.free(text); - try VM.interpret(text, true); + try VM.interpret(text, false); } pub fn repl(allocator: std.mem.Allocator, dbg: bool) anyerror!void { diff --git a/zlox/src/obj/table.zig b/zlox/src/obj/table.zig @@ -14,7 +14,6 @@ pub const Table = packed struct { obj: Super, table: *Self.Table, - hash: u32, len: usize, pub fn init(_: Arg, allocator: std.mem.Allocator) Error!*Self { @@ -24,7 +23,6 @@ pub const Table = packed struct { .type = Super.Type.Table, }, .table = try allocator.create(Self.Table), - .hash = 0, .len = 0, }; self.table.* = Self.Table.init(allocator); @@ -36,7 +34,6 @@ pub const Table = packed struct { } pub fn set(self: *Self, key: Value, val: Value) Error!bool { - self.hash +%= hash.hash(key) +% hash.hash(val); self.len += 1; return self.table.set(key, val); } @@ -46,8 +43,6 @@ pub const Table = packed struct { } pub fn delete(self: *Self, key: Value) void { - const val = self.table.get(key) catch return; - self.hash -%= hash.hash(key) -% hash.hash(val); if (self.table.delete(key)) self.len -= 1; } diff --git a/zlox/src/obj/upvalue.zig b/zlox/src/obj/upvalue.zig @@ -7,11 +7,13 @@ const Super = @import("../obj.zig").Obj; pub const Upvalue = packed struct { const Self = @This(); - pub const Arg = *Value; + pub const Arg = struct {val: *Value, slot: u8}; pub const Error = error { OutOfMemory }; obj: Super, location: *Value, + closed: bool, + slot: u8, pub fn init(arg: Arg, allocator: std.mem.Allocator) Error!*Self { const self: *Self = try allocator.create(Self); @@ -19,17 +21,29 @@ pub const Upvalue = packed struct { .obj = Super{ .type = Super.Type.Upvalue, }, - .location = arg, + .location = arg.val, + .closed = false, + .slot = arg.slot }; return self; } + pub fn close(self: *Self, allocator: std.mem.Allocator) Error!void { + if (!self.closed) { + const new = try allocator.create(Value); + new.* = self.location.*; + self.location = new; + self.closed = true; + } + } + + pub fn cast(self: anytype) utils.copy_const(@TypeOf(self), *Super) { return @ptrCast(self); } - pub fn format(_: *const Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { - _ = try writer.writeAll("<upvalue>"); + pub fn format(self: *const Self, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + try writer.print("<Upvalue{{{} at 0x{x}, {}, {}}}>", .{self.location.*, @intFromPtr(self.location), self.closed, self.slot}); } pub fn eql(_: *const Self, _: *const Self) bool { @@ -37,6 +51,7 @@ pub const Upvalue = packed struct { } pub fn free(self: *const Self, allocator: std.mem.Allocator) void { + if (self.closed) allocator.destroy(self.location); allocator.destroy(self); } }; diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig @@ -8,6 +8,7 @@ const Obj = @import("obj.zig").Obj; const GC = @import("gc.zig").GC; const Callback = @import("vm/callbacks.zig"); const table = @import("table.zig"); +const list = @import("list.zig"); const hash = @import("hash.zig"); const utils = @import("comptime_utils.zig"); const vm_native = @import("vm/native.zig"); @@ -103,11 +104,14 @@ pub const VM = struct { fn Interpreter(callstack_size: comptime_int, stack_size: comptime_int) type { return struct { + const List = list.List(*Obj.Upvalue); + frames: [callstack_size]CallFrame, frameCount: usize, stackTop: [*]Value, stack: [stack_size]Value, vm: *VM, + open_upvalues: List, pub fn run(vm: *VM, function: *Obj.Function, dbg: bool) InterpreterError!void { var self = @This(){ @@ -115,8 +119,12 @@ pub const VM = struct { .frameCount = 1, .stack = [_]Value{Value.init({})} ** stack_size, .stackTop = undefined, - .vm = vm + .vm = vm, + .open_upvalues = List.init(vm.allocator) }; + + defer self.open_upvalues.free(); + self.stackTop = &self.stack; self.frames[0] = CallFrame.init(.Function, function, self.stackTop); self.push(Value.init(function.cast())); @@ -220,8 +228,27 @@ pub const VM = struct { return; } - fn captureUpvalue(self: *@This(), local: *Value) !*Obj.Upvalue { - return self.vm.objects.emplace(.Upvalue, local); + fn captureUpvalue(self: *@This(), slot: u8) !*Obj.Upvalue { + var upvalue = self.open_upvalues.tip; + while(upvalue) |el| : (upvalue = el.next) { + const val = el.val.?; + if (val.slot == slot) + return val; + if (val.slot > slot) + break; + } + const new = try self.vm.objects.emplace(.Upvalue, .{.val = &self.frame().slots[slot], .slot = slot}); + try self.open_upvalues.insert_after(upvalue, new); + return new; + } + + fn closeUpvalues(self: *@This(), slot: u8) !void { + while(self.open_upvalues.tip) |el| { + if (el.val.?.slot < slot) break; + + const upval = self.open_upvalues.pop() catch unreachable; + try upval.close(self.vm.allocator); + } } fn binary_op(self: *@This(), comptime in_tag: anytype, comptime out_tag: anytype, op: Callback.Type(in_tag, out_tag)) InterpreterError!void { @@ -239,6 +266,10 @@ pub const VM = struct { return @intFromPtr(self.ip()) - @intFromPtr(self.frame().chunk.code.data.ptr); } + fn current_slot(self: *const @This()) u8 { + return @intCast((@intFromPtr(self.stackTop) - @intFromPtr(self.frame().slots)) / @sizeOf(@TypeOf(self.stackTop[0]))); + } + fn execute(self: *@This(), dbg: bool) !void { while (true) { if (dbg) { @@ -261,6 +292,7 @@ pub const VM = struct { _ = self.pop(); return; } + try self.closeUpvalues(0); self.stackTop = self.frame().slots; self.frameCount -= 1; self.push(result); @@ -323,11 +355,17 @@ pub const VM = struct { }, @intFromEnum(OP.GET_UPVALUE) => { const closure = try self.frame().callee.cast(.Closure); - self.push(closure.upvalues[self.read_byte()].?.location.*); + const index = self.read_byte(); + self.push(closure.upvalues[index].?.location.*); }, @intFromEnum(OP.SET_UPVALUE) => { const closure = try self.frame().callee.cast(.Closure); - closure.upvalues[self.read_byte()].?.location.* = self.peek(0); + const index = self.read_byte(); + closure.upvalues[index].?.location.* = self.peek(0); + }, + @intFromEnum(OP.CLOSE_UPVALUE) => { + try self.closeUpvalues(self.current_slot()); + _ = self.pop(); }, @intFromEnum(OP.GET_INDEX) => { const key = self.pop(); @@ -380,11 +418,13 @@ pub const VM = struct { const function = try self.read_constant().obj.cast(.Function); const closure = try self.vm.objects.emplace(.Closure, function); for(closure.upvalues[0..closure.upvalues_len]) |*upvalue| { - if(self.read_byte() == 1) { - upvalue.* = try self.captureUpvalue(&self.frame().slots[self.read_byte()]); + const isLocal = self.read_byte(); + const slot = self.read_byte(); + if(isLocal == 1) { + upvalue.* = try self.captureUpvalue(slot); } else { const callee = try self.frame().callee.cast(.Closure); - upvalue.* = callee.upvalues[self.read_byte()]; + upvalue.* = callee.upvalues[slot]; } } self.push(Value.init(closure.cast()));