refactor: using linked list for objects
This commit is contained in:
parent
ab00c0f785
commit
96e8b1742c
1
samples/hello_world.lox
Normal file
1
samples/hello_world.lox
Normal file
@ -0,0 +1 @@
|
||||
print "hello world!";
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
107
src/object.zig
107
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);
|
||||
}
|
||||
};
|
||||
|
50
src/vm.zig
50
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) {
|
||||
|
Loading…
Reference in New Issue
Block a user