| author | |
| committer | |
| log | 28a9da8bfc1a791e0eaf8c643827da88ea70f7d1 |
| tree | b15f8dfea2858c058d878d4917812d28202fe727 |
| parent | 576581bd7b78825ce27d6a73fc42dd90eab8fbd1 |
* introduce a dump() function on Module.Fn which helpfully prints to
stderr the ZIR representation of a function (can be called before
attempting to codegen it). This is a debugging tool.
* implement x86 codegen for loops
* liveness: fix analysis of conditional branches. The logic was buggy
in a couple ways:
- it never actually saved the results into the IR instruction (fixed now)
- it incorrectly labeled operands as dying when their true death was
after the conditional branch ended (fixed now)
* zir rendering is enhanced to show liveness analysis results. this
helps when debugging liveness analysis.
* fix bug in zir rendering not numbering instructions correctly
closes #60218 files changed, 355 insertions(+), 137 deletions(-)
lib/std/math.zig+1| ... | ... | @@ -747,6 +747,7 @@ test "math.negateCast" { |
| 747 | 747 | |
| 748 | 748 | /// Cast an integer to a different integer type. If the value doesn't fit, |
| 749 | 749 | /// return an error. |
| 750 | /// TODO make this an optional not an error. | |
| 750 | 751 | pub fn cast(comptime T: type, x: anytype) (error{Overflow}!T) { |
| 751 | 752 | comptime assert(@typeInfo(T) == .Int); // must pass an integer |
| 752 | 753 | comptime assert(@typeInfo(@TypeOf(x)) == .Int); // must pass an integer |
src-self-hosted/Module.zig+17| ... | ... | @@ -301,6 +301,23 @@ pub const Fn = struct { |
| 301 | 301 | body: zir.Module.Body, |
| 302 | 302 | arena: std.heap.ArenaAllocator.State, |
| 303 | 303 | }; |
| 304 | ||
| 305 | /// For debugging purposes. | |
| 306 | pub fn dump(self: *Fn, mod: Module) void { | |
| 307 | std.debug.print("Module.Function(name={}) ", .{self.owner_decl.name}); | |
| 308 | switch (self.analysis) { | |
| 309 | .queued => { | |
| 310 | std.debug.print("queued\n", .{}); | |
| 311 | }, | |
| 312 | .in_progress => { | |
| 313 | std.debug.print("in_progress\n", .{}); | |
| 314 | }, | |
| 315 | else => { | |
| 316 | std.debug.print("\n", .{}); | |
| 317 | zir.dumpFn(mod, self); | |
| 318 | }, | |
| 319 | } | |
| 320 | } | |
| 304 | 321 | }; |
| 305 | 322 | |
| 306 | 323 | pub const Scope = struct { |
src-self-hosted/codegen.zig+28-5| ... | ... | @@ -23,8 +23,6 @@ pub const BlockData = struct { |
| 23 | 23 | relocs: std.ArrayListUnmanaged(Reloc) = .{}, |
| 24 | 24 | }; |
| 25 | 25 | |
| 26 | pub const LoopData = struct { }; | |
| 27 | ||
| 28 | 26 | pub const Reloc = union(enum) { |
| 29 | 27 | /// The value is an offset into the `Function` `code` from the beginning. |
| 30 | 28 | /// To perform the reloc, write 32-bit signed little-endian integer |
| ... | ... | @@ -556,7 +554,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 556 | 554 | } |
| 557 | 555 | |
| 558 | 556 | fn genBody(self: *Self, body: ir.Body) InnerError!void { |
| 559 | const inst_table = &self.branch_stack.items[0].inst_table; | |
| 557 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; | |
| 558 | const inst_table = &branch.inst_table; | |
| 560 | 559 | for (body.instructions) |inst| { |
| 561 | 560 | const new_inst = try self.genFuncInst(inst); |
| 562 | 561 | try inst_table.putNoClobber(self.gpa, inst, new_inst); |
| ... | ... | @@ -1284,6 +1283,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1284 | 1283 | } |
| 1285 | 1284 | |
| 1286 | 1285 | fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue { |
| 1286 | // TODO Rework this so that the arch-independent logic isn't buried and duplicated. | |
| 1287 | 1287 | switch (arch) { |
| 1288 | 1288 | .x86_64 => { |
| 1289 | 1289 | try self.code.ensureCapacity(self.code.items.len + 6); |
| ... | ... | @@ -1336,6 +1336,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1336 | 1336 | } |
| 1337 | 1337 | |
| 1338 | 1338 | fn genX86CondBr(self: *Self, inst: *ir.Inst.CondBr, opcode: u8) !MCValue { |
| 1339 | // TODO deal with liveness / deaths condbr's then_entry_deaths and else_entry_deaths | |
| 1339 | 1340 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode }); |
| 1340 | 1341 | const reloc = Reloc{ .rel32 = self.code.items.len }; |
| 1341 | 1342 | self.code.items.len += 4; |
| ... | ... | @@ -1360,14 +1361,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1360 | 1361 | } |
| 1361 | 1362 | |
| 1362 | 1363 | fn genLoop(self: *Self, inst: *ir.Inst.Loop) !MCValue { |
| 1363 | return self.fail(inst.base.src, "TODO codegen loop", .{}); | |
| 1364 | // A loop is a setup to be able to jump back to the beginning. | |
| 1365 | const start_index = self.code.items.len; | |
| 1366 | try self.genBody(inst.body); | |
| 1367 | try self.jump(inst.base.src, start_index); | |
| 1368 | return MCValue.unreach; | |
| 1369 | } | |
| 1370 | ||
| 1371 | /// Send control flow to the `index` of `self.code`. | |
| 1372 | fn jump(self: *Self, src: usize, index: usize) !void { | |
| 1373 | switch (arch) { | |
| 1374 | .i386, .x86_64 => { | |
| 1375 | try self.code.ensureCapacity(self.code.items.len + 5); | |
| 1376 | if (math.cast(i8, @intCast(i32, index) - (@intCast(i32, self.code.items.len + 2)))) |delta| { | |
| 1377 | self.code.appendAssumeCapacity(0xeb); // jmp rel8 | |
| 1378 | self.code.appendAssumeCapacity(@bitCast(u8, delta)); | |
| 1379 | } else |_| { | |
| 1380 | const delta = @intCast(i32, index) - (@intCast(i32, self.code.items.len + 5)); | |
| 1381 | self.code.appendAssumeCapacity(0xe9); // jmp rel32 | |
| 1382 | mem.writeIntLittle(i32, self.code.addManyAsArrayAssumeCapacity(4), delta); | |
| 1383 | } | |
| 1384 | }, | |
| 1385 | else => return self.fail(src, "TODO implement jump for {}", .{self.target.cpu.arch}), | |
| 1386 | } | |
| 1364 | 1387 | } |
| 1365 | 1388 | |
| 1366 | 1389 | fn genBlock(self: *Self, inst: *ir.Inst.Block) !MCValue { |
| 1367 | 1390 | if (inst.base.ty.hasCodeGenBits()) { |
| 1368 | 1391 | return self.fail(inst.base.src, "TODO codegen Block with non-void type", .{}); |
| 1369 | 1392 | } |
| 1370 | // A block is nothing but a setup to be able to jump to the end. | |
| 1393 | // A block is a setup to be able to jump to the end. | |
| 1371 | 1394 | defer inst.codegen.relocs.deinit(self.gpa); |
| 1372 | 1395 | try self.genBody(inst.body); |
| 1373 | 1396 |
src-self-hosted/ir.zig+10-6| ... | ... | @@ -372,11 +372,11 @@ pub const Inst = struct { |
| 372 | 372 | then_body: Body, |
| 373 | 373 | else_body: Body, |
| 374 | 374 | /// Set of instructions whose lifetimes end at the start of one of the branches. |
| 375 | /// The `true` branch is first: `deaths[0..true_death_count]`. | |
| 376 | /// The `false` branch is next: `(deaths + true_death_count)[..false_death_count]`. | |
| 375 | /// The `then` branch is first: `deaths[0..then_death_count]`. | |
| 376 | /// The `else` branch is next: `(deaths + then_death_count)[0..else_death_count]`. | |
| 377 | 377 | deaths: [*]*Inst = undefined, |
| 378 | true_death_count: u32 = 0, | |
| 379 | false_death_count: u32 = 0, | |
| 378 | then_death_count: u32 = 0, | |
| 379 | else_death_count: u32 = 0, | |
| 380 | 380 | |
| 381 | 381 | pub fn operandCount(self: *const CondBr) usize { |
| 382 | 382 | return 1; |
| ... | ... | @@ -390,6 +390,12 @@ pub const Inst = struct { |
| 390 | 390 | |
| 391 | 391 | return null; |
| 392 | 392 | } |
| 393 | pub fn thenDeaths(self: *const CondBr) []*Inst { | |
| 394 | return self.deaths[0..self.then_death_count]; | |
| 395 | } | |
| 396 | pub fn elseDeaths(self: *const CondBr) []*Inst { | |
| 397 | return (self.deaths + self.then_death_count)[0..self.else_death_count]; | |
| 398 | } | |
| 393 | 399 | }; |
| 394 | 400 | |
| 395 | 401 | pub const Constant = struct { |
| ... | ... | @@ -411,8 +417,6 @@ pub const Inst = struct { |
| 411 | 417 | |
| 412 | 418 | base: Inst, |
| 413 | 419 | body: Body, |
| 414 | /// This memory is reserved for codegen code to do whatever it needs to here. | |
| 415 | codegen: codegen.LoopData = .{}, | |
| 416 | 420 | |
| 417 | 421 | pub fn operandCount(self: *const Loop) usize { |
| 418 | 422 | return 0; |
src-self-hosted/link.zig+2| ... | ... | @@ -1887,6 +1887,8 @@ pub const File = struct { |
| 1887 | 1887 | else => false, |
| 1888 | 1888 | }; |
| 1889 | 1889 | if (is_fn) { |
| 1890 | //typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*); | |
| 1891 | ||
| 1890 | 1892 | // For functions we need to add a prologue to the debug line program. |
| 1891 | 1893 | try dbg_line_buffer.ensureCapacity(26); |
| 1892 | 1894 |
src-self-hosted/liveness.zig+81-44| ... | ... | @@ -16,20 +16,42 @@ pub fn analyze( |
| 16 | 16 | var table = std.AutoHashMap(*ir.Inst, void).init(gpa); |
| 17 | 17 | defer table.deinit(); |
| 18 | 18 | try table.ensureCapacity(body.instructions.len); |
| 19 | try analyzeWithTable(arena, &table, body); | |
| 19 | try analyzeWithTable(arena, &table, null, body); | |
| 20 | 20 | } |
| 21 | 21 | |
| 22 | fn analyzeWithTable(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), body: ir.Body) error{OutOfMemory}!void { | |
| 22 | fn analyzeWithTable( | |
| 23 | arena: *std.mem.Allocator, | |
| 24 | table: *std.AutoHashMap(*ir.Inst, void), | |
| 25 | new_set: ?*std.AutoHashMap(*ir.Inst, void), | |
| 26 | body: ir.Body, | |
| 27 | ) error{OutOfMemory}!void { | |
| 23 | 28 | var i: usize = body.instructions.len; |
| 24 | 29 | |
| 25 | while (i != 0) { | |
| 26 | i -= 1; | |
| 27 | const base = body.instructions[i]; | |
| 28 | try analyzeInst(arena, table, base); | |
| 30 | if (new_set) |ns| { | |
| 31 | // We are only interested in doing this for instructions which are born | |
| 32 | // before a conditional branch, so after obtaining the new set for | |
| 33 | // each branch we prune the instructions which were born within. | |
| 34 | while (i != 0) { | |
| 35 | i -= 1; | |
| 36 | const base = body.instructions[i]; | |
| 37 | _ = ns.remove(base); | |
| 38 | try analyzeInst(arena, table, new_set, base); | |
| 39 | } | |
| 40 | } else { | |
| 41 | while (i != 0) { | |
| 42 | i -= 1; | |
| 43 | const base = body.instructions[i]; | |
| 44 | try analyzeInst(arena, table, new_set, base); | |
| 45 | } | |
| 29 | 46 | } |
| 30 | 47 | } |
| 31 | 48 | |
| 32 | fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), base: *ir.Inst) error{OutOfMemory}!void { | |
| 49 | fn analyzeInst( | |
| 50 | arena: *std.mem.Allocator, | |
| 51 | table: *std.AutoHashMap(*ir.Inst, void), | |
| 52 | new_set: ?*std.AutoHashMap(*ir.Inst, void), | |
| 53 | base: *ir.Inst, | |
| 54 | ) error{OutOfMemory}!void { | |
| 33 | 55 | if (table.contains(base)) { |
| 34 | 56 | base.deaths = 0; |
| 35 | 57 | } else { |
| ... | ... | @@ -42,56 +64,70 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void |
| 42 | 64 | .constant => return, |
| 43 | 65 | .block => { |
| 44 | 66 | const inst = base.castTag(.block).?; |
| 45 | try analyzeWithTable(arena, table, inst.body); | |
| 67 | try analyzeWithTable(arena, table, new_set, inst.body); | |
| 46 | 68 | // We let this continue so that it can possibly mark the block as |
| 47 | 69 | // unreferenced below. |
| 48 | 70 | }, |
| 71 | .loop => { | |
| 72 | const inst = base.castTag(.loop).?; | |
| 73 | try analyzeWithTable(arena, table, new_set, inst.body); | |
| 74 | return; // Loop has no operands and it is always unreferenced. | |
| 75 | }, | |
| 49 | 76 | .condbr => { |
| 50 | 77 | const inst = base.castTag(.condbr).?; |
| 51 | var true_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); | |
| 52 | defer true_table.deinit(); | |
| 53 | try true_table.ensureCapacity(inst.then_body.instructions.len); | |
| 54 | try analyzeWithTable(arena, &true_table, inst.then_body); | |
| 55 | ||
| 56 | var false_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); | |
| 57 | defer false_table.deinit(); | |
| 58 | try false_table.ensureCapacity(inst.else_body.instructions.len); | |
| 59 | try analyzeWithTable(arena, &false_table, inst.else_body); | |
| 60 | 78 | |
| 61 | 79 | // Each death that occurs inside one branch, but not the other, needs |
| 62 | 80 | // to be added as a death immediately upon entering the other branch. |
| 63 | // During the iteration of the table, we additionally propagate the | |
| 64 | // deaths to the parent table. | |
| 65 | var true_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); | |
| 66 | defer true_entry_deaths.deinit(); | |
| 67 | var false_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); | |
| 68 | defer false_entry_deaths.deinit(); | |
| 69 | { | |
| 70 | var it = false_table.iterator(); | |
| 71 | while (it.next()) |entry| { | |
| 72 | const false_death = entry.key; | |
| 73 | if (!true_table.contains(false_death)) { | |
| 74 | try true_entry_deaths.append(false_death); | |
| 75 | // Here we are only adding to the parent table if the following iteration | |
| 76 | // would miss it. | |
| 77 | try table.putNoClobber(false_death, {}); | |
| 78 | } | |
| 81 | ||
| 82 | var then_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); | |
| 83 | defer then_table.deinit(); | |
| 84 | try analyzeWithTable(arena, table, &then_table, inst.then_body); | |
| 85 | ||
| 86 | // Reset the table back to its state from before the branch. | |
| 87 | for (then_table.items()) |entry| { | |
| 88 | table.removeAssertDiscard(entry.key); | |
| 89 | } | |
| 90 | ||
| 91 | var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator); | |
| 92 | defer else_table.deinit(); | |
| 93 | try analyzeWithTable(arena, table, &else_table, inst.else_body); | |
| 94 | ||
| 95 | var then_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); | |
| 96 | defer then_entry_deaths.deinit(); | |
| 97 | var else_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator); | |
| 98 | defer else_entry_deaths.deinit(); | |
| 99 | ||
| 100 | for (else_table.items()) |entry| { | |
| 101 | const else_death = entry.key; | |
| 102 | if (!then_table.contains(else_death)) { | |
| 103 | try then_entry_deaths.append(else_death); | |
| 104 | } | |
| 105 | } | |
| 106 | // This loop is the same, except it's for the then branch, and it additionally | |
| 107 | // has to put its items back into the table to undo the reset. | |
| 108 | for (then_table.items()) |entry| { | |
| 109 | const then_death = entry.key; | |
| 110 | if (!else_table.contains(then_death)) { | |
| 111 | try else_entry_deaths.append(then_death); | |
| 79 | 112 | } |
| 113 | _ = try table.put(then_death, {}); | |
| 80 | 114 | } |
| 81 | { | |
| 82 | var it = true_table.iterator(); | |
| 83 | while (it.next()) |entry| { | |
| 84 | const true_death = entry.key; | |
| 85 | try table.putNoClobber(true_death, {}); | |
| 86 | if (!false_table.contains(true_death)) { | |
| 87 | try false_entry_deaths.append(true_death); | |
| 88 | } | |
| 115 | // Now we have to correctly populate new_set. | |
| 116 | if (new_set) |ns| { | |
| 117 | try ns.ensureCapacity(ns.items().len + then_table.items().len + else_table.items().len); | |
| 118 | for (then_table.items()) |entry| { | |
| 119 | _ = ns.putAssumeCapacity(entry.key, {}); | |
| 120 | } | |
| 121 | for (else_table.items()) |entry| { | |
| 122 | _ = ns.putAssumeCapacity(entry.key, {}); | |
| 89 | 123 | } |
| 90 | 124 | } |
| 91 | inst.true_death_count = std.math.cast(@TypeOf(inst.true_death_count), true_entry_deaths.items.len) catch return error.OutOfMemory; | |
| 92 | inst.false_death_count = std.math.cast(@TypeOf(inst.false_death_count), false_entry_deaths.items.len) catch return error.OutOfMemory; | |
| 93 | const allocated_slice = try arena.alloc(*ir.Inst, true_entry_deaths.items.len + false_entry_deaths.items.len); | |
| 125 | inst.then_death_count = std.math.cast(@TypeOf(inst.then_death_count), then_entry_deaths.items.len) catch return error.OutOfMemory; | |
| 126 | inst.else_death_count = std.math.cast(@TypeOf(inst.else_death_count), else_entry_deaths.items.len) catch return error.OutOfMemory; | |
| 127 | const allocated_slice = try arena.alloc(*ir.Inst, then_entry_deaths.items.len + else_entry_deaths.items.len); | |
| 94 | 128 | inst.deaths = allocated_slice.ptr; |
| 129 | std.mem.copy(*ir.Inst, inst.thenDeaths(), then_entry_deaths.items); | |
| 130 | std.mem.copy(*ir.Inst, inst.elseDeaths(), else_entry_deaths.items); | |
| 95 | 131 | |
| 96 | 132 | // Continue on with the instruction analysis. The following code will find the condition |
| 97 | 133 | // instruction, and the deaths flag for the CondBr instruction will indicate whether the |
| ... | ... | @@ -108,6 +144,7 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void |
| 108 | 144 | if (prev == null) { |
| 109 | 145 | // Death. |
| 110 | 146 | base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i; |
| 147 | if (new_set) |ns| try ns.putNoClobber(operand, {}); | |
| 111 | 148 | } |
| 112 | 149 | } |
| 113 | 150 | } else { |
src-self-hosted/zir.zig+177-82| ... | ... | @@ -822,6 +822,16 @@ pub const Module = struct { |
| 822 | 822 | decls: []*Decl, |
| 823 | 823 | arena: std.heap.ArenaAllocator, |
| 824 | 824 | error_msg: ?ErrorMsg = null, |
| 825 | metadata: std.AutoHashMap(*Inst, MetaData), | |
| 826 | body_metadata: std.AutoHashMap(*Body, BodyMetaData), | |
| 827 | ||
| 828 | pub const MetaData = struct { | |
| 829 | deaths: ir.Inst.DeathsInt, | |
| 830 | }; | |
| 831 | ||
| 832 | pub const BodyMetaData = struct { | |
| 833 | deaths: []*Inst, | |
| 834 | }; | |
| 825 | 835 | |
| 826 | 836 | pub const Body = struct { |
| 827 | 837 | instructions: []*Inst, |
| ... | ... | @@ -878,6 +888,7 @@ pub const Module = struct { |
| 878 | 888 | .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator), |
| 879 | 889 | .arena = std.heap.ArenaAllocator.init(allocator), |
| 880 | 890 | .indent = 2, |
| 891 | .next_instr_index = undefined, | |
| 881 | 892 | }; |
| 882 | 893 | defer write.arena.deinit(); |
| 883 | 894 | defer write.inst_table.deinit(); |
| ... | ... | @@ -889,15 +900,10 @@ pub const Module = struct { |
| 889 | 900 | |
| 890 | 901 | for (self.decls) |decl, decl_i| { |
| 891 | 902 | try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name }); |
| 892 | ||
| 893 | if (decl.inst.cast(Inst.Fn)) |fn_inst| { | |
| 894 | for (fn_inst.positionals.body.instructions) |inst, inst_i| { | |
| 895 | try write.inst_table.putNoClobber(inst, .{ .inst = inst, .index = inst_i, .name = undefined }); | |
| 896 | } | |
| 897 | } | |
| 898 | 903 | } |
| 899 | 904 | |
| 900 | 905 | for (self.decls) |decl, i| { |
| 906 | write.next_instr_index = 0; | |
| 901 | 907 | try stream.print("@{} ", .{decl.name}); |
| 902 | 908 | try write.writeInstToStream(stream, decl.inst); |
| 903 | 909 | try stream.writeByte('\n'); |
| ... | ... | @@ -914,6 +920,7 @@ const Writer = struct { |
| 914 | 920 | loop_table: std.AutoHashMap(*Inst.Loop, []const u8), |
| 915 | 921 | arena: std.heap.ArenaAllocator, |
| 916 | 922 | indent: usize, |
| 923 | next_instr_index: usize, | |
| 917 | 924 | |
| 918 | 925 | fn writeInstToStream( |
| 919 | 926 | self: *Writer, |
| ... | ... | @@ -944,7 +951,7 @@ const Writer = struct { |
| 944 | 951 | if (i != 0) { |
| 945 | 952 | try stream.writeAll(", "); |
| 946 | 953 | } |
| 947 | try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name)); | |
| 954 | try self.writeParamToStream(stream, &@field(inst.positionals, arg_field.name)); | |
| 948 | 955 | } |
| 949 | 956 | |
| 950 | 957 | comptime var need_comma = pos_fields.len != 0; |
| ... | ... | @@ -954,13 +961,13 @@ const Writer = struct { |
| 954 | 961 | if (@field(inst.kw_args, arg_field.name)) |non_optional| { |
| 955 | 962 | if (need_comma) try stream.writeAll(", "); |
| 956 | 963 | try stream.print("{}=", .{arg_field.name}); |
| 957 | try self.writeParamToStream(stream, non_optional); | |
| 964 | try self.writeParamToStream(stream, &non_optional); | |
| 958 | 965 | need_comma = true; |
| 959 | 966 | } |
| 960 | 967 | } else { |
| 961 | 968 | if (need_comma) try stream.writeAll(", "); |
| 962 | 969 | try stream.print("{}=", .{arg_field.name}); |
| 963 | try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name)); | |
| 970 | try self.writeParamToStream(stream, &@field(inst.kw_args, arg_field.name)); | |
| 964 | 971 | need_comma = true; |
| 965 | 972 | } |
| 966 | 973 | } |
| ... | ... | @@ -968,7 +975,8 @@ const Writer = struct { |
| 968 | 975 | try stream.writeByte(')'); |
| 969 | 976 | } |
| 970 | 977 | |
| 971 | fn writeParamToStream(self: *Writer, stream: anytype, param: anytype) !void { | |
| 978 | fn writeParamToStream(self: *Writer, stream: anytype, param_ptr: anytype) !void { | |
| 979 | const param = param_ptr.*; | |
| 972 | 980 | if (@typeInfo(@TypeOf(param)) == .Enum) { |
| 973 | 981 | return stream.writeAll(@tagName(param)); |
| 974 | 982 | } |
| ... | ... | @@ -986,18 +994,36 @@ const Writer = struct { |
| 986 | 994 | }, |
| 987 | 995 | Module.Body => { |
| 988 | 996 | try stream.writeAll("{\n"); |
| 989 | for (param.instructions) |inst, i| { | |
| 997 | if (self.module.body_metadata.get(param_ptr)) |metadata| { | |
| 998 | if (metadata.deaths.len > 0) { | |
| 999 | try stream.writeByteNTimes(' ', self.indent); | |
| 1000 | try stream.writeAll("; deaths={"); | |
| 1001 | for (metadata.deaths) |death, i| { | |
| 1002 | if (i != 0) try stream.writeAll(", "); | |
| 1003 | try self.writeInstParamToStream(stream, death); | |
| 1004 | } | |
| 1005 | try stream.writeAll("}\n"); | |
| 1006 | } | |
| 1007 | } | |
| 1008 | ||
| 1009 | for (param.instructions) |inst| { | |
| 1010 | const my_i = self.next_instr_index; | |
| 1011 | self.next_instr_index += 1; | |
| 1012 | try self.inst_table.putNoClobber(inst, .{ .inst = inst, .index = my_i, .name = undefined }); | |
| 990 | 1013 | try stream.writeByteNTimes(' ', self.indent); |
| 991 | try stream.print("%{} ", .{i}); | |
| 1014 | try stream.print("%{} ", .{my_i}); | |
| 992 | 1015 | if (inst.cast(Inst.Block)) |block| { |
| 993 | const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{}", .{i}); | |
| 1016 | const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{}", .{my_i}); | |
| 994 | 1017 | try self.block_table.put(block, name); |
| 995 | 1018 | } else if (inst.cast(Inst.Loop)) |loop| { |
| 996 | const name = try std.fmt.allocPrint(&self.arena.allocator, "loop_{}", .{i}); | |
| 1019 | const name = try std.fmt.allocPrint(&self.arena.allocator, "loop_{}", .{my_i}); | |
| 997 | 1020 | try self.loop_table.put(loop, name); |
| 998 | 1021 | } |
| 999 | 1022 | self.indent += 2; |
| 1000 | 1023 | try self.writeInstToStream(stream, inst); |
| 1024 | if (self.module.metadata.get(inst)) |metadata| { | |
| 1025 | try stream.print(" ; deaths=0b{b}", .{metadata.deaths}); | |
| 1026 | } | |
| 1001 | 1027 | self.indent -= 2; |
| 1002 | 1028 | try stream.writeByte('\n'); |
| 1003 | 1029 | } |
| ... | ... | @@ -1070,6 +1096,8 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module |
| 1070 | 1096 | .decls = parser.decls.toOwnedSlice(allocator), |
| 1071 | 1097 | .arena = parser.arena, |
| 1072 | 1098 | .error_msg = parser.error_msg, |
| 1099 | .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), | |
| 1100 | .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), | |
| 1073 | 1101 | }; |
| 1074 | 1102 | } |
| 1075 | 1103 | |
| ... | ... | @@ -1478,7 +1506,11 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { |
| 1478 | 1506 | .indent = 0, |
| 1479 | 1507 | .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), |
| 1480 | 1508 | .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator), |
| 1509 | .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), | |
| 1510 | .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), | |
| 1481 | 1511 | }; |
| 1512 | defer ctx.metadata.deinit(); | |
| 1513 | defer ctx.body_metadata.deinit(); | |
| 1482 | 1514 | defer ctx.block_table.deinit(); |
| 1483 | 1515 | defer ctx.loop_table.deinit(); |
| 1484 | 1516 | defer ctx.decls.deinit(allocator); |
| ... | ... | @@ -1491,7 +1523,50 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module { |
| 1491 | 1523 | return Module{ |
| 1492 | 1524 | .decls = ctx.decls.toOwnedSlice(allocator), |
| 1493 | 1525 | .arena = ctx.arena, |
| 1526 | .metadata = ctx.metadata, | |
| 1527 | .body_metadata = ctx.body_metadata, | |
| 1528 | }; | |
| 1529 | } | |
| 1530 | ||
| 1531 | /// For debugging purposes, prints a function representation to stderr. | |
| 1532 | pub fn dumpFn(old_module: IrModule, module_fn: *IrModule.Fn) void { | |
| 1533 | const allocator = old_module.gpa; | |
| 1534 | var ctx: EmitZIR = .{ | |
| 1535 | .allocator = allocator, | |
| 1536 | .decls = .{}, | |
| 1537 | .arena = std.heap.ArenaAllocator.init(allocator), | |
| 1538 | .old_module = &old_module, | |
| 1539 | .next_auto_name = 0, | |
| 1540 | .names = std.StringHashMap(void).init(allocator), | |
| 1541 | .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator), | |
| 1542 | .indent = 0, | |
| 1543 | .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator), | |
| 1544 | .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator), | |
| 1545 | .metadata = std.AutoHashMap(*Inst, Module.MetaData).init(allocator), | |
| 1546 | .body_metadata = std.AutoHashMap(*Module.Body, Module.BodyMetaData).init(allocator), | |
| 1547 | }; | |
| 1548 | defer ctx.metadata.deinit(); | |
| 1549 | defer ctx.body_metadata.deinit(); | |
| 1550 | defer ctx.block_table.deinit(); | |
| 1551 | defer ctx.loop_table.deinit(); | |
| 1552 | defer ctx.decls.deinit(allocator); | |
| 1553 | defer ctx.names.deinit(); | |
| 1554 | defer ctx.primitive_table.deinit(); | |
| 1555 | defer ctx.arena.deinit(); | |
| 1556 | ||
| 1557 | const fn_ty = module_fn.owner_decl.typed_value.most_recent.typed_value.ty; | |
| 1558 | _ = ctx.emitFn(module_fn, 0, fn_ty) catch |err| { | |
| 1559 | std.debug.print("unable to dump function: {}\n", .{err}); | |
| 1560 | return; | |
| 1561 | }; | |
| 1562 | var module = Module{ | |
| 1563 | .decls = ctx.decls.items, | |
| 1564 | .arena = ctx.arena, | |
| 1565 | .metadata = ctx.metadata, | |
| 1566 | .body_metadata = ctx.body_metadata, | |
| 1494 | 1567 | }; |
| 1568 | ||
| 1569 | module.dump(); | |
| 1495 | 1570 | } |
| 1496 | 1571 | |
| 1497 | 1572 | const EmitZIR = struct { |
| ... | ... | @@ -1505,6 +1580,8 @@ const EmitZIR = struct { |
| 1505 | 1580 | indent: usize, |
| 1506 | 1581 | block_table: std.AutoHashMap(*ir.Inst.Block, *Inst.Block), |
| 1507 | 1582 | loop_table: std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop), |
| 1583 | metadata: std.AutoHashMap(*Inst, Module.MetaData), | |
| 1584 | body_metadata: std.AutoHashMap(*Module.Body, Module.BodyMetaData), | |
| 1508 | 1585 | |
| 1509 | 1586 | fn emit(self: *EmitZIR) !void { |
| 1510 | 1587 | // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced |
| ... | ... | @@ -1604,7 +1681,7 @@ const EmitZIR = struct { |
| 1604 | 1681 | } else blk: { |
| 1605 | 1682 | break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst; |
| 1606 | 1683 | }; |
| 1607 | try new_body.inst_table.putNoClobber(inst, new_inst); | |
| 1684 | _ = try new_body.inst_table.put(inst, new_inst); | |
| 1608 | 1685 | return new_inst; |
| 1609 | 1686 | } else { |
| 1610 | 1687 | return new_body.inst_table.get(inst).?; |
| ... | ... | @@ -1655,6 +1732,70 @@ const EmitZIR = struct { |
| 1655 | 1732 | return &declref_inst.base; |
| 1656 | 1733 | } |
| 1657 | 1734 | |
| 1735 | fn emitFn(self: *EmitZIR, module_fn: *IrModule.Fn, src: usize, ty: Type) Allocator.Error!*Decl { | |
| 1736 | var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); | |
| 1737 | defer inst_table.deinit(); | |
| 1738 | ||
| 1739 | var instructions = std.ArrayList(*Inst).init(self.allocator); | |
| 1740 | defer instructions.deinit(); | |
| 1741 | ||
| 1742 | switch (module_fn.analysis) { | |
| 1743 | .queued => unreachable, | |
| 1744 | .in_progress => unreachable, | |
| 1745 | .success => |body| { | |
| 1746 | try self.emitBody(body, &inst_table, &instructions); | |
| 1747 | }, | |
| 1748 | .sema_failure => { | |
| 1749 | const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?; | |
| 1750 | const fail_inst = try self.arena.allocator.create(Inst.CompileError); | |
| 1751 | fail_inst.* = .{ | |
| 1752 | .base = .{ | |
| 1753 | .src = src, | |
| 1754 | .tag = Inst.CompileError.base_tag, | |
| 1755 | }, | |
| 1756 | .positionals = .{ | |
| 1757 | .msg = try self.arena.allocator.dupe(u8, err_msg.msg), | |
| 1758 | }, | |
| 1759 | .kw_args = .{}, | |
| 1760 | }; | |
| 1761 | try instructions.append(&fail_inst.base); | |
| 1762 | }, | |
| 1763 | .dependency_failure => { | |
| 1764 | const fail_inst = try self.arena.allocator.create(Inst.CompileError); | |
| 1765 | fail_inst.* = .{ | |
| 1766 | .base = .{ | |
| 1767 | .src = src, | |
| 1768 | .tag = Inst.CompileError.base_tag, | |
| 1769 | }, | |
| 1770 | .positionals = .{ | |
| 1771 | .msg = try self.arena.allocator.dupe(u8, "depends on another failed Decl"), | |
| 1772 | }, | |
| 1773 | .kw_args = .{}, | |
| 1774 | }; | |
| 1775 | try instructions.append(&fail_inst.base); | |
| 1776 | }, | |
| 1777 | } | |
| 1778 | ||
| 1779 | const fn_type = try self.emitType(src, ty); | |
| 1780 | ||
| 1781 | const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len); | |
| 1782 | mem.copy(*Inst, arena_instrs, instructions.items); | |
| 1783 | ||
| 1784 | const fn_inst = try self.arena.allocator.create(Inst.Fn); | |
| 1785 | fn_inst.* = .{ | |
| 1786 | .base = .{ | |
| 1787 | .src = src, | |
| 1788 | .tag = Inst.Fn.base_tag, | |
| 1789 | }, | |
| 1790 | .positionals = .{ | |
| 1791 | .fn_type = fn_type.inst, | |
| 1792 | .body = .{ .instructions = arena_instrs }, | |
| 1793 | }, | |
| 1794 | .kw_args = .{}, | |
| 1795 | }; | |
| 1796 | return self.emitUnnamedDecl(&fn_inst.base); | |
| 1797 | } | |
| 1798 | ||
| 1658 | 1799 | fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Decl { |
| 1659 | 1800 | const allocator = &self.arena.allocator; |
| 1660 | 1801 | if (typed_value.val.cast(Value.Payload.DeclRef)) |decl_ref| { |
| ... | ... | @@ -1718,68 +1859,7 @@ const EmitZIR = struct { |
| 1718 | 1859 | }, |
| 1719 | 1860 | .Fn => { |
| 1720 | 1861 | const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; |
| 1721 | ||
| 1722 | var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); | |
| 1723 | defer inst_table.deinit(); | |
| 1724 | ||
| 1725 | var instructions = std.ArrayList(*Inst).init(self.allocator); | |
| 1726 | defer instructions.deinit(); | |
| 1727 | ||
| 1728 | switch (module_fn.analysis) { | |
| 1729 | .queued => unreachable, | |
| 1730 | .in_progress => unreachable, | |
| 1731 | .success => |body| { | |
| 1732 | try self.emitBody(body, &inst_table, &instructions); | |
| 1733 | }, | |
| 1734 | .sema_failure => { | |
| 1735 | const err_msg = self.old_module.failed_decls.get(module_fn.owner_decl).?; | |
| 1736 | const fail_inst = try self.arena.allocator.create(Inst.CompileError); | |
| 1737 | fail_inst.* = .{ | |
| 1738 | .base = .{ | |
| 1739 | .src = src, | |
| 1740 | .tag = Inst.CompileError.base_tag, | |
| 1741 | }, | |
| 1742 | .positionals = .{ | |
| 1743 | .msg = try self.arena.allocator.dupe(u8, err_msg.msg), | |
| 1744 | }, | |
| 1745 | .kw_args = .{}, | |
| 1746 | }; | |
| 1747 | try instructions.append(&fail_inst.base); | |
| 1748 | }, | |
| 1749 | .dependency_failure => { | |
| 1750 | const fail_inst = try self.arena.allocator.create(Inst.CompileError); | |
| 1751 | fail_inst.* = .{ | |
| 1752 | .base = .{ | |
| 1753 | .src = src, | |
| 1754 | .tag = Inst.CompileError.base_tag, | |
| 1755 | }, | |
| 1756 | .positionals = .{ | |
| 1757 | .msg = try self.arena.allocator.dupe(u8, "depends on another failed Decl"), | |
| 1758 | }, | |
| 1759 | .kw_args = .{}, | |
| 1760 | }; | |
| 1761 | try instructions.append(&fail_inst.base); | |
| 1762 | }, | |
| 1763 | } | |
| 1764 | ||
| 1765 | const fn_type = try self.emitType(src, typed_value.ty); | |
| 1766 | ||
| 1767 | const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len); | |
| 1768 | mem.copy(*Inst, arena_instrs, instructions.items); | |
| 1769 | ||
| 1770 | const fn_inst = try self.arena.allocator.create(Inst.Fn); | |
| 1771 | fn_inst.* = .{ | |
| 1772 | .base = .{ | |
| 1773 | .src = src, | |
| 1774 | .tag = Inst.Fn.base_tag, | |
| 1775 | }, | |
| 1776 | .positionals = .{ | |
| 1777 | .fn_type = fn_type.inst, | |
| 1778 | .body = .{ .instructions = arena_instrs }, | |
| 1779 | }, | |
| 1780 | .kw_args = .{}, | |
| 1781 | }; | |
| 1782 | return self.emitUnnamedDecl(&fn_inst.base); | |
| 1862 | return self.emitFn(module_fn, src, typed_value.ty); | |
| 1783 | 1863 | }, |
| 1784 | 1864 | .Array => { |
| 1785 | 1865 | // TODO more checks to make sure this can be emitted as a string literal |
| ... | ... | @@ -1810,7 +1890,7 @@ const EmitZIR = struct { |
| 1810 | 1890 | } |
| 1811 | 1891 | } |
| 1812 | 1892 | |
| 1813 | fn emitNoOp(self: *EmitZIR, src: usize, tag: Inst.Tag) Allocator.Error!*Inst { | |
| 1893 | fn emitNoOp(self: *EmitZIR, src: usize, old_inst: *ir.Inst.NoOp, tag: Inst.Tag) Allocator.Error!*Inst { | |
| 1814 | 1894 | const new_inst = try self.arena.allocator.create(Inst.NoOp); |
| 1815 | 1895 | new_inst.* = .{ |
| 1816 | 1896 | .base = .{ |
| ... | ... | @@ -1902,10 +1982,10 @@ const EmitZIR = struct { |
| 1902 | 1982 | const new_inst = switch (inst.tag) { |
| 1903 | 1983 | .constant => unreachable, // excluded from function bodies |
| 1904 | 1984 | |
| 1905 | .breakpoint => try self.emitNoOp(inst.src, .breakpoint), | |
| 1906 | .unreach => try self.emitNoOp(inst.src, .@"unreachable"), | |
| 1907 | .retvoid => try self.emitNoOp(inst.src, .returnvoid), | |
| 1908 | .dbg_stmt => try self.emitNoOp(inst.src, .dbg_stmt), | |
| 1985 | .breakpoint => try self.emitNoOp(inst.src, inst.castTag(.breakpoint).?, .breakpoint), | |
| 1986 | .unreach => try self.emitNoOp(inst.src, inst.castTag(.unreach).?, .unreach_nocheck), | |
| 1987 | .retvoid => try self.emitNoOp(inst.src, inst.castTag(.retvoid).?, .returnvoid), | |
| 1988 | .dbg_stmt => try self.emitNoOp(inst.src, inst.castTag(.dbg_stmt).?, .dbg_stmt), | |
| 1909 | 1989 | |
| 1910 | 1990 | .not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot), |
| 1911 | 1991 | .ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"), |
| ... | ... | @@ -2119,10 +2199,24 @@ const EmitZIR = struct { |
| 2119 | 2199 | defer then_body.deinit(); |
| 2120 | 2200 | defer else_body.deinit(); |
| 2121 | 2201 | |
| 2202 | const then_deaths = try self.arena.allocator.alloc(*Inst, old_inst.thenDeaths().len); | |
| 2203 | const else_deaths = try self.arena.allocator.alloc(*Inst, old_inst.elseDeaths().len); | |
| 2204 | ||
| 2205 | for (old_inst.thenDeaths()) |death, i| { | |
| 2206 | then_deaths[i] = try self.resolveInst(new_body, death); | |
| 2207 | } | |
| 2208 | for (old_inst.elseDeaths()) |death, i| { | |
| 2209 | else_deaths[i] = try self.resolveInst(new_body, death); | |
| 2210 | } | |
| 2211 | ||
| 2122 | 2212 | try self.emitBody(old_inst.then_body, inst_table, &then_body); |
| 2123 | 2213 | try self.emitBody(old_inst.else_body, inst_table, &else_body); |
| 2124 | 2214 | |
| 2125 | 2215 | const new_inst = try self.arena.allocator.create(Inst.CondBr); |
| 2216 | ||
| 2217 | try self.body_metadata.put(&new_inst.positionals.then_body, .{ .deaths = then_deaths }); | |
| 2218 | try self.body_metadata.put(&new_inst.positionals.else_body, .{ .deaths = else_deaths }); | |
| 2219 | ||
| 2126 | 2220 | new_inst.* = .{ |
| 2127 | 2221 | .base = .{ |
| 2128 | 2222 | .src = inst.src, |
| ... | ... | @@ -2138,6 +2232,7 @@ const EmitZIR = struct { |
| 2138 | 2232 | break :blk &new_inst.base; |
| 2139 | 2233 | }, |
| 2140 | 2234 | }; |
| 2235 | try self.metadata.put(new_inst, .{ .deaths = inst.deaths }); | |
| 2141 | 2236 | try instructions.append(new_inst); |
| 2142 | 2237 | try inst_table.put(inst, new_inst); |
| 2143 | 2238 | } |
test/stage2/compare_output.zig+39| ... | ... | @@ -465,5 +465,44 @@ pub fn addCases(ctx: *TestContext) !void { |
| 465 | 465 | , |
| 466 | 466 | "", |
| 467 | 467 | ); |
| 468 | ||
| 469 | // While loops | |
| 470 | case.addCompareOutput( | |
| 471 | \\export fn _start() noreturn { | |
| 472 | \\ var i: u32 = 0; | |
| 473 | \\ while (i < 4) : (i += 1) print(); | |
| 474 | \\ assert(i == 4); | |
| 475 | \\ | |
| 476 | \\ exit(); | |
| 477 | \\} | |
| 478 | \\ | |
| 479 | \\fn print() void { | |
| 480 | \\ asm volatile ("syscall" | |
| 481 | \\ : | |
| 482 | \\ : [number] "{rax}" (1), | |
| 483 | \\ [arg1] "{rdi}" (1), | |
| 484 | \\ [arg2] "{rsi}" (@ptrToInt("hello\n")), | |
| 485 | \\ [arg3] "{rdx}" (6) | |
| 486 | \\ : "rcx", "r11", "memory" | |
| 487 | \\ ); | |
| 488 | \\ return; | |
| 489 | \\} | |
| 490 | \\ | |
| 491 | \\pub fn assert(ok: bool) void { | |
| 492 | \\ if (!ok) unreachable; // assertion failure | |
| 493 | \\} | |
| 494 | \\ | |
| 495 | \\fn exit() noreturn { | |
| 496 | \\ asm volatile ("syscall" | |
| 497 | \\ : | |
| 498 | \\ : [number] "{rax}" (231), | |
| 499 | \\ [arg1] "{rdi}" (0) | |
| 500 | \\ : "rcx", "r11", "memory" | |
| 501 | \\ ); | |
| 502 | \\ unreachable; | |
| 503 | \\} | |
| 504 | , | |
| 505 | "hello\nhello\nhello\nhello\n", | |
| 506 | ); | |
| 468 | 507 | } |
| 469 | 508 | } |