commit 74a99fe819ad724c164709d45493935ca1ce2c27
parent 1068dd9d4b352cba1f2db33bee4969e00aaba2d0
Author: Thomas Vigouroux <thomas.vigouroux@univ-grenoble-alpes.fr>
Date: Wed, 12 Jun 2024 14:38:28 +0200
feat: function calls
Diffstat:
A | benchmakrs/fib.scm | | | 2 | ++ |
M | src/chunk.zig | | | 21 | +++++++++++++++++---- |
M | src/compile.zig | | | 366 | +++++++++++++++++++++++++++++++++++++++++++------------------------------------ |
M | src/main.zig | | | 58 | +++++++++++++++++++++++++++++++++++++++++----------------- |
M | src/scan.zig | | | 40 | ++++++++++++++++++++++++++++------------ |
M | src/value.zig | | | 67 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- |
M | src/vm.zig | | | 105 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- |
7 files changed, 438 insertions(+), 221 deletions(-)
diff --git a/benchmakrs/fib.scm b/benchmakrs/fib.scm
@@ -0,0 +1,2 @@
+(def! fib (fn* (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))
+(print! (fib 20))
diff --git a/src/chunk.zig b/src/chunk.zig
@@ -32,12 +32,21 @@ pub const Op = union(enum) {
// ..[a] -> ..[-a]
negate,
+ // ..[args..] -> ..[ret]
+ call: usize,
+
// ..[r][l] -> ..[l op r]
add,
substract,
multiply,
pow,
+ // ..[r][l] -> ..[l cmp r]
+ compare: std.math.CompareOperator,
+
+ // ..[v] -> ..[v]
+ print,
+
// ..[r][l] -> ..[l/r][l%r]
div
};
@@ -86,11 +95,15 @@ pub const Chunk = struct {
std.debug.print("{s} {}\n", .{@tagName(tag), num.formatter()});
},
+ inline .call, .compare => |cval, tag| {
+ std.debug.print("{s} {}\n", .{@tagName(tag), cval});
+ },
+
inline .@"local", .popscope, .jumpfalse, .jump => |vindex, tag| {
std.debug.print("{s} {}\n", .{@tagName(tag), vindex});
},
- inline .@"return", .negate, .add, .substract, .multiply, .pow, .div, .pop => |_, tag| {
+ inline .@"return", .negate, .add, .substract, .multiply, .pow, .div, .pop, .print => |_, tag| {
std.debug.print("{s}\n", .{@tagName(tag)});
},
}
@@ -98,13 +111,13 @@ pub const Chunk = struct {
pub fn disassemble(self: Self) void {
for (self.instrs.items, 0..) |instr, pc| {
- std.debug.print("{} |\t", .{pc});
+ std.debug.print("{:04} |\t", .{pc});
self.disassembleInstr(instr);
}
}
- pub fn lastInstrRef(self: *Self) *Op {
- return &self.instrs.items[self.instrs.items.len - 1];
+ pub fn lastInstrRef(self: *Self) usize {
+ return self.instrs.items.len - 1;
}
pub fn nextPc(self: *const Self) u32 {
diff --git a/src/compile.zig b/src/compile.zig
@@ -82,172 +82,17 @@ pub const Compiler = struct {
},
.special => @panic("Cannot compile special"),
.list => |lst| {
- const fstsym = lst[0].val.special;
- // TODO: maybe this can be something else
- switch (fstsym) {
- inline .@"+", .@"*" => |tag| {
- try self.compileInner(lst[1]);
- for (lst[2..]) |inner| {
- try self.compileInner(inner);
- try self.getChunk().write(if (tag == .@"+") .add else .multiply);
- }
- },
- .@"-" => {
- if (lst.len == 2) {
- try self.compileInner(lst[1]);
- try self.getChunk().write(.negate);
- } else {
- try self.compileInner(lst[2]);
- try self.compileInner(lst[1]);
- try self.getChunk().write(.substract);
- }
- },
- .@"^" => {
- try self.compileInner(lst[2]);
- try self.compileInner(lst[1]);
- try self.getChunk().write(.pow);
- },
- .@"/" => {
- @panic("TODO: multiple return values");
- // try self.compileInner(lst[2]);
- // try self.compileInner(lst[1]);
- // try self.chunk.write(.div);
- // try self.chunk.write(.pop);
- },
- // (let* (bindings...) expr)
- .@"let*" => {
- if (lst.len != 3) {
- return CompileError.InvalidNumberOfArguments;
- }
- if (!lst[1].isA(.list)) {
- return CompileError.InvalidArgType;
- }
-
- const blist = lst[1].val.list;
- if (blist.len % 2 != 0) {
- return CompileError.UnbalancedBindings;
- }
-
- for (0..blist.len / 2) |idx| {
- const sym = blist[2 * idx];
- const sval = blist[2 * idx + 1];
-
- if (!sym.isA(.symbol)) {
- return CompileError.InvalidArgType;
- }
-
- // Perform constant folding !!
- const storage: Storage = if (self.isConstant(sval)) |cval|
- .{ .value = cval }
- else blk: {
- try self.compileInner(sval);
- complog.debug("{s} is at depth {}", .{ sym.val.symbol, self.scope_depth });
- const loc = .{ .local = self.scope_depth };
- self.scope_depth += 1;
- break :blk loc;
- };
-
- try self.sym_map.put(sym.val.symbol, storage);
- }
-
- try self.compileInner(lst[2]);
-
- // Now, remove the bindings of the variables
- var to_pop: usize = 0;
- for (0..blist.len / 2) |idx| {
- const got = self.sym_map.fetchRemove(blist[2 * idx].val.symbol).?;
- switch (got.value) {
- .local => {
- to_pop += 1;
- self.scope_depth -= 1;
- },
- .value => {},
- }
- }
- if (to_pop != 0) {
- try self.getChunk().write(.{ .popscope = to_pop });
- }
- },
-
- // (if cond then else)
- .@"if" => {
- if (lst.len != 4) {
- return CompileError.InvalidNumberOfArguments;
- }
-
- // Generate cond
- try self.compileInner(lst[1]);
-
- // If falsy then jump to else
- try self.getChunk().write(.{ .jumpfalse = undefined });
- const jmpthen = self.getChunk().lastInstrRef();
-
- try self.compileInner(lst[2]);
-
- try self.getChunk().write(.{ .jump = undefined });
- const jmpelse = self.getChunk().lastInstrRef();
-
- jmpthen.* = .{ .jumpfalse = self.getChunk().nextPc() };
-
- try self.compileInner(lst[3]);
- jmpelse.* = .{ .jump = self.getChunk().nextPc() };
- },
-
- // (def! name value)
- .@"def!" => {
- if (lst.len != 3) {
- return CompileError.InvalidNumberOfArguments;
- }
-
- if (!lst[1].isA(.symbol)) {
- return CompileError.InvalidArgType;
- }
-
- const sym = try self.getChunk().addConstant(lst[1].ref());
-
- try self.compileInner(lst[2]);
-
- try self.getChunk().write(.{ .define_global = sym });
- },
-
- // (fn* (args...) expr)
- .@"fn*" => {
- if (lst.len != 3) {
- return CompileError.InvalidNumberOfArguments;
- }
-
- if (!lst[1].isA(.list)) {
- return CompileError.InvalidArgType;
- }
-
- const args = lst[1].val.list;
-
- for (args) |arg| {
- if (!arg.isA(.symbol)) {
- return CompileError.InvalidArgType;
- }
- }
-
- const arity = args.len;
-
- var inner = try Compiler.init(self.all, lst[2]);
- defer inner.deinit();
-
- // Declare the parameters
- for (args) |arg| {
- const loc: Storage = .{.local = inner.scope_depth};
- inner.scope_depth+=1;
- try inner.sym_map.put(arg.val.symbol, loc);
- }
-
- // Now finish compilation
- var res = try inner.compile();
- res.arity = arity;
- const valres = try v.Value.newFunction(self.all, res);
-
- const vindex = try self.getChunk().addConstant(valres);
- try self.getChunk().write(.{.constant = vindex});
+ if (lst[0].isA(.symbol)) {
+ // First, push the arguments one by one
+ for (lst[1..]) |param| {
+ try self.compileInner(param);
}
+
+ const symc = try self.getChunk().addConstant(lst[0].ref());
+ try self.getChunk().write(.{ .get_global = symc });
+ try self.getChunk().write(.{ .call = lst.len - 1 });
+ } else {
+ try self.compileSpecial(lst[0].val.special, lst);
}
},
.number, .nil, .true, .false => {
@@ -257,10 +102,201 @@ pub const Compiler = struct {
}
}
+ fn compileSpecial(self: *Self, name: v.Special, lst: []*v.Value) !void {
+ switch (name) {
+ inline .@"+", .@"*" => |tag| {
+ try self.compileInner(lst[1]);
+ for (lst[2..]) |inner| {
+ try self.compileInner(inner);
+ try self.getChunk().write(if (tag == .@"+") .add else .multiply);
+ }
+ },
+ .@"-" => {
+ if (lst.len == 2) {
+ try self.compileInner(lst[1]);
+ try self.getChunk().write(.negate);
+ } else {
+ try self.compileInner(lst[2]);
+ try self.compileInner(lst[1]);
+ try self.getChunk().write(.substract);
+ }
+ },
+
+ inline .@"/", .@"%" => |tag| {
+ try self.compileInner(lst[2]);
+ try self.compileInner(lst[1]);
+ try self.getChunk().write(.div);
+ try self.getChunk().write(switch (tag) {
+ .@"/" => .pop,
+ .@"%" => .{ .popscope = 1 },
+ else => @compileError("Unreachable")
+ });
+ },
+
+ inline .@"=", .@"<", .@"^" => |val| {
+ try self.compileInner(lst[2]);
+ try self.compileInner(lst[1]);
+ try self.getChunk().write(switch (val) {
+ .@"^" => .pow,
+ .@"=" => .{ .compare = .eq },
+ .@"<" => .{ .compare = .lt },
+ else => @compileError("unreachable"),
+ });
+ },
+ // (let* (bindings...) expr)
+ .@"let*" => {
+ if (lst.len != 3) {
+ return CompileError.InvalidNumberOfArguments;
+ }
+ if (!lst[1].isA(.list)) {
+ return CompileError.InvalidArgType;
+ }
+
+ const blist = lst[1].val.list;
+ if (blist.len % 2 != 0) {
+ return CompileError.UnbalancedBindings;
+ }
+
+ for (0..blist.len / 2) |idx| {
+ const sym = blist[2 * idx];
+ const sval = blist[2 * idx + 1];
+
+ if (!sym.isA(.symbol)) {
+ return CompileError.InvalidArgType;
+ }
+
+ // Perform constant folding !!
+ const storage: Storage = if (self.isConstant(sval)) |cval|
+ .{ .value = cval }
+ else blk: {
+ try self.compileInner(sval);
+ complog.debug("{s} is at depth {}", .{ sym.val.symbol, self.scope_depth });
+ const loc = .{ .local = self.scope_depth };
+ self.scope_depth += 1;
+ break :blk loc;
+ };
+
+ try self.sym_map.put(sym.val.symbol, storage);
+ }
+
+ try self.compileInner(lst[2]);
+
+ // Now, remove the bindings of the variables
+ var to_pop: usize = 0;
+ for (0..blist.len / 2) |idx| {
+ const got = self.sym_map.fetchRemove(blist[2 * idx].val.symbol).?;
+ switch (got.value) {
+ .local => {
+ to_pop += 1;
+ self.scope_depth -= 1;
+ },
+ .value => {},
+ }
+ }
+ if (to_pop != 0) {
+ try self.getChunk().write(.{ .popscope = to_pop });
+ }
+ },
+
+ // (if cond then else)
+ .@"if" => {
+ if (lst.len != 4) {
+ return CompileError.InvalidNumberOfArguments;
+ }
+
+ // Generate cond
+ try self.compileInner(lst[1]);
+
+ // If falsy then jump to else
+ try self.getChunk().write(.{ .jumpfalse = undefined });
+ const jmpthen = self.getChunk().lastInstrRef();
+
+ try self.compileInner(lst[2]);
+
+ try self.getChunk().write(.{ .jump = undefined });
+ const jmpelse = self.getChunk().lastInstrRef();
+
+ self.getChunk().instrs.items[jmpthen] = .{ .jumpfalse = self.getChunk().nextPc() };
+ complog.debug("Backpatched jumpfalse: {}", .{jmpthen});
+
+ try self.compileInner(lst[3]);
+ self.getChunk().instrs.items[jmpelse] = .{ .jump = self.getChunk().nextPc() };
+ complog.debug("Backpatched jump: {}", .{jmpelse});
+ },
+
+ // (def! name value)
+ .@"def!" => {
+ if (lst.len != 3) {
+ return CompileError.InvalidNumberOfArguments;
+ }
+
+ if (!lst[1].isA(.symbol)) {
+ return CompileError.InvalidArgType;
+ }
+
+ const sym = try self.getChunk().addConstant(lst[1].ref());
+
+ try self.compileInner(lst[2]);
+
+ try self.getChunk().write(.{ .define_global = sym });
+ },
+
+ // (fn* (args...) expr)
+ .@"fn*" => {
+ if (lst.len != 3) {
+ return CompileError.InvalidNumberOfArguments;
+ }
+
+ if (!lst[1].isA(.list)) {
+ return CompileError.InvalidArgType;
+ }
+
+ const args = lst[1].val.list;
+
+ for (args) |arg| {
+ if (!arg.isA(.symbol)) {
+ return CompileError.InvalidArgType;
+ }
+ }
+
+ const arity = args.len;
+
+ var inner = try Compiler.init(self.all, lst[2]);
+ defer inner.deinit();
+
+ // Declare the parameters
+ for (args) |arg| {
+ const loc: Storage = .{.local = inner.scope_depth};
+ inner.scope_depth+=1;
+ try inner.sym_map.put(arg.val.symbol, loc);
+ }
+
+ // Now finish compilation
+ var res = try inner.compile();
+ res.arity = arity;
+ const valres = try v.Value.newFunction(self.all, res);
+
+ const vindex = try self.getChunk().addConstant(valres);
+ try self.getChunk().write(.{.constant = vindex});
+ },
+
+ // (print expr)
+ .@"print!" => {
+ try self.compileInner(lst[1]);
+ try self.getChunk().write(.print);
+ }
+ }
+ }
+
pub fn compile(self: *Self) CompileError!v.Function {
try self.compileInner(self.val);
try self.getChunk().write(.@"return");
+ if (std.log.logEnabled(.debug, .compile)) {
+ complog.debug("Compiled version of {}", .{self.val.formatter()});
+ self.func.code.disassemble();
+ }
+
return self.func;
}
};
diff --git a/src/main.zig b/src/main.zig
@@ -7,11 +7,12 @@ const cl = @cImport(@cInclude("crossline.h"));
const s = @import("scan.zig");
const comp = @import("compile.zig");
const symintern = @import("symintern.zig");
+const builtin = @import("builtin");
const GPAll = std.heap.GeneralPurposeAllocator(.{});
pub const std_options = .{
- .log_level = std.log.Level.debug,
+ .log_level = if (builtin.mode == .Debug) std.log.Level.debug else .info,
.log_scope_levels = &.{.{ .scope = .read, .level = .err }},
.logFn = myLogFn,
};
@@ -22,11 +23,8 @@ pub fn myLogFn(
comptime format: []const u8,
args: anytype,
) void {
- // Ignore all non-error logging from sources other than
- // .my_project, .nice_library and the default
const scope_prefix = "(" ++ @tagName(scope) ++ "): ";
- // Print the message to stderr, silently ignoring any errors
std.debug.lockStdErr();
defer std.debug.unlockStdErr();
const stderrf = std.io.getStdErr();
@@ -47,34 +45,65 @@ pub fn myLogFn(
pub fn main() !void {
var gp = GPAll{};
- const all = gp.allocator();
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
- defer std.debug.assert(gp.deinit() == .ok);
+ const all = if (builtin.mode == .Debug) gp.allocator() else arena.allocator();
+
+ defer if (builtin.mode == .Debug) std.debug.assert(gp.deinit() == .ok);
symintern.init(all);
defer symintern.deinit();
+ try value.init(all);
+ defer value.deinit();
+
var v = try vm.VM.init(all);
defer v.deinit();
var args = std.process.args();
_ = args.next();
- if (args.next()) |file| {
+ var offset: ?usize = null;
+
+ if (args.next()) |path| {
// TODO: file parsing stuff
- _ = file;
+ var file = try std.fs.cwd().openFile(path, .{});
+ defer file.close();
+
+ const content = try file.readToEndAlloc(all, 10 * 1024 * 1024);
+ defer all.free(content);
+
+ var reader = try s.Reader.init(content, all, &offset);
+
+ while (reader.readValue(&offset)) |val| {
+ defer val.unref();
+
+ var compiler = try comp.Compiler.init(all, val);
+ defer compiler.deinit();
+
+ var c = try compiler.compile();
+ defer c.deinit();
+
+ const result = try v.interpret(&c);
+ defer result.unref();
+ std.debug.assert(v.stack.items.len == 0);
+ } else |err| {
+ if (err != error.Eof) {
+ std.log.err("Parse error {} at {}, char '{c}' ({})", .{err, offset.?, content[offset.?], content[offset.?]});
+ return err;
+ }
+ }
} else {
var buf: [1024:0]u8 = undefined;
-
const stdout = std.io.getStdOut();
while (cl.crossline_readline("> ", &buf, buf.len)) |obuf| {
const input = obuf[0..std.mem.len(obuf)];
- var reader = try s.Reader.init(input, all);
+ var reader = try s.Reader.init(input, all, &offset);
- var val = reader.readValue() catch {
- std.log.err("Parsing error", .{});
+ var val = reader.readValue(&offset) catch |err| {
+ std.log.err("Parsing error {} at {}, char '{c}'", .{err, offset.?, obuf[offset.?]});
continue;
};
defer val.unref();
@@ -85,9 +114,6 @@ pub fn main() !void {
var c = try compiler.compile();
defer c.deinit();
- std.log.debug("Compiled version of {}", .{val.formatter()});
- c.code.disassemble();
-
const result = try v.interpret(&c);
defer result.unref();
std.debug.assert(v.stack.items.len == 0);
@@ -95,6 +121,4 @@ pub fn main() !void {
try std.fmt.format(stdout.writer(), "Result: {}\n", .{result.formatter()});
}
}
-
- // try v.interpret(&c);
}
diff --git a/src/scan.zig b/src/scan.zig
@@ -26,7 +26,7 @@ pub const Reader = struct {
curtok: Token,
all: std.mem.Allocator,
- pub fn init(input: []const u8, all: std.mem.Allocator) !Self {
+ pub fn init(input: []const u8, all: std.mem.Allocator, err_offset: *?usize) !Self {
var self: Self = .{
.input = input,
.curpos = 0,
@@ -34,7 +34,7 @@ pub const Reader = struct {
.all = all,
};
- try self.nextTok();
+ try self.nextTok(err_offset);
return self;
}
@@ -44,7 +44,8 @@ pub const Reader = struct {
(ascii.isWhitespace(self.input[self.curpos]) or
self.input[self.curpos] == ';')) : (self.curpos += 1)
{
- // skip whitespace and ','
+ readlog.debug("Skip over offset {} char '{c}'", .{self.curpos, self.input[self.curpos]});
+ // skip comments
if (self.input[self.curpos] == ';') {
const endcomment = std.mem.indexOfScalar(u8, self.input[self.curpos..], '\n') orelse {
self.curpos = self.input.len;
@@ -66,23 +67,27 @@ pub const Reader = struct {
return self.curtok;
}
- pub fn nextTok(self: *Reader) !void {
+ pub fn nextTok(self: *Reader, err_offset: *?usize) !void {
self.skipWhitespaceComment();
if (self.curpos >= self.input.len) {
self.curtok = .eof;
return;
}
+ errdefer if (err_offset.* == null) {
+ err_offset.* = self.curpos;
+ };
+
self.curtok = switch (self.input[self.curpos]) {
inline '(', ')' => |val| onechar: {
self.curpos += 1;
break :onechar switch (val) {
'(' => .oparen,
')' => .cparen,
- else => @panic("Unreachable")
+ else => @compileError("Unreachable")
};
},
- '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => .{ .number = try self.tokInt() },
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => .{ .number = try self.tokInt(err_offset) },
else => sym: {
var offset = self.curpos;
while (offset < self.input.len and isSymChar(self.input[offset])) : (offset += 1) {}
@@ -97,8 +102,9 @@ pub const Reader = struct {
readlog.debug("Curtok: {}", .{self.curtok});
}
- fn tokInt(self: *Self) !v.Int {
+ fn tokInt(self: *Self, err_offset: *?usize) !v.Int {
var offset = self.curpos;
+ errdefer err_offset.* = offset;
// TODO: hex/binary...
while (offset < self.input.len and ascii.isDigit(self.input[offset])) : (offset += 1) {}
@@ -112,9 +118,14 @@ pub const Reader = struct {
}
// Reader functions
- pub fn readValue(self: *Self) !*v.Value {
+ pub fn readValue(self: *Self, err_offset: *?usize) !*v.Value {
+ errdefer if (err_offset.* == null) {
+ err_offset.* = self.curpos;
+ };
+
switch (self.peekTok()) {
.oparen => {
+ readlog.debug("Start parse list", .{});
var acc = std.ArrayList(*v.Value).init(self.all);
defer acc.deinit();
errdefer {
@@ -123,21 +134,26 @@ pub const Reader = struct {
}
}
- try self.nextTok();
- while (self.peekTok() != .cparen) : (try self.nextTok()) {
+ try self.nextTok(err_offset);
+ while (self.peekTok() != .cparen) {
if (self.peekTok() == .eof) {
return error.Eof;
}
- try acc.append(try self.readValue());
+ try acc.append(try self.readValue(err_offset));
}
+ // Consume the closing parenthesis
+ try self.nextTok(err_offset);
return v.Value.newList(self.all, try acc.toOwnedSlice());
},
.number => |num| {
+ try self.nextTok(err_offset);
return v.Value.newInt(self.all, num);
},
.symbol => |sym| {
+ try self.nextTok(err_offset);
+
inline for (comptime std.enums.values(v.Special)) |tag| {
if (ueql(sym, @tagName(tag))) {
return v.Value.newSpecial(self.all, tag);
@@ -153,7 +169,7 @@ pub const Reader = struct {
else
v.Value.newSymbol(self.all, try symintern.intern(sym));
},
- .cparen, .eof => return error.Unexpected
+ .cparen, .eof => return error.Eof
}
}
};
diff --git a/src/value.zig b/src/value.zig
@@ -9,12 +9,18 @@ pub const Special = enum {
@"*",
@"^",
@"-",
+ @"%",
@"let*",
@"if",
@"def!",
+ @"print!",
@"fn*",
+
+ // Comparisons
+ @"=",
+ @"<"
};
pub const Function = struct {
@@ -69,8 +75,16 @@ pub const Value = struct {
}
pub fn newInt(all: std.mem.Allocator, val: anytype) !*Self {
- const payload = if (@TypeOf(val) == Int) val else (try Int.initSet(all, val));
- return try Self.init(.{ .number = payload }, all);
+ var payload = if (@TypeOf(val) == Int) val else (try Int.initSet(all, val));
+
+ if (intcache.get(payload)) |cached| {
+ payload.deinit();
+ return cached.ref();
+ } else {
+ var new = try Self.init(.{ .number = payload }, all);
+ try intcache.put(payload, new);
+ return new.ref();
+ }
}
// orig has to be allocated with all
@@ -92,7 +106,8 @@ pub const Value = struct {
}
pub fn newBool(all: std.mem.Allocator, val: bool) !*Self {
- return try Self.init(if (val) .true else .false, all);
+ _ = all;
+ return if (val) trueval.ref() else falseval.ref();
}
pub fn newFunction(all: std.mem.Allocator, val: Function) !*Self {
@@ -120,8 +135,8 @@ pub const Value = struct {
pub fn deinit(self: *Self) void {
vallog.debug("Destroy {}", .{self.formatter()});
switch (self.val) {
- .number => |*num| {
- num.deinit();
+ .number => |_| {
+ // XXX: numbers are freed when the cache is cleared
},
.list => |lst| {
for (lst) |val| {
@@ -184,3 +199,45 @@ pub const Value = struct {
}
}
};
+
+var trueval: *Value = undefined;
+var falseval: *Value = undefined;
+
+const IntHashContext = struct {
+ pub fn hash(_: @This(), v: Int) u64 {
+ var wh = std.hash.Wyhash.init(0xDEADBEEF);
+ for (v.limbs[0..v.len()]) |limb| {
+ wh.update(std.mem.asBytes(&limb));
+ }
+ return wh.final();
+ }
+
+ pub fn eql(_: @This(), left: Int, right: Int) bool {
+ return left.eql(right);
+ }
+};
+
+const IntHashMap = std.HashMap(Int, *Value, IntHashContext, 80);
+
+var intcache: IntHashMap = undefined;
+
+pub fn init(all: std.mem.Allocator) !void {
+ trueval = try Value.init(.true, all);
+ falseval = try Value.init(.false, all);
+ intcache = IntHashMap.init(all);
+}
+
+pub fn deinit() void {
+ trueval.unref();
+ falseval.unref();
+
+ var iter = intcache.iterator();
+
+ while (iter.next()) |entry| {
+ // First unref to avoid invalid memory accesses
+ entry.value_ptr.*.unref();
+ entry.key_ptr.deinit();
+ }
+
+ intcache.deinit();
+}
diff --git a/src/vm.zig b/src/vm.zig
@@ -13,7 +13,7 @@ const VMError = error {
OutOfMemory,
NegativeIntoUnsigned,
TargetTooSmall
-};
+} || std.fs.File.WriteError;
const CallFrame = struct {
ip: []const c.Op,
@@ -69,13 +69,27 @@ pub const VM = struct {
const start_time = std.time.microTimestamp();
const res = self.run();
const end_time = std.time.microTimestamp();
- vmlog.debug("Eval in {} us", .{end_time - start_time});
+ vmlog.info("Eval in {} us", .{end_time - start_time});
return res;
}
+ fn advanceIp(self: *Self) void {
+ self.currentFrame().ip = self.currentFrame().ip[1..];
+ }
+
+ fn doPopScope(self: *Self, amount: usize) !void {
+ const topval = self.pop();
+
+ for (0..amount) |_| {
+ self.pop().unref();
+ }
+
+ try self.push(topval);
+ }
+
fn run(self: *Self) !*v.Value {
- while (true) : (self.currentFrame().ip = self.currentFrame().ip[1..]) {
+ while (true) {
const cframe = self.currentFrame();
const instr = cframe.ip[0];
const cchunk = cframe.func.code;
@@ -93,11 +107,13 @@ pub const VM = struct {
.constant => |vindex| {
const val = cchunk.values.items[vindex];
try self.push(val.ref());
+ self.advanceIp();
},
.local => |vindex| {
const val = self.stack.items[vindex + cframe.offset];
try self.push(val.ref());
+ self.advanceIp();
},
.get_global => |vindex| {
@@ -113,6 +129,17 @@ pub const VM = struct {
} else {
return VMError.UndefinedSymbol;
}
+ self.advanceIp();
+ },
+
+ .print => {
+ const val = self.stack.getLast();
+
+ var stdout = std.io.getStdOut().writer();
+
+ try stdout.print("{}\n", .{val.formatter()});
+
+ self.advanceIp();
},
.define_global => |index| {
@@ -123,6 +150,7 @@ pub const VM = struct {
vmlog.debug("Defining {s}", .{sym.val.symbol});
try self.globals.put(sym.val.symbol, val);
+ self.advanceIp();
},
.jumpfalse => |offset| {
@@ -130,33 +158,57 @@ pub const VM = struct {
defer val.unref();
if (val.isFalsy()) {
- cframe.ip = cchunk.instrs.items[offset - 1 ..];
+ cframe.ip = cchunk.instrs.items[offset..];
+ } else {
+ self.advanceIp();
}
},
.jump => |offset| {
- cframe.ip = cchunk.instrs.items[offset - 1 ..];
+ cframe.ip = cchunk.instrs.items[offset..];
+ },
+
+ .call => |arity| {
+ const funcval = self.pop();
+ defer funcval.unref();
+ if (!funcval.isA(.function)) {
+ return VMError.InvalidType;
+ }
+
+ const func = funcval.val.function;
+
+ std.debug.assert(arity == func.arity);
+
+ const offset = self.stack.items.len - arity;
+
+ const frame: CallFrame = .{.ip = func.code.instrs.items, .offset = offset, .func = &funcval.val.function};
+
+ self.advanceIp();
+ try self.call_stack.append(frame);
},
.@"return" => {
- _ = self.call_stack.pop();
- // XXX: no unref because we return
- if (self.stack.items.len > 0) {
- return self.pop();
+ const frame = self.call_stack.pop();
+
+ if (self.call_stack.items.len == 0) {
+ // XXX: no unref because we return
+ if (self.stack.items.len > 0) {
+ return self.pop();
+ } else {
+ return try v.Value.newNil(self.all);
+ }
} else {
- return try v.Value.newNil(self.all);
+ try self.doPopScope(frame.func.arity);
}
},
- .pop => self.pop().unref(),
+ .pop => pop: {
+ self.advanceIp();
+ break :pop self.pop().unref();
+ },
.popscope => |amount| {
- const topval = self.pop();
-
- for (0..amount) |_| {
- self.pop().unref();
- }
-
- try self.push(topval);
+ try self.doPopScope(amount);
+ self.advanceIp();
},
.negate => {
@@ -172,6 +224,7 @@ pub const VM = struct {
errdefer new.unref();
try self.push(new);
+ self.advanceIp();
},
.div => {
@@ -188,6 +241,7 @@ pub const VM = struct {
try self.push(try v.Value.newInt(self.all, div));
try self.push(try v.Value.newInt(self.all, rem));
+ self.advanceIp();
},
inline .add, .multiply, .substract, .pow => |_, tag| {
@@ -214,7 +268,22 @@ pub const VM = struct {
}
try self.push(try v.Value.newInt(self.all, new));
+ self.advanceIp();
},
+
+ .compare => |op| {
+ var left = self.pop();
+ defer left.unref();
+
+ var right = self.pop();
+ defer right.unref();
+
+ const lnum = left.val.number;
+ const rnum = right.val.number;
+
+ try self.push(try v.Value.newBool(self.all, lnum.order(rnum).compare(op)));
+ self.advanceIp();
+ }
}
}
}