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" {
747747
748748/// Cast an integer to a different integer type. If the value doesn't fit,
749749/// return an error.
750/// TODO make this an optional not an error.
750751pub fn cast(comptime T: type, x: anytype) (error{Overflow}!T) {
751752 comptime assert(@typeInfo(T) == .Int); // must pass an integer
752753 comptime assert(@typeInfo(@TypeOf(x)) == .Int); // must pass an integer
src-self-hosted/Module.zig+17
......@@ -301,6 +301,23 @@ pub const Fn = struct {
301301 body: zir.Module.Body,
302302 arena: std.heap.ArenaAllocator.State,
303303 };
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 }
304321};
305322
306323pub const Scope = struct {
src-self-hosted/codegen.zig+28-5
......@@ -23,8 +23,6 @@ pub const BlockData = struct {
2323 relocs: std.ArrayListUnmanaged(Reloc) = .{},
2424};
2525
26pub const LoopData = struct { };
27
2826pub const Reloc = union(enum) {
2927 /// The value is an offset into the `Function` `code` from the beginning.
3028 /// To perform the reloc, write 32-bit signed little-endian integer
......@@ -556,7 +554,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
556554 }
557555
558556 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;
560559 for (body.instructions) |inst| {
561560 const new_inst = try self.genFuncInst(inst);
562561 try inst_table.putNoClobber(self.gpa, inst, new_inst);
......@@ -1284,6 +1283,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
12841283 }
12851284
12861285 fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue {
1286 // TODO Rework this so that the arch-independent logic isn't buried and duplicated.
12871287 switch (arch) {
12881288 .x86_64 => {
12891289 try self.code.ensureCapacity(self.code.items.len + 6);
......@@ -1336,6 +1336,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
13361336 }
13371337
13381338 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
13391340 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode });
13401341 const reloc = Reloc{ .rel32 = self.code.items.len };
13411342 self.code.items.len += 4;
......@@ -1360,14 +1361,36 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
13601361 }
13611362
13621363 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 }
13641387 }
13651388
13661389 fn genBlock(self: *Self, inst: *ir.Inst.Block) !MCValue {
13671390 if (inst.base.ty.hasCodeGenBits()) {
13681391 return self.fail(inst.base.src, "TODO codegen Block with non-void type", .{});
13691392 }
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.
13711394 defer inst.codegen.relocs.deinit(self.gpa);
13721395 try self.genBody(inst.body);
13731396
src-self-hosted/ir.zig+10-6
......@@ -372,11 +372,11 @@ pub const Inst = struct {
372372 then_body: Body,
373373 else_body: Body,
374374 /// 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]`.
377377 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,
380380
381381 pub fn operandCount(self: *const CondBr) usize {
382382 return 1;
......@@ -390,6 +390,12 @@ pub const Inst = struct {
390390
391391 return null;
392392 }
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 }
393399 };
394400
395401 pub const Constant = struct {
......@@ -411,8 +417,6 @@ pub const Inst = struct {
411417
412418 base: Inst,
413419 body: Body,
414 /// This memory is reserved for codegen code to do whatever it needs to here.
415 codegen: codegen.LoopData = .{},
416420
417421 pub fn operandCount(self: *const Loop) usize {
418422 return 0;
src-self-hosted/link.zig+2
......@@ -1887,6 +1887,8 @@ pub const File = struct {
18871887 else => false,
18881888 };
18891889 if (is_fn) {
1890 //typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*);
1891
18901892 // For functions we need to add a prologue to the debug line program.
18911893 try dbg_line_buffer.ensureCapacity(26);
18921894
src-self-hosted/liveness.zig+81-44
......@@ -16,20 +16,42 @@ pub fn analyze(
1616 var table = std.AutoHashMap(*ir.Inst, void).init(gpa);
1717 defer table.deinit();
1818 try table.ensureCapacity(body.instructions.len);
19 try analyzeWithTable(arena, &table, body);
19 try analyzeWithTable(arena, &table, null, body);
2020}
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 {
2328 var i: usize = body.instructions.len;
2429
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 }
2946 }
3047}
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 {
3355 if (table.contains(base)) {
3456 base.deaths = 0;
3557 } else {
......@@ -42,56 +64,70 @@ fn analyzeInst(arena: *std.mem.Allocator, table: *std.AutoHashMap(*ir.Inst, void
4264 .constant => return,
4365 .block => {
4466 const inst = base.castTag(.block).?;
45 try analyzeWithTable(arena, table, inst.body);
67 try analyzeWithTable(arena, table, new_set, inst.body);
4668 // We let this continue so that it can possibly mark the block as
4769 // unreferenced below.
4870 },
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 },
4976 .condbr => {
5077 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
6179 // Each death that occurs inside one branch, but not the other, needs
6280 // 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);
79112 }
113 _ = try table.put(then_death, {});
80114 }
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, {});
89123 }
90124 }
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);
94128 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
96132 // Continue on with the instruction analysis. The following code will find the condition
97133 // 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
108144 if (prev == null) {
109145 // Death.
110146 base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i;
147 if (new_set) |ns| try ns.putNoClobber(operand, {});
111148 }
112149 }
113150 } else {
src-self-hosted/zir.zig+177-82
......@@ -822,6 +822,16 @@ pub const Module = struct {
822822 decls: []*Decl,
823823 arena: std.heap.ArenaAllocator,
824824 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
826836 pub const Body = struct {
827837 instructions: []*Inst,
......@@ -878,6 +888,7 @@ pub const Module = struct {
878888 .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator),
879889 .arena = std.heap.ArenaAllocator.init(allocator),
880890 .indent = 2,
891 .next_instr_index = undefined,
881892 };
882893 defer write.arena.deinit();
883894 defer write.inst_table.deinit();
......@@ -889,15 +900,10 @@ pub const Module = struct {
889900
890901 for (self.decls) |decl, decl_i| {
891902 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 }
898903 }
899904
900905 for (self.decls) |decl, i| {
906 write.next_instr_index = 0;
901907 try stream.print("@{} ", .{decl.name});
902908 try write.writeInstToStream(stream, decl.inst);
903909 try stream.writeByte('\n');
......@@ -914,6 +920,7 @@ const Writer = struct {
914920 loop_table: std.AutoHashMap(*Inst.Loop, []const u8),
915921 arena: std.heap.ArenaAllocator,
916922 indent: usize,
923 next_instr_index: usize,
917924
918925 fn writeInstToStream(
919926 self: *Writer,
......@@ -944,7 +951,7 @@ const Writer = struct {
944951 if (i != 0) {
945952 try stream.writeAll(", ");
946953 }
947 try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name));
954 try self.writeParamToStream(stream, &@field(inst.positionals, arg_field.name));
948955 }
949956
950957 comptime var need_comma = pos_fields.len != 0;
......@@ -954,13 +961,13 @@ const Writer = struct {
954961 if (@field(inst.kw_args, arg_field.name)) |non_optional| {
955962 if (need_comma) try stream.writeAll(", ");
956963 try stream.print("{}=", .{arg_field.name});
957 try self.writeParamToStream(stream, non_optional);
964 try self.writeParamToStream(stream, &non_optional);
958965 need_comma = true;
959966 }
960967 } else {
961968 if (need_comma) try stream.writeAll(", ");
962969 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));
964971 need_comma = true;
965972 }
966973 }
......@@ -968,7 +975,8 @@ const Writer = struct {
968975 try stream.writeByte(')');
969976 }
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.*;
972980 if (@typeInfo(@TypeOf(param)) == .Enum) {
973981 return stream.writeAll(@tagName(param));
974982 }
......@@ -986,18 +994,36 @@ const Writer = struct {
986994 },
987995 Module.Body => {
988996 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 });
9901013 try stream.writeByteNTimes(' ', self.indent);
991 try stream.print("%{} ", .{i});
1014 try stream.print("%{} ", .{my_i});
9921015 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});
9941017 try self.block_table.put(block, name);
9951018 } 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});
9971020 try self.loop_table.put(loop, name);
9981021 }
9991022 self.indent += 2;
10001023 try self.writeInstToStream(stream, inst);
1024 if (self.module.metadata.get(inst)) |metadata| {
1025 try stream.print(" ; deaths=0b{b}", .{metadata.deaths});
1026 }
10011027 self.indent -= 2;
10021028 try stream.writeByte('\n');
10031029 }
......@@ -1070,6 +1096,8 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module
10701096 .decls = parser.decls.toOwnedSlice(allocator),
10711097 .arena = parser.arena,
10721098 .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),
10731101 };
10741102}
10751103
......@@ -1478,7 +1506,11 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
14781506 .indent = 0,
14791507 .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),
14801508 .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),
14811511 };
1512 defer ctx.metadata.deinit();
1513 defer ctx.body_metadata.deinit();
14821514 defer ctx.block_table.deinit();
14831515 defer ctx.loop_table.deinit();
14841516 defer ctx.decls.deinit(allocator);
......@@ -1491,7 +1523,50 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
14911523 return Module{
14921524 .decls = ctx.decls.toOwnedSlice(allocator),
14931525 .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,
14941567 };
1568
1569 module.dump();
14951570}
14961571
14971572const EmitZIR = struct {
......@@ -1505,6 +1580,8 @@ const EmitZIR = struct {
15051580 indent: usize,
15061581 block_table: std.AutoHashMap(*ir.Inst.Block, *Inst.Block),
15071582 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
15091586 fn emit(self: *EmitZIR) !void {
15101587 // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced
......@@ -1604,7 +1681,7 @@ const EmitZIR = struct {
16041681 } else blk: {
16051682 break :blk (try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val })).inst;
16061683 };
1607 try new_body.inst_table.putNoClobber(inst, new_inst);
1684 _ = try new_body.inst_table.put(inst, new_inst);
16081685 return new_inst;
16091686 } else {
16101687 return new_body.inst_table.get(inst).?;
......@@ -1655,6 +1732,70 @@ const EmitZIR = struct {
16551732 return &declref_inst.base;
16561733 }
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
16581799 fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Decl {
16591800 const allocator = &self.arena.allocator;
16601801 if (typed_value.val.cast(Value.Payload.DeclRef)) |decl_ref| {
......@@ -1718,68 +1859,7 @@ const EmitZIR = struct {
17181859 },
17191860 .Fn => {
17201861 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);
17831863 },
17841864 .Array => {
17851865 // TODO more checks to make sure this can be emitted as a string literal
......@@ -1810,7 +1890,7 @@ const EmitZIR = struct {
18101890 }
18111891 }
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 {
18141894 const new_inst = try self.arena.allocator.create(Inst.NoOp);
18151895 new_inst.* = .{
18161896 .base = .{
......@@ -1902,10 +1982,10 @@ const EmitZIR = struct {
19021982 const new_inst = switch (inst.tag) {
19031983 .constant => unreachable, // excluded from function bodies
19041984
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),
19091989
19101990 .not => try self.emitUnOp(inst.src, new_body, inst.castTag(.not).?, .boolnot),
19111991 .ret => try self.emitUnOp(inst.src, new_body, inst.castTag(.ret).?, .@"return"),
......@@ -2119,10 +2199,24 @@ const EmitZIR = struct {
21192199 defer then_body.deinit();
21202200 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
21222212 try self.emitBody(old_inst.then_body, inst_table, &then_body);
21232213 try self.emitBody(old_inst.else_body, inst_table, &else_body);
21242214
21252215 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
21262220 new_inst.* = .{
21272221 .base = .{
21282222 .src = inst.src,
......@@ -2138,6 +2232,7 @@ const EmitZIR = struct {
21382232 break :blk &new_inst.base;
21392233 },
21402234 };
2235 try self.metadata.put(new_inst, .{ .deaths = inst.deaths });
21412236 try instructions.append(new_inst);
21422237 try inst_table.put(inst, new_inst);
21432238 }
test/stage2/compare_output.zig+39
......@@ -465,5 +465,44 @@ pub fn addCases(ctx: *TestContext) !void {
465465 ,
466466 "",
467467 );
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 );
468507 }
469508}