refactor: using linked list for objects

This commit is contained in:
Patrick MARIE 2024-08-29 23:05:49 +02:00
parent ab00c0f785
commit 96e8b1742c
5 changed files with 68 additions and 105 deletions

1
samples/hello_world.lox Normal file
View File

@ -0,0 +1 @@
print "hello world!";

View File

@ -326,9 +326,7 @@ const Parser = struct {
const str = self.previous.?.start[1 .. self.previous.?.length - 1]; const str = self.previous.?.start[1 .. self.previous.?.length - 1];
var string_obj = self.vm.copy_string(str); const string_obj = self.vm.copy_string(str);
self.vm.add_reference(&string_obj.obj);
try self.emit_constant(Value.obj_val(&string_obj.obj)); 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 { fn identifier_constant(self: *Parser, token: Token) ParsingError!u8 {
const copy = &self.vm.copy_string(token.start[0..token.length]).obj; 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)); return self.make_constant(Value.obj_val(copy));
} }
@ -774,7 +771,7 @@ const Parser = struct {
} }
fn function(self: *Parser, function_type: FunctionType) ParsingError!void { 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; self.compiler = &compiler;
if (function_type != FunctionType.Script) { if (function_type != FunctionType.Script) {
@ -879,8 +876,8 @@ const Compiler = struct {
upvalues: [constants.UINT8_COUNT]Upvalue, upvalues: [constants.UINT8_COUNT]Upvalue,
scope_depth: usize, scope_depth: usize,
fn new(allocator: std.mem.Allocator, enclosing: ?*Compiler, function_type: FunctionType) Compiler { fn new(vm: *VM, enclosing: ?*Compiler, function_type: FunctionType) Compiler {
const obj_function = Obj.Function.new(allocator); const obj_function = Obj.Function.new(vm);
var compiler = Compiler{ var compiler = Compiler{
.locals = undefined, .locals = undefined,
@ -924,7 +921,7 @@ const Upvalue = struct {
}; };
pub fn compile(vm: *VM, contents: []const u8) !?*Obj.Function { 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 scanner = Scanner.init(contents);
var parser = Parser.new(vm, &compiler, &scanner); var parser = Parser.new(vm, &compiler, &scanner);

View File

@ -55,7 +55,7 @@ pub fn run_file(allocator: Allocator, vm: *VM, filepath: []const u8) !void {
} }
pub fn main() !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); defer _ = debug.assert(gpa.deinit() == .ok);
const allocator = gpa.allocator(); const allocator = gpa.allocator();

View File

@ -20,21 +20,40 @@ pub const NativeFn = *const fn (vm: *VM, arg_count: usize, args: []Value) Value;
pub const Obj = struct { pub const Obj = struct {
kind: ObjType, 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 { pub const String = struct {
obj: Obj, obj: Obj,
chars: []const u8, chars: []const u8,
hash: u32, hash: u32,
pub fn new(allocator: std.mem.Allocator, chars: []const u8) *String { pub fn new(vm: *VM, chars: []const u8) *String {
const obj = Obj{ const str_obj = Obj.new(String, vm, ObjType.String);
.kind = ObjType.String,
.allocator = allocator,
};
const str_obj = allocator.create(String) catch unreachable;
str_obj.obj = obj;
str_obj.chars = chars; str_obj.chars = chars;
str_obj.hash = compute_hash(str_obj.chars); str_obj.hash = compute_hash(str_obj.chars);
@ -55,17 +74,12 @@ pub const Obj = struct {
chunk: *Chunk, chunk: *Chunk,
name: ?*Obj.String, name: ?*Obj.String,
pub fn new(allocator: std.mem.Allocator) *Function { pub fn new(vm: *VM) *Function {
const obj = Obj{ const function_obj = Obj.new(Function, vm, ObjType.Function);
.kind = ObjType.Function,
.allocator = allocator,
};
const function_obj = allocator.create(Function) catch unreachable;
function_obj.obj = obj;
function_obj.arity = 0; function_obj.arity = 0;
function_obj.upvalue_count = 0; function_obj.upvalue_count = 0;
function_obj.chunk = Chunk.new(allocator); function_obj.chunk = Chunk.new(vm.allocator);
function_obj.name = null; function_obj.name = null;
return function_obj; return function_obj;
@ -81,14 +95,9 @@ pub const Obj = struct {
obj: Obj, obj: Obj,
native: NativeFn, native: NativeFn,
pub fn new(allocator: std.mem.Allocator, native: NativeFn) *Native { pub fn new(vm: *VM, native: NativeFn) *Native {
const obj = Obj{ const native_obj = Obj.new(Native, vm, ObjType.Native);
.kind = ObjType.Native,
.allocator = allocator,
};
const native_obj = allocator.create(Native) catch unreachable;
native_obj.obj = obj;
native_obj.native = native; native_obj.native = native;
return native_obj; return native_obj;
@ -105,18 +114,13 @@ pub const Obj = struct {
upvalues: []?*Obj.Upvalue, upvalues: []?*Obj.Upvalue,
upvalue_count: usize, upvalue_count: usize,
pub fn new(allocator: std.mem.Allocator, function: *Obj.Function) *Closure { pub fn new(vm: *VM, function: *Obj.Function) *Closure {
const obj = Obj{ const closure_obj = Obj.new(Closure, vm, ObjType.Closure);
.kind = ObjType.Closure,
.allocator = allocator,
};
const closure_obj = allocator.create(Closure) catch unreachable;
closure_obj.obj = obj;
closure_obj.function = function; closure_obj.function = function;
closure_obj.upvalue_count = function.upvalue_count; 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| { for (0..function.upvalue_count) |i| {
closure_obj.upvalues[i] = null; closure_obj.upvalues[i] = null;
@ -137,14 +141,9 @@ pub const Obj = struct {
next: ?*Obj.Upvalue, next: ?*Obj.Upvalue,
closed: Value, closed: Value,
pub fn new(allocator: std.mem.Allocator, slot: *Value) *Upvalue { pub fn new(vm: *VM, slot: *Value) *Upvalue {
const obj = Obj{ const upvalue_obj = Obj.new(Upvalue, vm, ObjType.Upvalue);
.kind = ObjType.Upvalue,
.allocator = allocator,
};
const upvalue_obj = allocator.create(Upvalue) catch unreachable;
upvalue_obj.obj = obj;
upvalue_obj.location = slot; upvalue_obj.location = slot;
upvalue_obj.next = null; upvalue_obj.next = null;
upvalue_obj.closed = Value.nil_val(); 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 { pub fn as_string(self: *Obj) *String {
std.debug.assert(self.kind == ObjType.String); std.debug.assert(self.kind == ObjType.String);
return @fieldParentPtr("obj", self); return @fieldParentPtr("obj", self);
@ -252,4 +226,9 @@ pub const Obj = struct {
std.debug.assert(self.kind == ObjType.Closure); std.debug.assert(self.kind == ObjType.Closure);
return @fieldParentPtr("obj", self); return @fieldParentPtr("obj", self);
} }
pub fn as_upvalue(self: *Obj) *Upvalue {
std.debug.assert(self.kind == ObjType.Upvalue);
return @fieldParentPtr("obj", self);
}
}; };

View File

@ -36,26 +36,24 @@ pub const VM = struct {
allocator: Allocator, allocator: Allocator,
stack: [constants.STACK_MAX]Value, stack: [constants.STACK_MAX]Value,
stack_top: usize, 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, strings: Table,
globals: Table, globals: Table,
frames: [constants.FRAMES_MAX]CallFrame, frames: [constants.FRAMES_MAX]CallFrame,
frame_count: usize, frame_count: usize,
open_upvalues: ?*Obj.Upvalue, open_upvalues: ?*Obj.Upvalue,
objects: ?*Obj,
pub fn new(allocator: Allocator) VM { pub fn new(allocator: Allocator) VM {
return VM{ return VM{
.allocator = allocator, .allocator = allocator,
.stack = undefined, .stack = undefined,
.stack_top = 0, .stack_top = 0,
.references = std.ArrayList(*Obj).init(allocator),
.strings = Table.new(allocator), .strings = Table.new(allocator),
.globals = Table.new(allocator), .globals = Table.new(allocator),
.frames = undefined, .frames = undefined,
.frame_count = 0, .frame_count = 0,
.open_upvalues = null, .open_upvalues = null,
.objects = null,
}; };
} }
@ -72,8 +70,16 @@ pub const VM = struct {
self.strings.destroy(); self.strings.destroy();
self.globals.destroy(); self.globals.destroy();
self.clean_references(); self.destroy_objects();
self.references.deinit(); }
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 { inline fn current_chunk(self: *VM) *Chunk {
@ -89,10 +95,9 @@ pub const VM = struct {
if (function == null) { if (function == null) {
return InterpretResult.COMPILE_ERROR; return InterpretResult.COMPILE_ERROR;
} }
defer function.?.destroy();
_ = try self.push(Value.obj_val(&function.?.obj)); _ = 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(); _ = self.pop();
_ = try self.push(Value.obj_val(&closure.obj)); _ = try self.push(Value.obj_val(&closure.obj));
_ = self.call(closure, 0); _ = self.call(closure, 0);
@ -229,7 +234,7 @@ pub const VM = struct {
}, },
@intFromEnum(OpCode.OP_CLOSURE) => { @intFromEnum(OpCode.OP_CLOSURE) => {
const function = self.read_constant().as_obj().as_function(); 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)); _ = try self.push(Value.obj_val(&closure.obj));
for (0..closure.upvalue_count) |i| { for (0..closure.upvalue_count) |i| {
const is_local = self.read_byte(); 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 }); const concat_str = try std.mem.concat(self.current_chunk().allocator, u8, &.{ a, b });
var string_obj = self.take_string(concat_str); const string_obj = self.take_string(concat_str);
self.add_reference(&string_obj.obj);
try self.push(Value.obj_val(&string_obj.obj)); 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 { pub fn copy_string(self: *VM, source: []const u8) *Obj.String {
const hash = compute_hash(source); const hash = compute_hash(source);
const obj_string = self.strings.find_string(source, hash); 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 { 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()); _ = self.strings.set(obj_string, Value.nil_val());
return obj_string; return obj_string;
@ -465,7 +451,7 @@ pub const VM = struct {
pub fn define_native(self: *VM, name: []const u8, native_fn: NativeFn) void { 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(&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]); _ = self.globals.set(self.stack[0].as_string(), self.stack[1]);
@ -486,7 +472,7 @@ pub const VM = struct {
return upvalue.?; return upvalue.?;
} }
const created_upvalue = Obj.Upvalue.new(self.allocator, local); const created_upvalue = Obj.Upvalue.new(self, local);
created_upvalue.next = upvalue; created_upvalue.next = upvalue;
if (prev_upvalue == null) { if (prev_upvalue == null) {