authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-13 20:27:25-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-13 20:32:32-07:00
log28a9da8bfc1a791e0eaf8c643827da88ea70f7d1
treeb15f8dfea2858c058d878d4917812d28202fe727
parent576581bd7b78825ce27d6a73fc42dd90eab8fbd1

stage2: implement while loops (bool condition)

* 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 #6021

8 files changed, 355 insertions(+), 137 deletions(-)

lib/std/math.zig+1
...@@ -747,6 +747,7 @@ test "math.negateCast" {...@@ -747,6 +747,7 @@ test "math.negateCast" {
747747
748/// Cast an integer to a different integer type. If the value doesn't fit,748/// Cast an integer to a different integer type. If the value doesn't fit,
749/// return an error.749/// return an error.
750/// TODO make this an optional not an error.
750pub fn cast(comptime T: type, x: anytype) (error{Overflow}!T) {751pub fn cast(comptime T: type, x: anytype) (error{Overflow}!T) {
751 comptime assert(@typeInfo(T) == .Int); // must pass an integer752 comptime assert(@typeInfo(T) == .Int); // must pass an integer
752 comptime assert(@typeInfo(@TypeOf(x)) == .Int); // must pass an integer753 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,6 +301,23 @@ pub const Fn = struct {
301 body: zir.Module.Body,301 body: zir.Module.Body,
302 arena: std.heap.ArenaAllocator.State,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};
305322
306pub const Scope = struct {323pub const Scope = struct {
src-self-hosted/codegen.zig+28-5
...@@ -23,8 +23,6 @@ pub const BlockData = struct {...@@ -23,8 +23,6 @@ pub const BlockData = struct {
23 relocs: std.ArrayListUnmanaged(Reloc) = .{},23 relocs: std.ArrayListUnmanaged(Reloc) = .{},
24};24};
2525
26pub const LoopData = struct { };
27
28pub const Reloc = union(enum) {26pub const Reloc = union(enum) {
29 /// The value is an offset into the `Function` `code` from the beginning.27 /// The value is an offset into the `Function` `code` from the beginning.
30 /// To perform the reloc, write 32-bit signed little-endian integer28 /// 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,7 +554,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
556 }554 }
557555
558 fn genBody(self: *Self, body: ir.Body) InnerError!void {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 for (body.instructions) |inst| {559 for (body.instructions) |inst| {
561 const new_inst = try self.genFuncInst(inst);560 const new_inst = try self.genFuncInst(inst);
562 try inst_table.putNoClobber(self.gpa, inst, new_inst);561 try inst_table.putNoClobber(self.gpa, inst, new_inst);
...@@ -1284,6 +1283,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1284,6 +1283,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1284 }1283 }
12851284
1286 fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue {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 switch (arch) {1287 switch (arch) {
1288 .x86_64 => {1288 .x86_64 => {
1289 try self.code.ensureCapacity(self.code.items.len + 6);1289 try self.code.ensureCapacity(self.code.items.len + 6);
...@@ -1336,6 +1336,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1336,6 +1336,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1336 }1336 }
13371337
1338 fn genX86CondBr(self: *Self, inst: *ir.Inst.CondBr, opcode: u8) !MCValue {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 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode });1340 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode });
1340 const reloc = Reloc{ .rel32 = self.code.items.len };1341 const reloc = Reloc{ .rel32 = self.code.items.len };
1341 self.code.items.len += 4;1342 self.code.items.len += 4;
...@@ -1360,14 +1361,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1360,14 +1361,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1360 }1361 }
13611362
1362 fn genLoop(self: *Self, inst: *ir.Inst.Loop) !MCValue {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 }
13651388
1366 fn genBlock(self: *Self, inst: *ir.Inst.Block) !MCValue {1389 fn genBlock(self: *Self, inst: *ir.Inst.Block) !MCValue {
1367 if (inst.base.ty.hasCodeGenBits()) {1390 if (inst.base.ty.hasCodeGenBits()) {
1368 return self.fail(inst.base.src, "TODO codegen Block with non-void type", .{});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 defer inst.codegen.relocs.deinit(self.gpa);1394 defer inst.codegen.relocs.deinit(self.gpa);
1372 try self.genBody(inst.body);1395 try self.genBody(inst.body);
13731396
src-self-hosted/ir.zig+10-6
...@@ -372,11 +372,11 @@ pub const Inst = struct {...@@ -372,11 +372,11 @@ pub const Inst = struct {
372 then_body: Body,372 then_body: Body,
373 else_body: Body,373 else_body: Body,
374 /// Set of instructions whose lifetimes end at the start of one of the branches.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]`.375 /// The `then` branch is first: `deaths[0..then_death_count]`.
376 /// The `false` branch is next: `(deaths + true_death_count)[..false_death_count]`.376 /// The `else` branch is next: `(deaths + then_death_count)[0..else_death_count]`.
377 deaths: [*]*Inst = undefined,377 deaths: [*]*Inst = undefined,
378 true_death_count: u32 = 0,378 then_death_count: u32 = 0,
379 false_death_count: u32 = 0,379 else_death_count: u32 = 0,
380380
381 pub fn operandCount(self: *const CondBr) usize {381 pub fn operandCount(self: *const CondBr) usize {
382 return 1;382 return 1;
...@@ -390,6 +390,12 @@ pub const Inst = struct {...@@ -390,6 +390,12 @@ pub const Inst = struct {
390390
391 return null;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 };
394400
395 pub const Constant = struct {401 pub const Constant = struct {
...@@ -411,8 +417,6 @@ pub const Inst = struct {...@@ -411,8 +417,6 @@ pub const Inst = struct {
411417
412 base: Inst,418 base: Inst,
413 body: Body,419 body: Body,
414 /// This memory is reserved for codegen code to do whatever it needs to here.
415 codegen: codegen.LoopData = .{},
416420
417 pub fn operandCount(self: *const Loop) usize {421 pub fn operandCount(self: *const Loop) usize {
418 return 0;422 return 0;
src-self-hosted/link.zig+2
...@@ -1887,6 +1887,8 @@ pub const File = struct {...@@ -1887,6 +1887,8 @@ pub const File = struct {
1887 else => false,1887 else => false,
1888 };1888 };
1889 if (is_fn) {1889 if (is_fn) {
1890 //typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*);
1891
1890 // For functions we need to add a prologue to the debug line program.1892 // For functions we need to add a prologue to the debug line program.
1891 try dbg_line_buffer.ensureCapacity(26);1893 try dbg_line_buffer.ensureCapacity(26);
18921894
src-self-hosted/liveness.zig+81-44
...@@ -16,20 +16,42 @@ pub fn analyze(...@@ -16,20 +16,42 @@ pub fn analyze(
16 var table = std.AutoHashMap(*ir.Inst, void).init(gpa);16 var table = std.AutoHashMap(*ir.Inst, void).init(gpa);
17 defer table.deinit();17 defer table.deinit();
18 try table.ensureCapacity(body.instructions.len);18 try table.ensureCapacity(body.instructions.len);
19 try analyzeWithTable(arena, &table, body);19 try analyzeWithTable(arena, &table, null, body);
20}20}
2121
22fn analyzeWithTable(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), body: ir.Body) error{OutOfMemory}!void {22fn 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 var i: usize = body.instructions.len;28 var i: usize = body.instructions.len;
2429
25 while (i != 0) {30 if (new_set) |ns| {
26 i -= 1;31 // We are only interested in doing this for instructions which are born
27 const base = body.instructions[i];32 // before a conditional branch, so after obtaining the new set for
28 try analyzeInst(arena, table, base);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}
3148
32fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void), base: *ir.Inst) error{OutOfMemory}!void {49fn 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 if (table.contains(base)) {55 if (table.contains(base)) {
34 base.deaths = 0;56 base.deaths = 0;
35 } else {57 } else {
...@@ -42,56 +64,70 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void...@@ -42,56 +64,70 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void
42 .constant => return,64 .constant => return,
43 .block => {65 .block => {
44 const inst = base.castTag(.block).?;66 const inst = base.castTag(.block).?;
45 try analyzeWithTable(arena, table, inst.body);67 try analyzeWithTable(arena, table, new_set, inst.body);
46 // We let this continue so that it can possibly mark the block as68 // We let this continue so that it can possibly mark the block as
47 // unreferenced below.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 .condbr => {76 .condbr => {
50 const inst = base.castTag(.condbr).?;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);
6078
61 // Each death that occurs inside one branch, but not the other, needs79 // Each death that occurs inside one branch, but not the other, needs
62 // to be added as a death immediately upon entering the other branch.80 // to be added as a death immediately upon entering the other branch.
63 // During the iteration of the table, we additionally propagate the81
64 // deaths to the parent table.82 var then_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);
65 var true_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator);83 defer then_table.deinit();
66 defer true_entry_deaths.deinit();84 try analyzeWithTable(arena, table, &then_table, inst.then_body);
67 var false_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator);85
68 defer false_entry_deaths.deinit();86 // Reset the table back to its state from before the branch.
69 {87 for (then_table.items()) |entry| {
70 var it = false_table.iterator();88 table.removeAssertDiscard(entry.key);
71 while (it.next()) |entry| {89 }
72 const false_death = entry.key;90
73 if (!true_table.contains(false_death)) {91 var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);
74 try true_entry_deaths.append(false_death);92 defer else_table.deinit();
75 // Here we are only adding to the parent table if the following iteration93 try analyzeWithTable(arena, table, &else_table, inst.else_body);
76 // would miss it.94
77 try table.putNoClobber(false_death, {});95 var then_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator);
78 }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 {115 // Now we have to correctly populate new_set.
82 var it = true_table.iterator();116 if (new_set) |ns| {
83 while (it.next()) |entry| {117 try ns.ensureCapacity(ns.items().len + then_table.items().len + else_table.items().len);
84 const true_death = entry.key;118 for (then_table.items()) |entry| {
85 try table.putNoClobber(true_death, {});119 _ = ns.putAssumeCapacity(entry.key, {});
86 if (!false_table.contains(true_death)) {120 }
87 try false_entry_deaths.append(true_death);121 for (else_table.items()) |entry| {
88 }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;125 inst.then_death_count = std.math.cast(@TypeOf(inst.then_death_count), then_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;126 inst.else_death_count = std.math.cast(@TypeOf(inst.else_death_count), else_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);127 const allocated_slice = try arena.alloc(*ir.Inst, then_entry_deaths.items.len + else_entry_deaths.items.len);
94 inst.deaths = allocated_slice.ptr;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);
95131
96 // Continue on with the instruction analysis. The following code will find the condition132 // Continue on with the instruction analysis. The following code will find the condition
97 // instruction, and the deaths flag for the CondBr instruction will indicate whether the133 // 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,6 +144,7 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void
108 if (prev == null) {144 if (prev == null) {
109 // Death.145 // Death.
110 base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i;146 base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i;
147 if (new_set) |ns| try ns.putNoClobber(operand, {});
111 }148 }
112 }149 }
113 } else {150 } else {
src-self-hosted/zir.zig+177-82
...@@ -822,6 +822,16 @@ pub const Module = struct {...@@ -822,6 +822,16 @@ pub const Module = struct {
822 decls: []*Decl,822 decls: []*Decl,
823 arena: std.heap.ArenaAllocator,823 arena: std.heap.ArenaAllocator,
824 error_msg: ?ErrorMsg = null,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 };
825835
826 pub const Body = struct {836 pub const Body = struct {
827 instructions: []*Inst,837 instructions: []*Inst,
...@@ -878,6 +888,7 @@ pub const Module = struct {...@@ -878,6 +888,7 @@ pub const Module = struct {
878 .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator),888 .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator),
879 .arena = std.heap.ArenaAllocator.init(allocator),889 .arena = std.heap.ArenaAllocator.init(allocator),
880 .indent = 2,890 .indent = 2,
891 .next_instr_index = undefined,
881 };892 };
882 defer write.arena.deinit();893 defer write.arena.deinit();
883 defer write.inst_table.deinit();894 defer write.inst_table.deinit();
...@@ -889,15 +900,10 @@ pub const Module = struct {...@@ -889,15 +900,10 @@ pub const Module = struct {
889900
890 for (self.decls) |decl, decl_i| {901 for (self.decls) |decl, decl_i| {
891 try write.inst_table.putNoClobber(decl.inst, .{ .inst = decl.inst, .index = null, .name = decl.name });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 }
899904
900 for (self.decls) |decl, i| {905 for (self.decls) |decl, i| {
906 write.next_instr_index = 0;
901 try stream.print("@{} ", .{decl.name});907 try stream.print("@{} ", .{decl.name});
902 try write.writeInstToStream(stream, decl.inst);908 try write.writeInstToStream(stream, decl.inst);
903 try stream.writeByte('\n');909 try stream.writeByte('\n');
...@@ -914,6 +920,7 @@ const Writer = struct {...@@ -914,6 +920,7 @@ const Writer = struct {
914 loop_table: std.AutoHashMap(*Inst.Loop, []const u8),920 loop_table: std.AutoHashMap(*Inst.Loop, []const u8),
915 arena: std.heap.ArenaAllocator,921 arena: std.heap.ArenaAllocator,
916 indent: usize,922 indent: usize,
923 next_instr_index: usize,
917924
918 fn writeInstToStream(925 fn writeInstToStream(
919 self: *Writer,926 self: *Writer,
...@@ -944,7 +951,7 @@ const Writer = struct {...@@ -944,7 +951,7 @@ const Writer = struct {
944 if (i != 0) {951 if (i != 0) {
945 try stream.writeAll(", ");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 }
949956
950 comptime var need_comma = pos_fields.len != 0;957 comptime var need_comma = pos_fields.len != 0;
...@@ -954,13 +961,13 @@ const Writer = struct {...@@ -954,13 +961,13 @@ const Writer = struct {
954 if (@field(inst.kw_args, arg_field.name)) |non_optional| {961 if (@field(inst.kw_args, arg_field.name)) |non_optional| {
955 if (need_comma) try stream.writeAll(", ");962 if (need_comma) try stream.writeAll(", ");
956 try stream.print("{}=", .{arg_field.name});963 try stream.print("{}=", .{arg_field.name});
957 try self.writeParamToStream(stream, non_optional);964 try self.writeParamToStream(stream, &non_optional);
958 need_comma = true;965 need_comma = true;
959 }966 }
960 } else {967 } else {
961 if (need_comma) try stream.writeAll(", ");968 if (need_comma) try stream.writeAll(", ");
962 try stream.print("{}=", .{arg_field.name});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 need_comma = true;971 need_comma = true;
965 }972 }
966 }973 }
...@@ -968,7 +975,8 @@ const Writer = struct {...@@ -968,7 +975,8 @@ const Writer = struct {
968 try stream.writeByte(')');975 try stream.writeByte(')');
969 }976 }
970977
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 if (@typeInfo(@TypeOf(param)) == .Enum) {980 if (@typeInfo(@TypeOf(param)) == .Enum) {
973 return stream.writeAll(@tagName(param));981 return stream.writeAll(@tagName(param));
974 }982 }
...@@ -986,18 +994,36 @@ const Writer = struct {...@@ -986,18 +994,36 @@ const Writer = struct {
986 },994 },
987 Module.Body => {995 Module.Body => {
988 try stream.writeAll("{\n");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 try stream.writeByteNTimes(' ', self.indent);1013 try stream.writeByteNTimes(' ', self.indent);
991 try stream.print("%{} ", .{i});1014 try stream.print("%{} ", .{my_i});
992 if (inst.cast(Inst.Block)) |block| {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 try self.block_table.put(block, name);1017 try self.block_table.put(block, name);
995 } else if (inst.cast(Inst.Loop)) |loop| {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 try self.loop_table.put(loop, name);1020 try self.loop_table.put(loop, name);
998 }1021 }
999 self.indent += 2;1022 self.indent += 2;
1000 try self.writeInstToStream(stream, inst);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 self.indent -= 2;1027 self.indent -= 2;
1002 try stream.writeByte('\n');1028 try stream.writeByte('\n');
1003 }1029 }
...@@ -1070,6 +1096,8 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module...@@ -1070,6 +1096,8 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module
1070 .decls = parser.decls.toOwnedSlice(allocator),1096 .decls = parser.decls.toOwnedSlice(allocator),
1071 .arena = parser.arena,1097 .arena = parser.arena,
1072 .error_msg = parser.error_msg,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}
10751103
...@@ -1478,7 +1506,11 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {...@@ -1478,7 +1506,11 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
1478 .indent = 0,1506 .indent = 0,
1479 .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),1507 .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),
1480 .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator),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 defer ctx.block_table.deinit();1514 defer ctx.block_table.deinit();
1483 defer ctx.loop_table.deinit();1515 defer ctx.loop_table.deinit();
1484 defer ctx.decls.deinit(allocator);1516 defer ctx.decls.deinit(allocator);
...@@ -1491,7 +1523,50 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {...@@ -1491,7 +1523,50 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
1491 return Module{1523 return Module{
1492 .decls = ctx.decls.toOwnedSlice(allocator),1524 .decls = ctx.decls.toOwnedSlice(allocator),
1493 .arena = ctx.arena,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.
1532pub 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}
14961571
1497const EmitZIR = struct {1572const EmitZIR = struct {
...@@ -1505,6 +1580,8 @@ const EmitZIR = struct {...@@ -1505,6 +1580,8 @@ const EmitZIR = struct {
1505 indent: usize,1580 indent: usize,
1506 block_table: std.AutoHashMap(*ir.Inst.Block, *Inst.Block),1581 block_table: std.AutoHashMap(*ir.Inst.Block, *Inst.Block),
1507 loop_table: std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop),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),
15081585
1509 fn emit(self: *EmitZIR) !void {1586 fn emit(self: *EmitZIR) !void {
1510 // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced1587 // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced
...@@ -1604,7 +1681,7 @@ const EmitZIR = struct {...@@ -1604,7 +1681,7 @@ const EmitZIR = struct {
1604 } else blk: {1681 } else blk: {
1605 break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst;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 return new_inst;1685 return new_inst;
1609 } else {1686 } else {
1610 return new_body.inst_table.get(inst).?;1687 return new_body.inst_table.get(inst).?;
...@@ -1655,6 +1732,70 @@ const EmitZIR = struct {...@@ -1655,6 +1732,70 @@ const EmitZIR = struct {
1655 return &declref_inst.base;1732 return &declref_inst.base;
1656 }1733 }
16571734
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 fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Decl {1799 fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Decl {
1659 const allocator = &self.arena.allocator;1800 const allocator = &self.arena.allocator;
1660 if (typed_value.val.cast(Value.Payload.DeclRef)) |decl_ref| {1801 if (typed_value.val.cast(Value.Payload.DeclRef)) |decl_ref| {
...@@ -1718,68 +1859,7 @@ const EmitZIR = struct {...@@ -1718,68 +1859,7 @@ const EmitZIR = struct {
1718 },1859 },
1719 .Fn => {1860 .Fn => {
1720 const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;1861 const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
17211862 return self.emitFn(module_fn, src, typed_value.ty);
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);
1783 },1863 },
1784 .Array => {1864 .Array => {
1785 // TODO more checks to make sure this can be emitted as a string literal1865 // TODO more checks to make sure this can be emitted as a string literal
...@@ -1810,7 +1890,7 @@ const EmitZIR = struct {...@@ -1810,7 +1890,7 @@ const EmitZIR = struct {
1810 }1890 }
1811 }1891 }
18121892
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 const new_inst = try self.arena.allocator.create(Inst.NoOp);1894 const new_inst = try self.arena.allocator.create(Inst.NoOp);
1815 new_inst.* = .{1895 new_inst.* = .{
1816 .base = .{1896 .base = .{
...@@ -1902,10 +1982,10 @@ const EmitZIR = struct {...@@ -1902,10 +1982,10 @@ const EmitZIR = struct {
1902 const new_inst = switch (inst.tag) {1982 const new_inst = switch (inst.tag) {
1903 .constant => unreachable, // excluded from function bodies1983 .constant => unreachable, // excluded from function bodies
19041984
1905 .breakpoint => try self.emitNoOp(inst.src, .breakpoint),1985 .breakpoint => try self.emitNoOp(inst.src, inst.castTag(.breakpoint).?, .breakpoint),
1906 .unreach => try self.emitNoOp(inst.src, .@"unreachable"),1986 .unreach => try self.emitNoOp(inst.src, inst.castTag(.unreach).?, .unreach_nocheck),
1907 .retvoid => try self.emitNoOp(inst.src, .returnvoid),1987 .retvoid => try self.emitNoOp(inst.src, inst.castTag(.retvoid).?, .returnvoid),
1908 .dbg_stmt => try self.emitNoOp(inst.src, .dbg_stmt),1988 .dbg_stmt => try self.emitNoOp(inst.src, inst.castTag(.dbg_stmt).?, .dbg_stmt),
19091989
1910 .not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot),1990 .not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot),
1911 .ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"),1991 .ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"),
...@@ -2119,10 +2199,24 @@ const EmitZIR = struct {...@@ -2119,10 +2199,24 @@ const EmitZIR = struct {
2119 defer then_body.deinit();2199 defer then_body.deinit();
2120 defer else_body.deinit();2200 defer else_body.deinit();
21212201
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 try self.emitBody(old_inst.then_body, inst_table, &then_body);2212 try self.emitBody(old_inst.then_body, inst_table, &then_body);
2123 try self.emitBody(old_inst.else_body, inst_table, &else_body);2213 try self.emitBody(old_inst.else_body, inst_table, &else_body);
21242214
2125 const new_inst = try self.arena.allocator.create(Inst.CondBr);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 new_inst.* = .{2220 new_inst.* = .{
2127 .base = .{2221 .base = .{
2128 .src = inst.src,2222 .src = inst.src,
...@@ -2138,6 +2232,7 @@ const EmitZIR = struct {...@@ -2138,6 +2232,7 @@ const EmitZIR = struct {
2138 break :blk &new_inst.base;2232 break :blk &new_inst.base;
2139 },2233 },
2140 };2234 };
2235 try self.metadata.put(new_inst, .{ .deaths = inst.deaths });
2141 try instructions.append(new_inst);2236 try instructions.append(new_inst);
2142 try inst_table.put(inst, new_inst);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,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}