compile.zig (17923B)
1 const std = @import("std"); 2 const v = @import("value.zig"); 3 const c = @import("chunk.zig"); 4 const core = @import("core.zig"); 5 6 const complog = std.log.scoped(.compile); 7 8 const Storage = union(enum) { local: u32, value: *v.Value }; 9 10 const CompileError = error{ InvalidNumberOfArguments, InvalidArgType, UnbalancedBindings, Undefined, OutOfMemory, NotEnoughArguments }; 11 12 pub const Compiler = struct { 13 const Self = @This(); 14 15 const CompileState = struct { 16 return_this: bool, 17 }; 18 19 all: std.mem.Allocator, 20 val: *v.Value, 21 func: v.Function, 22 23 // Variable management (+ constant folding) 24 sym_map: std.StringHashMap(Storage), 25 scope_depth: u32, 26 27 enclosing: ?*const Compiler = null, 28 29 pub fn init(all: std.mem.Allocator, val: *v.Value) !Self { 30 return Self{ 31 .all = all, 32 .val = val.ref(), 33 .func = v.Function.init(all), 34 .sym_map = std.StringHashMap(Storage).init(all), 35 .scope_depth = 0, 36 }; 37 } 38 39 fn getChunk(self: *Self) *c.Chunk { 40 return &self.func.code; 41 } 42 43 pub fn deinit(self: *Self) void { 44 self.val.unref(); 45 self.sym_map.deinit(); 46 } 47 48 fn isConstant(self: *Self, val: *v.Value) ?*v.Value { 49 if (val.isA(.number) or val.isA(.nil)) { 50 return val; 51 } 52 53 switch (val.val) { 54 .symbol => |sym| { 55 if (self.sym_map.get(sym)) |store| { 56 switch (store) { 57 .value => |cval| return cval, 58 else => {}, 59 } 60 } 61 }, 62 else => {}, 63 } 64 return null; 65 } 66 67 fn compileIntrinsic(self: *Self, index: core.mapping) !void { 68 const val = core.getIntrinsic(index); 69 const cst = try self.getChunk().addConstant(try v.Value.newNative(self.all, val)); 70 try self.getChunk().write(.{ .constant = cst }); 71 try self.getChunk().write(.{ .call = val.arity }); 72 } 73 74 // "macro" that allows to perform pattern mathing 75 // this is nice to generate intrinsics for certain patterns 76 fn checkMatch(comptime desc: anytype, val: *v.Value) bool { 77 const rootT = @TypeOf(desc); 78 switch (rootT) { 79 v.Special => { 80 return val.isA(.special) and val.val.special == desc; 81 }, 82 v.Value.Tag => { 83 return val.isA(desc); 84 }, 85 else => {}, 86 } 87 88 switch (@typeInfo(rootT)) { 89 .Struct => |s| { 90 if (!val.isA(.list)) { 91 return false; 92 } 93 94 const lst = val.val.list; 95 96 inline for (s.fields, 0..) |field, idx| { 97 if (!checkMatch(@field(desc, field.name), lst[idx])) { 98 return false; 99 } 100 } 101 102 return true; 103 }, 104 .ComptimeInt => { 105 if (!val.isA(.number)) { 106 return false; 107 } 108 109 const llen: usize = comptime std.math.big.int.calcLimbLen(desc); 110 var limbbuf: [llen]std.math.big.Limb = undefined; 111 var mut = std.math.big.int.Mutable.init(&limbbuf, desc).toConst(); 112 113 return mut.eql(val.val.number.toConst()); 114 }, 115 .EnumLiteral => { 116 if (desc == .any) { 117 return true; 118 } 119 }, 120 else => @compileError("Uninmplemented"), 121 } 122 } 123 124 fn compileInner(self: *Self, val: *v.Value, astate: CompileState) CompileError!void { 125 var state = astate; 126 127 // (= (% val 2) 0) => (even? val) 128 if (Compiler.checkMatch(.{ v.Special.@"=", .{ v.Special.@"%", .any, 2 }, 0 }, val)) { 129 try self.compileInner(val.val.list[1].val.list[1], .{ .return_this = false }); 130 try self.compileIntrinsic(.isEven); 131 // (= (% val 2) 1) => (odd? val) 132 } else if (Compiler.checkMatch(.{ v.Special.@"=", .{ v.Special.@"%", .any, 2 }, 1 }, val)) { 133 try self.compileInner(val.val.list[1].val.list[1], .{ .return_this = false }); 134 try self.compileIntrinsic(.isOdd); 135 // (+ (* v1 v2) v3) => (fma v1 v2 v3) 136 } else if (Compiler.checkMatch(.{ v.Special.@"+", .{ v.Special.@"*", .any, .any }, .any }, val)) { 137 try self.compileInner(val.val.list[1].val.list[1], .{ .return_this = false }); 138 try self.compileInner(val.val.list[1].val.list[2], .{ .return_this = false }); 139 try self.compileInner(val.val.list[2], .{ .return_this = false }); 140 try self.compileIntrinsic(.fma); 141 } else switch (val.val) { 142 inline .function, .native => |_| @panic("Unreachable"), 143 .symbol => |sym| { 144 if (self.sym_map.get(sym)) |store| { 145 switch (store) { 146 .local => |index| { 147 try self.getChunk().write(.{ .local = index }); 148 }, 149 150 // This variant takes care of constant folding 151 // actual constant folding is done when generating the symbol 152 .value => |cst| { 153 const cval = try self.getChunk().addConstant(cst.ref()); 154 try self.getChunk().write(.{ .constant = cval }); 155 }, 156 } 157 } else { 158 // Late binding of global variables 159 const symval = try self.getChunk().addConstant(val.ref()); 160 try self.getChunk().write(.{ .get_global = symval }); 161 } 162 }, 163 .special => @panic("Cannot compile special"), 164 .list => |lst| { 165 if (lst[0].isA(.symbol)) { 166 // First, push the arguments one by one 167 for (lst[1..]) |param| { 168 try self.compileInner(param, .{ .return_this = false }); 169 } 170 171 // Now either TCO or call 172 if (state.return_this and std.mem.eql(u8, lst[0].val.symbol, self.func.name)) { 173 if (lst.len - 1 != self.func.arity) { 174 return CompileError.NotEnoughArguments; 175 } 176 try self.getChunk().write(.{ .popscope = .{ .offset = self.func.arity, .scope = self.func.arity } }); 177 try self.getChunk().write(.{ .jump = 0 }); 178 state.return_this = false; 179 } else { 180 const symc = try self.getChunk().addConstant(lst[0].ref()); 181 try self.getChunk().write(.{ .get_global = symc }); 182 try self.getChunk().write(.{ .call = lst.len - 1 }); 183 } 184 } else { 185 state = try self.compileSpecial(lst[0].val.special, lst, state); 186 } 187 }, 188 .number, .nil, .true, .false => { 189 const cst = try self.getChunk().addConstant(val.ref()); 190 try self.getChunk().write(.{ .constant = cst }); 191 }, 192 } 193 194 if (state.return_this) { 195 complog.debug("Generate return for {}, offset={}", .{val.formatter(), self.getChunk().nextPc()}); 196 try self.getChunk().write(.@"return"); 197 } 198 } 199 200 fn compileSpecial(self: *Self, name: v.Special, lst: []*v.Value, state: CompileState) !CompileState { 201 switch (name) { 202 inline .@"+", .@"*" => |tag| { 203 try self.compileInner(lst[1], .{ .return_this = false }); 204 for (lst[2..]) |inner| { 205 try self.compileInner(inner, .{ .return_this = false }); 206 try self.getChunk().write(if (tag == .@"+") .add else .multiply); 207 } 208 }, 209 .@"-" => { 210 if (lst.len == 2) { 211 try self.compileInner(lst[1], .{ .return_this = false }); 212 try self.getChunk().write(.negate); 213 } else { 214 try self.compileInner(lst[2], .{ .return_this = false }); 215 try self.compileInner(lst[1], .{ .return_this = false }); 216 try self.getChunk().write(.substract); 217 } 218 }, 219 220 inline .@"/", .@"%" => |tag| { 221 try self.compileInner(lst[2], .{ .return_this = false }); 222 try self.compileInner(lst[1], .{ .return_this = false }); 223 try self.getChunk().write(.div); 224 try self.getChunk().write(switch (tag) { 225 .@"/" => .pop, 226 .@"%" => .{ 227 .popscope = .{ .offset = 1, .scope = 1 }, 228 }, 229 else => @compileError("Unreachable"), 230 }); 231 }, 232 233 inline .@"=", .@"<", .@"^" => |val| { 234 try self.compileInner(lst[2], .{ .return_this = false }); 235 try self.compileInner(lst[1], .{ .return_this = false }); 236 try self.getChunk().write(switch (val) { 237 .@"^" => .pow, 238 .@"=" => .{ .compare = .eq }, 239 .@"<" => .{ .compare = .lt }, 240 else => @compileError("unreachable"), 241 }); 242 }, 243 // (let* (bindings...) expr) 244 .@"let*" => { 245 if (lst.len != 3) { 246 return CompileError.InvalidNumberOfArguments; 247 } 248 if (!lst[1].isA(.list)) { 249 return CompileError.InvalidArgType; 250 } 251 252 const blist = lst[1].val.list; 253 if (blist.len % 2 != 0) { 254 return CompileError.UnbalancedBindings; 255 } 256 257 for (0..blist.len / 2) |idx| { 258 const sym = blist[2 * idx]; 259 const sval = blist[2 * idx + 1]; 260 261 if (!sym.isA(.symbol)) { 262 return CompileError.InvalidArgType; 263 } 264 265 // Perform constant folding !! 266 const storage: Storage = if (self.isConstant(sval)) |cval| 267 .{ .value = cval } 268 else blk: { 269 try self.compileInner(sval, .{ .return_this = false }); 270 complog.debug("{s} is at depth {}", .{ sym.val.symbol, self.scope_depth }); 271 const loc = .{ .local = self.scope_depth }; 272 self.scope_depth += 1; 273 break :blk loc; 274 }; 275 276 try self.sym_map.put(sym.val.symbol, storage); 277 } 278 279 // FIXME: TCO for let 280 try self.compileInner(lst[2], .{ .return_this = false }); 281 282 // Now, remove the bindings of the variables 283 var to_pop: u16 = 0; 284 for (0..blist.len / 2) |idx| { 285 const got = self.sym_map.fetchRemove(blist[2 * idx].val.symbol).?; 286 switch (got.value) { 287 .local => { 288 to_pop += 1; 289 self.scope_depth -= 1; 290 }, 291 .value => {}, 292 } 293 } 294 if (to_pop != 0) { 295 try self.getChunk().write(.{ .popscope = .{ .scope = to_pop, .offset = 1 } }); 296 } 297 }, 298 299 // (if cond then else) 300 .@"if" => { 301 if (lst.len != 4) { 302 return CompileError.InvalidNumberOfArguments; 303 } 304 305 // Generate cond 306 try self.compileInner(lst[1], .{ .return_this = false }); 307 308 // If falsy then jump to else 309 try self.getChunk().write(.{ .jumpfalse = undefined }); 310 const jmpthen = self.getChunk().lastInstrRef(); 311 312 try self.compileInner(lst[2], state); 313 314 var jmpelse: usize = undefined; 315 if (!state.return_this) { 316 try self.getChunk().write(.{ .jump = undefined }); 317 jmpelse = self.getChunk().lastInstrRef(); 318 } 319 320 self.getChunk().instrs.items[jmpthen] = .{ .jumpfalse = self.getChunk().nextPc() }; 321 complog.debug("Backpatched jumpfalse: {}", .{jmpthen}); 322 323 try self.compileInner(lst[3], state); 324 if (!state.return_this) { 325 self.getChunk().instrs.items[jmpelse] = .{ .jump = self.getChunk().nextPc() }; 326 complog.debug("Backpatched jump: {}", .{jmpelse}); 327 } 328 329 // Either it was false, or we generated the returns in both branches 330 return .{ .return_this = false }; 331 }, 332 333 // (defn! name (args...) value) 334 .@"defn!" => { 335 if (lst.len != 4) { 336 return CompileError.InvalidNumberOfArguments; 337 } 338 339 if (!lst[1].isA(.symbol)) { 340 return CompileError.InvalidArgType; 341 } 342 343 if (!lst[2].isA(.list)) { 344 return CompileError.InvalidArgType; 345 } 346 347 const args = lst[2].val.list; 348 349 for (args) |arg| { 350 if (!arg.isA(.symbol)) { 351 return CompileError.InvalidArgType; 352 } 353 } 354 355 const arity = args.len; 356 357 var inner = try Compiler.init(self.all, lst[3]); 358 defer inner.deinit(); 359 360 inner.enclosing = self; 361 inner.func.name = lst[1].val.symbol; 362 inner.func.arity = @intCast(arity); 363 364 // Declare the parameters 365 for (args) |arg| { 366 const loc: Storage = .{ .local = inner.scope_depth }; 367 inner.scope_depth += 1; 368 try inner.sym_map.put(arg.val.symbol, loc); 369 } 370 371 // Now finish compilation 372 const res = try inner.compile(); 373 const valres = try v.Value.newFunction(self.all, res); 374 375 const vindex = try self.getChunk().addConstant(valres); 376 try self.getChunk().write(.{ .constant = vindex }); 377 378 const sym = try self.getChunk().addConstant(lst[1].ref()); 379 380 try self.getChunk().write(.{ .define_global = sym }); 381 }, 382 383 // (print expr) 384 .@"print!" => { 385 try self.compileInner(lst[1], .{ .return_this = false }); 386 try self.getChunk().write(.print); 387 }, 388 389 // (do expr...) 390 .do => if (lst.len > 1) { 391 for (lst[1 .. lst.len - 1]) |ival| { 392 try self.compileInner(ival, .{ .return_this = false }); 393 try self.getChunk().write(.pop); 394 } 395 try self.compileInner(lst[lst.len - 1], state); 396 397 return .{ .return_this = false }; 398 }, 399 400 .cons => { 401 try self.compileInner(lst[2], .{ .return_this = false }); 402 try self.compileInner(lst[1], .{ .return_this = false }); 403 try self.getChunk().write(.cons); 404 }, 405 406 .list => { 407 var newarr = try self.all.alloc(*v.Value, lst.len - 1); 408 409 for (lst[1..], 0..) |oval, idx| { 410 newarr[idx] = oval.ref(); 411 } 412 413 errdefer { 414 for (newarr) |nval| { 415 nval.unref(); 416 } 417 self.all.free(newarr); 418 } 419 420 const val = try self.getChunk().addConstant(try v.Value.newList(self.all, newarr)); 421 try self.getChunk().write(.{ .constant = val }); 422 }, 423 424 inline .fst, .rest => |tag| { 425 try self.compileInner(lst[1], .{ .return_this = false }); 426 try self.getChunk().write(.uncons); 427 try self.getChunk().write(switch (tag) { 428 .fst => .{ .popscope = .{ .offset = 1, .scope = 1 } }, 429 .rest => .pop, 430 else => @compileError("unreachable"), 431 }); 432 }, 433 } 434 return state; 435 } 436 437 pub fn compile(self: *Self) CompileError!v.Function { 438 try self.compileInner(self.val, .{ .return_this = true }); 439 440 if (std.log.logEnabled(.debug, .compile)) { 441 complog.debug("Compiled version of {}", .{self.val.formatter()}); 442 self.func.code.disassemble(); 443 } 444 445 return self.func; 446 } 447 }; 448 449 test "Simple match" { 450 const scan = @import("scan.zig"); 451 452 try v.init(std.testing.allocator); 453 defer v.deinit(); 454 455 var offset: ?usize = null; 456 var scanner = try scan.Reader.init("(% 2 3)", std.testing.allocator, &offset); 457 458 var val = try scanner.readValue(&offset); 459 defer val.unref(); 460 461 try std.testing.expect(Compiler.checkMatch(.{ 462 v.Special.@"%", 463 2, 464 3, 465 }, val)); 466 } 467 468 test "Nested match" { 469 const scan = @import("scan.zig"); 470 const s = @import("symintern.zig"); 471 472 try v.init(std.testing.allocator); 473 defer v.deinit(); 474 475 s.init(std.testing.allocator); 476 defer s.deinit(); 477 478 var offset: ?usize = null; 479 var scanner = try scan.Reader.init("(= (% val 2) 0)", std.testing.allocator, &offset); 480 481 var val = try scanner.readValue(&offset); 482 defer val.unref(); 483 484 try std.testing.expect(Compiler.checkMatch(.{ 485 v.Special.@"=", 486 .{ v.Special.@"%", .any, 2 }, 487 0, 488 }, val)); 489 }