From 2a365287a88935578c765f5b3d5fa6111371d6de Mon Sep 17 00:00:00 2001 From: Patrick MARIE Date: Sat, 24 Aug 2024 18:04:47 +0200 Subject: [PATCH] creating a VM (ch15) --- src/chunk.zig | 100 +++++++++++++++++++++++++++++++++++++++++ src/main.zig | 118 ++++--------------------------------------------- src/opcode.zig | 1 + src/utils.zig | 20 +++++++++ src/vm.zig | 64 +++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 110 deletions(-) create mode 100644 src/chunk.zig create mode 100644 src/opcode.zig create mode 100644 src/vm.zig diff --git a/src/chunk.zig b/src/chunk.zig new file mode 100644 index 0000000..56c7130 --- /dev/null +++ b/src/chunk.zig @@ -0,0 +1,100 @@ +const std = @import("std"); +const debug = std.debug; +const Allocator = std.mem.Allocator; + +const Value = @import("./values.zig").Value; +const ValueArray = @import("./values.zig").ValueArray; +const OpCode = @import("./opcode.zig").OpCode; + +const grow_capacity = @import("./utils.zig").grow_capacity; +const utils = @import("./utils.zig"); + +pub const Chunk = struct { + count: usize, + capacity: usize, + code: []u8, + lines: []usize, + constants: ValueArray, + + pub fn new() Chunk { + return Chunk{ + .count = 0, + .capacity = 0, + .code = &.{}, + .lines = &.{}, + .constants = ValueArray.new(), + }; + } + + pub fn init(self: *Chunk, allocator: Allocator) !void { + self.deinit(allocator); + + self.count = 0; + self.capacity = 0; + self.code = &.{}; + self.lines = &.{}; + self.constants = ValueArray.new(); + } + + pub fn write(self: *Chunk, allocator: Allocator, byte: u8, line: usize) !void { + if (self.capacity < self.count + 1) { + const old_capacity = self.capacity; + self.capacity = grow_capacity(old_capacity); + self.code = try allocator.realloc(self.code, self.capacity); + self.lines = try allocator.realloc(self.lines, self.capacity); + } + + self.code[self.count] = byte; + self.lines[self.count] = line; + self.count += 1; + } + + pub fn dump(self: Chunk) void { + debug.print("{any}\n", .{self}); + } + + pub fn dissassemble(self: Chunk, name: []const u8) void { + debug.print("== {s} ==\n", .{name}); + + var offset: usize = 0; + + while (offset < self.count) { + offset += self.dissassemble_instruction(offset); + } + } + + pub fn dissassemble_instruction(self: Chunk, offset: usize) usize { + debug.print("{x:0>4} ", .{offset}); + + if (offset > 0 and self.lines[offset] == self.lines[offset - 1]) { + debug.print(" | ", .{}); + } else { + debug.print("{d:4} ", .{self.lines[offset]}); + } + + const instruction = self.code[offset]; + + switch (instruction) { + @intFromEnum(OpCode.OP_RETURN) => return utils.simple_instruction("OP_RETURN", offset), + @intFromEnum(OpCode.OP_CONSTANT) => return utils.constant_instruction("OP_CONSTANT", self, offset), + else => { + debug.print("unknown opcode {d}\n", .{instruction}); + return offset + 1; + }, + } + } + + pub fn deinit(self: *Chunk, allocator: Allocator) void { + self.constants.free(allocator); + + if (self.capacity > 0) { + allocator.free(self.code); + allocator.free(self.lines); + } + } + + pub fn add_constant(self: *Chunk, allocator: Allocator, value: Value) !usize { + try self.constants.write(allocator, value); + return self.constants.count - 1; + } +}; diff --git a/src/main.zig b/src/main.zig index 14645ec..0d3034a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,122 +2,17 @@ const std = @import("std"); const debug = std.debug; const Allocator = std.mem.Allocator; -const utils = @import("./utils.zig"); -const values = @import("./values.zig"); -const Value = values.Value; -const ValueArray = values.ValueArray; - -const OpCode = enum(u8) { OP_CONSTANT, OP_RETURN }; - -const Chunk = struct { - count: usize, - capacity: usize, - code: []u8, - lines: []usize, - constants: ValueArray, - - pub fn new() Chunk { - return Chunk{ - .count = 0, - .capacity = 0, - .code = &.{}, - .lines = &.{}, - .constants = ValueArray.new(), - }; - } - - pub fn init(self: *Chunk, allocator: Allocator) !void { - self.deinit(allocator); - - self.count = 0; - self.capacity = 0; - self.code = &.{}; - self.lines = &.{}; - self.constants = ValueArray.new(); - } - - pub fn write(self: *Chunk, allocator: Allocator, byte: u8, line: usize) !void { - if (self.capacity < self.count + 1) { - const old_capacity = self.capacity; - self.capacity = utils.grow_capacity(old_capacity); - self.code = try allocator.realloc(self.code, self.capacity); - self.lines = try allocator.realloc(self.lines, self.capacity); - } - - self.code[self.count] = byte; - self.lines[self.count] = line; - self.count += 1; - } - - pub fn dump(self: Chunk) void { - debug.print("{any}\n", .{self}); - } - - pub fn dissassemble(self: Chunk, name: []const u8) void { - debug.print("== {s} ==\n", .{name}); - - var offset: usize = 0; - - while (offset < self.count) { - offset += self.dissassemble_instruction(offset); - } - } - - pub fn dissassemble_instruction(self: Chunk, offset: usize) usize { - debug.print("{x:0>4} ", .{offset}); - - if (offset > 0 and self.lines[offset] == self.lines[offset - 1]) { - debug.print(" | ", .{}); - } else { - debug.print("{d:4} ", .{self.lines[offset]}); - } - - const instruction = self.code[offset]; - - switch (instruction) { - @intFromEnum(OpCode.OP_RETURN) => return simple_instruction("OP_RETURN", offset), - @intFromEnum(OpCode.OP_CONSTANT) => return constant_instruction("OP_CONSTANT", self, offset), - else => { - debug.print("unknown opcode {d}\n", .{instruction}); - return offset + 1; - }, - } - } - - pub fn deinit(self: *Chunk, allocator: Allocator) void { - self.constants.free(allocator); - - if (self.capacity > 0) { - allocator.free(self.code); - allocator.free(self.lines); - } - } - - pub fn add_constant(self: *Chunk, allocator: Allocator, value: Value) !usize { - try self.constants.write(allocator, value); - return self.constants.count - 1; - } -}; - -pub fn simple_instruction(opcode_name: []const u8, offset: usize) usize { - debug.print("{s:16}\n", .{opcode_name}); - - return offset + 1; -} - -pub fn constant_instruction(opcode_name: []const u8, chunk: Chunk, offset: usize) usize { - const constant = chunk.code[offset + 1]; - debug.print("{s:16} {d:4} '", .{ opcode_name, constant }); - values.print_value(chunk.constants.values[constant]); - debug.print("'\n", .{}); - return offset + 2; -} +const Chunk = @import("./chunk.zig").Chunk; +const OpCode = @import("./opcode.zig").OpCode; +const VM = @import("./vm.zig").VM; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){}; defer _ = debug.assert(gpa.deinit() == .ok); const allocator = gpa.allocator(); + var vm = VM.new(); + var chunk = Chunk.new(); try chunk.init(allocator); @@ -128,5 +23,8 @@ pub fn main() !void { chunk.dissassemble("test chunk"); + _ = vm.interpret(&chunk); + vm.free(); + chunk.deinit(allocator); } diff --git a/src/opcode.zig b/src/opcode.zig new file mode 100644 index 0000000..c99ab1b --- /dev/null +++ b/src/opcode.zig @@ -0,0 +1 @@ +pub const OpCode = enum(u8) { OP_CONSTANT, OP_RETURN }; diff --git a/src/utils.zig b/src/utils.zig index bd758bc..119cfc5 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -1,6 +1,26 @@ +const std = @import("std"); +const debug = std.debug; + +const Chunk = @import("./chunk.zig").Chunk; +const print_value = @import("./values.zig").print_value; + pub fn grow_capacity(capacity: usize) usize { if (capacity < 8) { return 8; } return capacity * 2; } + +pub fn simple_instruction(opcode_name: []const u8, offset: usize) usize { + debug.print("{s:16}\n", .{opcode_name}); + + return offset + 1; +} + +pub fn constant_instruction(opcode_name: []const u8, chunk: Chunk, offset: usize) usize { + const constant = chunk.code[offset + 1]; + debug.print("{s:16} {d:4} '", .{ opcode_name, constant }); + print_value(chunk.constants.values[constant]); + debug.print("'\n", .{}); + return offset + 2; +} diff --git a/src/vm.zig b/src/vm.zig new file mode 100644 index 0000000..78ca569 --- /dev/null +++ b/src/vm.zig @@ -0,0 +1,64 @@ +const std = @import("std"); +const Chunk = @import("./chunk.zig").Chunk; +const OpCode = @import("./opcode.zig").OpCode; +const Value = @import("./values.zig").Value; + +const print_value = @import("./values.zig").print_value; + +const InterpretResult = enum { + OK, + COMPILE_ERROR, + RUNTIME_ERROR, +}; + +pub const VM = struct { + chunk: ?*Chunk, + ip: ?usize, + + pub fn new() VM { + return VM{ + .chunk = null, + .ip = null, + }; + } + + pub fn free(self: *VM) void { + _ = self; + } + + pub fn interpret(self: *VM, chunk: *Chunk) InterpretResult { + self.chunk = chunk; + self.ip = 0; + + return self.run(); + } + + pub fn run(self: *VM) InterpretResult { + while (true) { + const instruction = self.read_byte(); + + switch (instruction) { + @intFromEnum(OpCode.OP_CONSTANT) => { + const constant = self.read_constant(); + print_value(constant); + }, + @intFromEnum(OpCode.OP_RETURN) => return InterpretResult.OK, + else => return InterpretResult.RUNTIME_ERROR, + } + } + + return InterpretResult.OK; + } + + // XXX In the book, we're using a ptr to data directly, to avoid dereferencing to a given offset + // How to do that in Zig? + pub fn read_byte(self: *VM) u8 { + self.ip.? += 1; + + return self.chunk.?.code[self.ip.?]; + } + + pub fn read_constant(self: *VM) Value { + return self.chunk.?.constants.values[read_byte(self)]; + } +};