zahl

Log | Files | Refs | README

vm.zig (12226B)


      1 const std = @import("std");
      2 const c = @import("chunk.zig");
      3 const v = @import("value.zig");
      4 
      5 const vmlog = std.log.scoped(.vm);
      6 
      7 // TODO: efficient stack implementation at some point ?
      8 const Stack = std.ArrayList(*v.Value);
      9 
     10 const VMError = error {
     11     UndefinedSymbol,
     12     InvalidType,
     13     OutOfMemory,
     14     NegativeIntoUnsigned,
     15     TargetTooSmall,
     16     NativeError,
     17 } || std.fs.File.WriteError;
     18 
     19 const CallFrame = struct {
     20     ip: []const c.Op,
     21     offset: usize,
     22     func: *const v.Function,
     23 };
     24 
     25 const CallStack = std.ArrayList(CallFrame);
     26 
     27 pub const VM = struct {
     28     const Self = @This();
     29 
     30     stack: Stack,
     31     all: std.mem.Allocator,
     32     call_stack: CallStack,
     33 
     34     // Global variables
     35     globals: std.StringHashMap(*v.Value),
     36 
     37     pub fn init(all: std.mem.Allocator) !Self {
     38         return Self{
     39             .stack = try Stack.initCapacity(all, 256),
     40             .all = all,
     41             .globals = std.StringHashMap(*v.Value).init(all),
     42             .call_stack = CallStack.init(all),
     43         };
     44     }
     45 
     46     pub fn setupCore(self: *Self, comptime funcs: []const v.NativeFunction) !void {
     47         inline for (funcs) |func| {
     48             try self.globals.put(func.name, try v.Value.newNative(self.all, func));
     49         }
     50     }
     51 
     52     fn push(self: *Self, val: *v.Value) !void {
     53         try self.stack.append(val);
     54     }
     55 
     56     fn pop(self: *Self) *v.Value {
     57         return self.stack.pop();
     58     }
     59 
     60     fn peek(self: *Self) *v.Value {
     61         return self.stack.getLast();
     62     }
     63 
     64     fn currentFrame(self: *Self) *CallFrame {
     65         return &self.call_stack.items[self.call_stack.items.len - 1];
     66     }
     67 
     68     pub fn interpret(self: *Self, func: *const v.Function) VMError!*v.Value {
     69         std.debug.assert(self.call_stack.items.len == 0);
     70         try self.call_stack.append(.{
     71             .offset = 0,
     72             .func = func,
     73             .ip = func.code.instrs.items,
     74         });
     75 
     76         const start_time = std.time.microTimestamp();
     77         const res = self.run();
     78         const end_time = std.time.microTimestamp();
     79         vmlog.info("Eval in {} us", .{end_time - start_time});
     80 
     81         return res;
     82     }
     83 
     84     fn advanceIp(self: *Self) void {
     85         self.currentFrame().ip = self.currentFrame().ip[1..];
     86     }
     87 
     88     fn doPopScope(self: *Self, offset: u16, amount: u16) !void {
     89         var valbuf: [255]*v.Value = undefined;
     90 
     91         for (0..offset) |idx| {
     92             valbuf[idx] = self.pop();
     93         }
     94 
     95         for (0..amount) |_| {
     96             self.pop().unref();
     97         }
     98 
     99 
    100         for (0..offset) |idx| {
    101             try self.push(valbuf[offset - idx - 1]);
    102         }
    103     }
    104 
    105     fn run(self: *Self) !*v.Value {
    106         while (true) {
    107             const cframe = self.currentFrame();
    108             const instr = cframe.ip[0];
    109             const cchunk = cframe.func.code;
    110 
    111             if (comptime std.log.logEnabled(.debug, .vm)) {
    112                 // TODO: proper logging here
    113                 for (self.stack.items) |val| {
    114                     std.debug.print("[{}] ", .{val.formatter()});
    115                 }
    116                 std.debug.print("\n", .{});
    117                 cchunk.disassembleInstr(instr);
    118             }
    119 
    120             switch (instr) {
    121                 .constant => |vindex| {
    122                     const val = cchunk.values.items[vindex];
    123                     try self.push(val.ref());
    124                     self.advanceIp();
    125                 },
    126 
    127                 .local => |vindex| {
    128                     const val = self.stack.items[vindex + cframe.offset];
    129                     try self.push(val.ref());
    130                     self.advanceIp();
    131                 },
    132 
    133                 .get_global => |vindex| {
    134                     const sym = cchunk.values.items[vindex];
    135                     if (!sym.isA(.symbol)) {
    136                         return VMError.InvalidType;
    137                     }
    138 
    139                     vmlog.debug("Getting {s}", .{sym.val.symbol});
    140 
    141                     if (self.globals.get(sym.val.symbol)) |found| {
    142                         try self.push(found.ref());
    143                     } else {
    144                         return VMError.UndefinedSymbol;
    145                     }
    146                     self.advanceIp();
    147                 },
    148 
    149                 .print => {
    150                     const val = self.stack.getLast();
    151 
    152                     var stdout = std.io.getStdOut().writer();
    153 
    154                     try stdout.print("{}\n", .{val.formatter()});
    155 
    156                     self.advanceIp();
    157                 },
    158 
    159                 .define_global => |index| {
    160                     const sym = cchunk.values.items[index];
    161                     const val = self.pop();
    162 
    163                     std.debug.assert(sym.isA(.symbol));
    164 
    165                     vmlog.debug("Defining {s}", .{sym.val.symbol});
    166                     if (try self.globals.fetchPut(sym.val.symbol, val)) |old| {
    167                         old.value.unref();
    168                     }
    169                     self.advanceIp();
    170                 },
    171 
    172                 .jumpfalse => |offset| {
    173                     const val = self.pop();
    174                     defer val.unref();
    175 
    176                     if (val.isFalsy()) {
    177                         cframe.ip = cchunk.instrs.items[offset..];
    178                     } else {
    179                         self.advanceIp();
    180                     }
    181                 },
    182                 .jump => |offset| {
    183                     cframe.ip = cchunk.instrs.items[offset..];
    184                 },
    185 
    186                 .call => |arity| {
    187                     const funcval = self.pop();
    188                     defer funcval.unref();
    189 
    190                     switch (funcval.val) {
    191                         .function => |func| {
    192                             std.debug.assert(arity == func.arity);
    193 
    194                             const offset = self.stack.items.len - arity;
    195 
    196                             const frame: CallFrame = .{.ip = func.code.instrs.items, .offset = offset, .func = &funcval.val.function};
    197 
    198                             self.advanceIp();
    199                             try self.call_stack.append(frame);
    200                         },
    201                         .native => |n| {
    202                             std.debug.assert(arity == n.arity);
    203 
    204                             const offset = self.stack.items.len - arity;
    205                             var new = n.code(self.stack.items[offset..], self.all) catch return VMError.NativeError;
    206                             errdefer new.unref();
    207 
    208                             self.advanceIp();
    209 
    210                             try self.doPopScope(0, n.arity);
    211 
    212                             try self.push(new);
    213                         },
    214                         else => return VMError.InvalidType,
    215                     }
    216                 },
    217 
    218                 .@"return" => {
    219                     const frame = self.call_stack.pop();
    220 
    221                     if (self.call_stack.items.len == 0) {
    222                         // XXX: no unref because we return
    223                         if (self.stack.items.len > 0) {
    224                             return self.pop();
    225                         } else {
    226                             return try v.Value.newNil(self.all);
    227                         }
    228                     } else {
    229                         try self.doPopScope(1, frame.func.arity);
    230                     }
    231                 },
    232 
    233                 .pop => pop: {
    234                     self.advanceIp();
    235                     break :pop self.pop().unref();
    236                 },
    237 
    238                 .popscope => |amount| {
    239                     try self.doPopScope(amount.offset, amount.scope);
    240                     self.advanceIp();
    241                 },
    242 
    243                 .negate => {
    244                     var val = self.pop();
    245                     defer val.unref();
    246 
    247                     std.debug.assert(val.isA(.number));
    248 
    249                     var n = try v.Int.cloneWithDifferentAllocator(val.val.number, self.all);
    250                     n.negate();
    251 
    252                     var new = try v.Value.newInt(self.all, n);
    253                     errdefer new.unref();
    254 
    255                     try self.push(new);
    256                     self.advanceIp();
    257                 },
    258 
    259                 .cons => {
    260                     var head = self.pop();
    261                     defer head.unref();
    262 
    263                     var lst = self.pop();
    264                     defer lst.unref();
    265 
    266                     if (!lst.isA(.list)) {
    267                         return VMError.InvalidType;
    268                     }
    269 
    270                     var newarr = try self.all.alloc(*v.Value, lst.val.list.len + 1);
    271 
    272                     newarr[0] = head.ref();
    273                     for (lst.val.list, 1..) |oldval, idx| {
    274                         newarr[idx] = oldval.ref();
    275                     }
    276 
    277                     errdefer {
    278                         for (newarr) |nval| {
    279                             nval.unref();
    280                         }
    281                         self.all.free(newarr);
    282                     }
    283 
    284                     try self.push(try v.Value.newList(self.all, newarr));
    285                     self.advanceIp();
    286                 },
    287 
    288                 .uncons => {
    289                     var lst = self.pop();
    290                     defer lst.unref();
    291 
    292                     if (!lst.isA(.list)) {
    293                         return VMError.InvalidType;
    294                     }
    295 
    296                     const oldarr = lst.val.list;
    297 
    298                     var newarr = try self.all.alloc(*v.Value, oldarr.len - 1);
    299 
    300                     for (oldarr[1..], 0..) |oldval, idx| {
    301                         newarr[idx] = oldval.ref();
    302                     }
    303                     errdefer {
    304                         for (newarr) |nval| {
    305                             nval.unref();
    306                         }
    307                         self.all.free(newarr);
    308                     }
    309 
    310                     try self.push(try v.Value.newList(self.all, newarr));
    311                     try self.push(oldarr[0].ref());
    312                     self.advanceIp();
    313                 },
    314 
    315                 .div => {
    316                     var left = self.pop();
    317                     defer left.unref();
    318 
    319                     var right = self.pop();
    320                     defer right.unref();
    321 
    322                     var div = try v.Int.init(self.all);
    323                     var rem = try v.Int.init(self.all);
    324 
    325                     try div.divTrunc(&rem, &left.val.number, &right.val.number);
    326 
    327                     try self.push(try v.Value.newInt(self.all, div));
    328                     try self.push(try v.Value.newInt(self.all, rem));
    329                     self.advanceIp();
    330                 },
    331 
    332                 inline .add, .multiply, .substract, .pow => |_, tag| {
    333                     var left = self.pop();
    334                     defer left.unref();
    335 
    336                     var right = self.pop();
    337                     defer right.unref();
    338 
    339                     const lnum = &left.val.number;
    340                     const rnum = &right.val.number;
    341 
    342                     var new = try v.Int.init(self.all);
    343                     errdefer new.deinit();
    344 
    345                     switch (tag) {
    346                         .add => try new.add(lnum, rnum),
    347                         .substract => try new.sub(lnum, rnum),
    348                         .multiply => try new.mul(lnum, rnum),
    349                         .pow => try new.pow(lnum, try rnum.to(u32)),
    350                         else => {
    351                             // Unreachable
    352                         },
    353                     }
    354 
    355                     try self.push(try v.Value.newInt(self.all, new));
    356                     self.advanceIp();
    357                 },
    358 
    359                 .compare => |op| {
    360                     var left = self.pop();
    361                     defer left.unref();
    362 
    363                     var right = self.pop();
    364                     defer right.unref();
    365 
    366                     const lnum = left.val.number;
    367                     const rnum = right.val.number;
    368 
    369                     try self.push(try v.Value.newBool(self.all, lnum.order(rnum).compare(op)));
    370                     self.advanceIp();
    371                 }
    372             }
    373         }
    374     }
    375 
    376     pub fn deinit(self: *Self) void {
    377         for (self.stack.items) |val| {
    378             val.unref();
    379         }
    380         self.stack.deinit();
    381 
    382         var iter = self.globals.iterator();
    383 
    384         while (iter.next()) |entry| {
    385             entry.value_ptr.*.unref();
    386         }
    387         self.globals.deinit();
    388 
    389         self.call_stack.deinit();
    390     }
    391 };