From 96e8b1742c090128b2cfd9a39ce02ab04a997bf2 Mon Sep 17 00:00:00 2001 From: Patrick MARIE Date: Thu, 29 Aug 2024 23:05:49 +0200 Subject: [PATCH] refactor: using linked list for objects --- samples/hello_world.lox | 1 + src/compile.zig | 13 ++--- src/main.zig | 2 +- src/object.zig | 107 ++++++++++++++++------------------------ src/vm.zig | 50 +++++++------------ 5 files changed, 68 insertions(+), 105 deletions(-) create mode 100644 samples/hello_world.lox diff --git a/samples/hello_world.lox b/samples/hello_world.lox new file mode 100644 index 0000000..0ecbd83 --- /dev/null +++ b/samples/hello_world.lox @@ -0,0 +1 @@ +print "hello world!"; diff --git a/src/compile.zig b/src/compile.zig index 8c2d4a2..9831af1 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -326,9 +326,7 @@ const Parser = struct { const str = self.previous.?.start[1 .. self.previous.?.length - 1]; - var string_obj = self.vm.copy_string(str); - - self.vm.add_reference(&string_obj.obj); + const string_obj = self.vm.copy_string(str); try self.emit_constant(Value.obj_val(&string_obj.obj)); } @@ -472,7 +470,6 @@ const Parser = struct { fn identifier_constant(self: *Parser, token: Token) ParsingError!u8 { const copy = &self.vm.copy_string(token.start[0..token.length]).obj; - self.vm.add_reference(copy); return self.make_constant(Value.obj_val(copy)); } @@ -774,7 +771,7 @@ const Parser = struct { } fn function(self: *Parser, function_type: FunctionType) ParsingError!void { - var compiler = Compiler.new(self.vm.allocator, self.compiler, function_type); + var compiler = Compiler.new(self.vm, self.compiler, function_type); self.compiler = &compiler; if (function_type != FunctionType.Script) { @@ -879,8 +876,8 @@ const Compiler = struct { upvalues: [constants.UINT8_COUNT]Upvalue, scope_depth: usize, - fn new(allocator: std.mem.Allocator, enclosing: ?*Compiler, function_type: FunctionType) Compiler { - const obj_function = Obj.Function.new(allocator); + fn new(vm: *VM, enclosing: ?*Compiler, function_type: FunctionType) Compiler { + const obj_function = Obj.Function.new(vm); var compiler = Compiler{ .locals = undefined, @@ -924,7 +921,7 @@ const Upvalue = struct { }; pub fn compile(vm: *VM, contents: []const u8) !?*Obj.Function { - var compiler = Compiler.new(vm.allocator, null, FunctionType.Script); + var compiler = Compiler.new(vm, null, FunctionType.Script); var scanner = Scanner.init(contents); var parser = Parser.new(vm, &compiler, &scanner); diff --git a/src/main.zig b/src/main.zig index 5912a66..07179c6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -55,7 +55,7 @@ pub fn run_file(allocator: Allocator, vm: *VM, filepath: []const u8) !void { } pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = false }){}; + var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){}; defer _ = debug.assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); diff --git a/src/object.zig b/src/object.zig index b540e42..19a7da5 100644 --- a/src/object.zig +++ b/src/object.zig @@ -20,21 +20,40 @@ pub const NativeFn = *const fn (vm: *VM, arg_count: usize, args: []Value) Value; pub const Obj = struct { kind: ObjType, - allocator: std.mem.Allocator, + allocator: Allocator, + next: ?*Obj, + + fn new(comptime T: type, vm: *VM, kind: ObjType) *T { + const created_obj = vm.allocator.create(T) catch unreachable; + + created_obj.obj = Obj{ + .kind = kind, + .allocator = vm.allocator, + .next = vm.objects, + }; + + vm.objects = &created_obj.obj; + + return created_obj; + } + + pub fn destroy(self: *Obj) void { + switch (self.kind) { + ObjType.String => self.as_string().destroy(), + ObjType.Function => self.as_function().destroy(), + ObjType.Native => self.as_native().destroy(), + ObjType.Closure => self.as_closure().destroy(), + ObjType.Upvalue => self.as_upvalue().destroy(), + } + } pub const String = struct { obj: Obj, chars: []const u8, hash: u32, - pub fn new(allocator: std.mem.Allocator, chars: []const u8) *String { - const obj = Obj{ - .kind = ObjType.String, - .allocator = allocator, - }; - - const str_obj = allocator.create(String) catch unreachable; - str_obj.obj = obj; + pub fn new(vm: *VM, chars: []const u8) *String { + const str_obj = Obj.new(String, vm, ObjType.String); str_obj.chars = chars; str_obj.hash = compute_hash(str_obj.chars); @@ -55,17 +74,12 @@ pub const Obj = struct { chunk: *Chunk, name: ?*Obj.String, - pub fn new(allocator: std.mem.Allocator) *Function { - const obj = Obj{ - .kind = ObjType.Function, - .allocator = allocator, - }; + pub fn new(vm: *VM) *Function { + const function_obj = Obj.new(Function, vm, ObjType.Function); - const function_obj = allocator.create(Function) catch unreachable; - function_obj.obj = obj; function_obj.arity = 0; function_obj.upvalue_count = 0; - function_obj.chunk = Chunk.new(allocator); + function_obj.chunk = Chunk.new(vm.allocator); function_obj.name = null; return function_obj; @@ -81,14 +95,9 @@ pub const Obj = struct { obj: Obj, native: NativeFn, - pub fn new(allocator: std.mem.Allocator, native: NativeFn) *Native { - const obj = Obj{ - .kind = ObjType.Native, - .allocator = allocator, - }; + pub fn new(vm: *VM, native: NativeFn) *Native { + const native_obj = Obj.new(Native, vm, ObjType.Native); - const native_obj = allocator.create(Native) catch unreachable; - native_obj.obj = obj; native_obj.native = native; return native_obj; @@ -105,18 +114,13 @@ pub const Obj = struct { upvalues: []?*Obj.Upvalue, upvalue_count: usize, - pub fn new(allocator: std.mem.Allocator, function: *Obj.Function) *Closure { - const obj = Obj{ - .kind = ObjType.Closure, - .allocator = allocator, - }; + pub fn new(vm: *VM, function: *Obj.Function) *Closure { + const closure_obj = Obj.new(Closure, vm, ObjType.Closure); - const closure_obj = allocator.create(Closure) catch unreachable; - closure_obj.obj = obj; closure_obj.function = function; closure_obj.upvalue_count = function.upvalue_count; - closure_obj.upvalues = allocator.alloc(?*Obj.Upvalue, function.upvalue_count) catch unreachable; + closure_obj.upvalues = vm.allocator.alloc(?*Obj.Upvalue, function.upvalue_count) catch unreachable; for (0..function.upvalue_count) |i| { closure_obj.upvalues[i] = null; @@ -137,14 +141,9 @@ pub const Obj = struct { next: ?*Obj.Upvalue, closed: Value, - pub fn new(allocator: std.mem.Allocator, slot: *Value) *Upvalue { - const obj = Obj{ - .kind = ObjType.Upvalue, - .allocator = allocator, - }; + pub fn new(vm: *VM, slot: *Value) *Upvalue { + const upvalue_obj = Obj.new(Upvalue, vm, ObjType.Upvalue); - const upvalue_obj = allocator.create(Upvalue) catch unreachable; - upvalue_obj.obj = obj; upvalue_obj.location = slot; upvalue_obj.next = null; upvalue_obj.closed = Value.nil_val(); @@ -208,31 +207,6 @@ pub const Obj = struct { } } - pub fn destroy(self: *Obj) void { - switch (self.kind) { - ObjType.String => { - const obj: *String = @fieldParentPtr("obj", self); - obj.destroy(); - }, - ObjType.Function => { - const obj: *Function = @fieldParentPtr("obj", self); - obj.destroy(); - }, - ObjType.Native => { - const obj: *Native = @fieldParentPtr("obj", self); - obj.destroy(); - }, - ObjType.Closure => { - const obj: *Closure = @fieldParentPtr("obj", self); - obj.destroy(); - }, - ObjType.Upvalue => { - const obj: *Upvalue = @fieldParentPtr("obj", self); - obj.destroy(); - }, - } - } - pub fn as_string(self: *Obj) *String { std.debug.assert(self.kind == ObjType.String); return @fieldParentPtr("obj", self); @@ -252,4 +226,9 @@ pub const Obj = struct { std.debug.assert(self.kind == ObjType.Closure); return @fieldParentPtr("obj", self); } + + pub fn as_upvalue(self: *Obj) *Upvalue { + std.debug.assert(self.kind == ObjType.Upvalue); + return @fieldParentPtr("obj", self); + } }; diff --git a/src/vm.zig b/src/vm.zig index 95eb3b6..a966d38 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -36,26 +36,24 @@ pub const VM = struct { allocator: Allocator, stack: [constants.STACK_MAX]Value, stack_top: usize, - // Keeping creating objects in references to destroy objects on cleaning. - // In the book, a linked list between objects is used to handle this. - references: std.ArrayList(*Obj), strings: Table, globals: Table, frames: [constants.FRAMES_MAX]CallFrame, frame_count: usize, open_upvalues: ?*Obj.Upvalue, + objects: ?*Obj, pub fn new(allocator: Allocator) VM { return VM{ .allocator = allocator, .stack = undefined, .stack_top = 0, - .references = std.ArrayList(*Obj).init(allocator), .strings = Table.new(allocator), .globals = Table.new(allocator), .frames = undefined, .frame_count = 0, .open_upvalues = null, + .objects = null, }; } @@ -72,8 +70,16 @@ pub const VM = struct { self.strings.destroy(); self.globals.destroy(); - self.clean_references(); - self.references.deinit(); + self.destroy_objects(); + } + + pub fn destroy_objects(self: *VM) void { + var obj = self.objects; + while (obj != null) { + const obj_next = obj.?.next; + obj.?.destroy(); + obj = obj_next; + } } inline fn current_chunk(self: *VM) *Chunk { @@ -89,10 +95,9 @@ pub const VM = struct { if (function == null) { return InterpretResult.COMPILE_ERROR; } - defer function.?.destroy(); _ = try self.push(Value.obj_val(&function.?.obj)); - const closure: *Obj.Closure = Obj.Closure.new(self.allocator, function.?); + const closure: *Obj.Closure = Obj.Closure.new(self, function.?); _ = self.pop(); _ = try self.push(Value.obj_val(&closure.obj)); _ = self.call(closure, 0); @@ -229,7 +234,7 @@ pub const VM = struct { }, @intFromEnum(OpCode.OP_CLOSURE) => { const function = self.read_constant().as_obj().as_function(); - const closure = Obj.Closure.new(self.allocator, function); + const closure = Obj.Closure.new(self, function); _ = try self.push(Value.obj_val(&closure.obj)); for (0..closure.upvalue_count) |i| { const is_local = self.read_byte(); @@ -329,9 +334,7 @@ pub const VM = struct { const concat_str = try std.mem.concat(self.current_chunk().allocator, u8, &.{ a, b }); - var string_obj = self.take_string(concat_str); - - self.add_reference(&string_obj.obj); + const string_obj = self.take_string(concat_str); try self.push(Value.obj_val(&string_obj.obj)); } @@ -365,23 +368,6 @@ pub const VM = struct { } } - pub fn add_reference(self: *VM, obj: *Obj) void { - // do not add duplicate references - for (self.references.items) |item| { - if (item == obj) { - return; - } - } - // XXX TODO catch unreachable to prevents - self.references.append(obj) catch unreachable; - } - - pub fn clean_references(self: *VM) void { - for (self.references.items) |item| { - item.destroy(); - } - } - pub fn copy_string(self: *VM, source: []const u8) *Obj.String { const hash = compute_hash(source); const obj_string = self.strings.find_string(source, hash); @@ -408,7 +394,7 @@ pub const VM = struct { } pub fn allocate_string(self: *VM, source: []const u8) *Obj.String { - const obj_string = Obj.String.new(self.allocator, source); + const obj_string = Obj.String.new(self, source); _ = self.strings.set(obj_string, Value.nil_val()); return obj_string; @@ -465,7 +451,7 @@ pub const VM = struct { pub fn define_native(self: *VM, name: []const u8, native_fn: NativeFn) void { _ = try self.push(Value.obj_val(&self.copy_string(name).obj)); - _ = try self.push(Value.obj_val(&Obj.Native.new(self.allocator, native_fn).obj)); + _ = try self.push(Value.obj_val(&Obj.Native.new(self, native_fn).obj)); _ = self.globals.set(self.stack[0].as_string(), self.stack[1]); @@ -486,7 +472,7 @@ pub const VM = struct { return upvalue.?; } - const created_upvalue = Obj.Upvalue.new(self.allocator, local); + const created_upvalue = Obj.Upvalue.new(self, local); created_upvalue.next = upvalue; if (prev_upvalue == null) {