authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-26 04:02:43-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-08-26 04:02:43-04:00
log3abf9e1457ed9332474ab211eb8d996d594a33c3
tree2ed6e829c2a74c5bb88384b420e11299eed6927f
parent982ab7df6cd61a874e98ef99e923a98e02cf7487
parent0c5faa61aebca4215683d233dd52bf3a7a5d1db6
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #6163 from ziglang/stage2-condbr

stage2: codegen for conditional branching

7 files changed, 338 insertions(+), 136 deletions(-)

build.zig+2
......@@ -83,6 +83,7 @@ pub fn build(b: *Builder) !void {
8383 }
8484
8585 const log_scopes = b.option([]const []const u8, "log", "Which log scopes to enable") orelse &[0][]const u8{};
86 const zir_dumps = b.option([]const []const u8, "dump-zir", "Which functions to dump ZIR for before codegen") orelse &[0][]const u8{};
8687
8788 const opt_version_string = b.option([]const u8, "version-string", "Override Zig version string. Default is to find out with git.");
8889 const version = if (opt_version_string) |version| version else v: {
......@@ -103,6 +104,7 @@ pub fn build(b: *Builder) !void {
103104 exe.addBuildOption([]const u8, "version", version);
104105
105106 exe.addBuildOption([]const []const u8, "log_scopes", log_scopes);
107 exe.addBuildOption([]const []const u8, "zir_dumps", zir_dumps);
106108 exe.addBuildOption(bool, "enable_tracy", tracy != null);
107109 if (tracy) |tracy_path| {
108110 const client_cpp = fs.path.join(
src-self-hosted/astgen.zig+2-1
......@@ -13,7 +13,8 @@ const Scope = Module.Scope;
1313const InnerError = Module.InnerError;
1414
1515pub const ResultLoc = union(enum) {
16 /// The expression is the right-hand side of assignment to `_`.
16 /// The expression is the right-hand side of assignment to `_`. Only the side-effects of the
17 /// expression should be generated.
1718 discard,
1819 /// The expression has an inferred type, and it will be evaluated as an rvalue.
1920 none,
src-self-hosted/codegen.zig+249-129
......@@ -273,8 +273,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
273273 /// across each runtime branch upon joining.
274274 branch_stack: *std.ArrayList(Branch),
275275
276 /// The key must be canonical register.
277 registers: std.AutoHashMapUnmanaged(Register, *ir.Inst) = .{},
278 free_registers: FreeRegInt = math.maxInt(FreeRegInt),
279 /// Maps offset to what is stored there.
280 stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{},
281
282 /// Offset from the stack base, representing the end of the stack frame.
283 max_end_stack: u32 = 0,
284 /// Represents the current end stack offset. If there is no existing slot
285 /// to place a new stack allocation, it goes here, and then bumps `max_end_stack`.
286 next_stack_offset: u32 = 0,
287
276288 const MCValue = union(enum) {
277289 /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
290 /// TODO Look into deleting this tag and using `dead` instead, since every use
291 /// of MCValue.none should be instead looking at the type and noticing it is 0 bits.
278292 none,
279293 /// Control flow will not allow this value to be observed.
280294 unreach,
......@@ -346,71 +360,55 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
346360
347361 const Branch = struct {
348362 inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{},
349 /// The key must be canonical register.
350 registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{},
351 free_registers: FreeRegInt = math.maxInt(FreeRegInt),
352
353 /// Maps offset to what is stored there.
354 stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{},
355 /// Offset from the stack base, representing the end of the stack frame.
356 max_end_stack: u32 = 0,
357 /// Represents the current end stack offset. If there is no existing slot
358 /// to place a new stack allocation, it goes here, and then bumps `max_end_stack`.
359 next_stack_offset: u32 = 0,
360
361 fn markRegUsed(self: *Branch, reg: Register) void {
362 if (FreeRegInt == u0) return;
363 const index = reg.allocIndex() orelse return;
364 const ShiftInt = math.Log2Int(FreeRegInt);
365 const shift = @intCast(ShiftInt, index);
366 self.free_registers &= ~(@as(FreeRegInt, 1) << shift);
367 }
368
369 fn markRegFree(self: *Branch, reg: Register) void {
370 if (FreeRegInt == u0) return;
371 const index = reg.allocIndex() orelse return;
372 const ShiftInt = math.Log2Int(FreeRegInt);
373 const shift = @intCast(ShiftInt, index);
374 self.free_registers |= @as(FreeRegInt, 1) << shift;
375 }
376
377 /// Before calling, must ensureCapacity + 1 on branch.registers.
378 /// Returns `null` if all registers are allocated.
379 fn allocReg(self: *Branch, inst: *ir.Inst) ?Register {
380 const free_index = @ctz(FreeRegInt, self.free_registers);
381 if (free_index >= callee_preserved_regs.len) {
382 return null;
383 }
384 self.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
385 const reg = callee_preserved_regs[free_index];
386 self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst });
387 log.debug("alloc {} => {*}", .{reg, inst});
388 return reg;
389 }
390
391 /// Does not track the register.
392 fn findUnusedReg(self: *Branch) ?Register {
393 const free_index = @ctz(FreeRegInt, self.free_registers);
394 if (free_index >= callee_preserved_regs.len) {
395 return null;
396 }
397 return callee_preserved_regs[free_index];
398 }
399363
400364 fn deinit(self: *Branch, gpa: *Allocator) void {
401365 self.inst_table.deinit(gpa);
402 self.registers.deinit(gpa);
403 self.stack.deinit(gpa);
404366 self.* = undefined;
405367 }
406368 };
407369
408 const RegisterAllocation = struct {
409 inst: *ir.Inst,
410 };
370 fn markRegUsed(self: *Self, reg: Register) void {
371 if (FreeRegInt == u0) return;
372 const index = reg.allocIndex() orelse return;
373 const ShiftInt = math.Log2Int(FreeRegInt);
374 const shift = @intCast(ShiftInt, index);
375 self.free_registers &= ~(@as(FreeRegInt, 1) << shift);
376 }
377
378 fn markRegFree(self: *Self, reg: Register) void {
379 if (FreeRegInt == u0) return;
380 const index = reg.allocIndex() orelse return;
381 const ShiftInt = math.Log2Int(FreeRegInt);
382 const shift = @intCast(ShiftInt, index);
383 self.free_registers |= @as(FreeRegInt, 1) << shift;
384 }
385
386 /// Before calling, must ensureCapacity + 1 on self.registers.
387 /// Returns `null` if all registers are allocated.
388 fn allocReg(self: *Self, inst: *ir.Inst) ?Register {
389 const free_index = @ctz(FreeRegInt, self.free_registers);
390 if (free_index >= callee_preserved_regs.len) {
391 return null;
392 }
393 self.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
394 const reg = callee_preserved_regs[free_index];
395 self.registers.putAssumeCapacityNoClobber(reg, inst);
396 log.debug("alloc {} => {*}", .{reg, inst});
397 return reg;
398 }
399
400 /// Does not track the register.
401 fn findUnusedReg(self: *Self) ?Register {
402 const free_index = @ctz(FreeRegInt, self.free_registers);
403 if (free_index >= callee_preserved_regs.len) {
404 return null;
405 }
406 return callee_preserved_regs[free_index];
407 }
411408
412409 const StackAllocation = struct {
413410 inst: *ir.Inst,
411 /// TODO do we need size? should be determined by inst.ty.abiSize()
414412 size: u32,
415413 };
416414
......@@ -435,8 +433,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
435433 branch_stack.items[0].deinit(bin_file.allocator);
436434 branch_stack.deinit();
437435 }
438 const branch = try branch_stack.addOne();
439 branch.* = .{};
436 try branch_stack.append(.{});
440437
441438 const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: {
442439 if (module_fn.owner_decl.scope.cast(Module.Scope.File)) |scope_file| {
......@@ -476,6 +473,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
476473 .rbrace_src = src_data.rbrace_src,
477474 .source = src_data.source,
478475 };
476 defer function.registers.deinit(bin_file.allocator);
477 defer function.stack.deinit(bin_file.allocator);
479478 defer function.exitlude_jump_relocs.deinit(bin_file.allocator);
480479
481480 var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) {
......@@ -487,7 +486,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
487486 function.args = call_info.args;
488487 function.ret_mcv = call_info.return_value;
489488 function.stack_align = call_info.stack_align;
490 branch.max_end_stack = call_info.stack_byte_count;
489 function.max_end_stack = call_info.stack_byte_count;
491490
492491 function.gen() catch |err| switch (err) {
493492 error.CodegenFail => return Result{ .fail = function.err_msg.? },
......@@ -523,7 +522,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
523522 try self.dbgSetPrologueEnd();
524523 try self.genBody(self.mod_fn.analysis.success);
525524
526 const stack_end = self.branch_stack.items[0].max_end_stack;
525 const stack_end = self.max_end_stack;
527526 if (stack_end > math.maxInt(i32))
528527 return self.fail(self.src, "too much stack used in call parameters", .{});
529528 const aligned_stack_end = mem.alignForward(stack_end, self.stack_align);
......@@ -580,13 +579,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
580579 }
581580
582581 fn genBody(self: *Self, body: ir.Body) InnerError!void {
583 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
584 const inst_table = &branch.inst_table;
585582 for (body.instructions) |inst| {
583 try self.ensureProcessDeathCapacity(@popCount(@TypeOf(inst.deaths), inst.deaths));
584
586585 const mcv = try self.genFuncInst(inst);
587 log.debug("{*} => {}", .{inst, mcv});
588 // TODO don't put void or dead things in here
589 try inst_table.putNoClobber(self.gpa, inst, mcv);
586 if (!inst.isUnused()) {
587 log.debug("{*} => {}", .{inst, mcv});
588 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
589 try branch.inst_table.putNoClobber(self.gpa, inst, mcv);
590 }
590591
591592 var i: ir.Inst.DeathsBitIndex = 0;
592593 while (inst.getOperand(i)) |operand| : (i += 1) {
......@@ -628,21 +629,28 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
628629 self.dbg_line.appendAssumeCapacity(DW.LNS_copy);
629630 }
630631
632 /// Asserts there is already capacity to insert into top branch inst_table.
631633 fn processDeath(self: *Self, inst: *ir.Inst) void {
634 if (inst.tag == .constant) return; // Constants are immortal.
635 // When editing this function, note that the logic must synchronize with `reuseOperand`.
636 const prev_value = self.getResolvedInstValue(inst);
632637 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
633 const entry = branch.inst_table.getEntry(inst) orelse return;
634 const prev_value = entry.value;
635 entry.value = .dead;
638 branch.inst_table.putAssumeCapacity(inst, .dead);
636639 switch (prev_value) {
637640 .register => |reg| {
638641 const canon_reg = toCanonicalReg(reg);
639 _ = branch.registers.remove(canon_reg);
640 branch.markRegFree(canon_reg);
642 _ = self.registers.remove(canon_reg);
643 self.markRegFree(canon_reg);
641644 },
642645 else => {}, // TODO process stack allocation death
643646 }
644647 }
645648
649 fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void {
650 const table = &self.branch_stack.items[self.branch_stack.items.len - 1].inst_table;
651 try table.ensureCapacity(self.gpa, table.items().len + additional_count);
652 }
653
646654 /// Adds a Type to the .debug_info at the current position. The bytes will be populated later,
647655 /// after codegen for this symbol is done.
648656 fn addDbgInfoTypeReloc(self: *Self, ty: Type) !void {
......@@ -705,13 +713,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
705713 fn allocMem(self: *Self, inst: *ir.Inst, abi_size: u32, abi_align: u32) !u32 {
706714 if (abi_align > self.stack_align)
707715 self.stack_align = abi_align;
708 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
709716 // TODO find a free slot instead of always appending
710 const offset = mem.alignForwardGeneric(u32, branch.next_stack_offset, abi_align);
711 branch.next_stack_offset = offset + abi_size;
712 if (branch.next_stack_offset > branch.max_end_stack)
713 branch.max_end_stack = branch.next_stack_offset;
714 try branch.stack.putNoClobber(self.gpa, offset, .{
717 const offset = mem.alignForwardGeneric(u32, self.next_stack_offset, abi_align);
718 self.next_stack_offset = offset + abi_size;
719 if (self.next_stack_offset > self.max_end_stack)
720 self.max_end_stack = self.next_stack_offset;
721 try self.stack.putNoClobber(self.gpa, offset, .{
715722 .inst = inst,
716723 .size = abi_size,
717724 });
......@@ -737,15 +744,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
737744 const abi_align = elem_ty.abiAlignment(self.target.*);
738745 if (abi_align > self.stack_align)
739746 self.stack_align = abi_align;
740 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
741747
742748 if (reg_ok) {
743749 // Make sure the type can fit in a register before we try to allocate one.
744750 const ptr_bits = arch.ptrBitWidth();
745751 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
746752 if (abi_size <= ptr_bytes) {
747 try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
748 if (branch.allocReg(inst)) |reg| {
753 try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1);
754 if (self.allocReg(inst)) |reg| {
749755 return MCValue{ .register = registerAlias(reg, abi_size) };
750756 }
751757 }
......@@ -758,20 +764,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
758764 /// allocated. A second call to `copyToTmpRegister` may return the same register.
759765 /// This can have a side effect of spilling instructions to the stack to free up a register.
760766 fn copyToTmpRegister(self: *Self, src: usize, mcv: MCValue) !Register {
761 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
762
763 const reg = branch.findUnusedReg() orelse b: {
767 const reg = self.findUnusedReg() orelse b: {
764768 // We'll take over the first register. Move the instruction that was previously
765769 // there to a stack allocation.
766770 const reg = callee_preserved_regs[0];
767 const regs_entry = branch.registers.remove(reg).?;
768 const spilled_inst = regs_entry.value.inst;
771 const regs_entry = self.registers.remove(reg).?;
772 const spilled_inst = regs_entry.value;
769773
770774 const stack_mcv = try self.allocRegOrMem(spilled_inst, false);
771 const inst_entry = branch.inst_table.getEntry(spilled_inst).?;
772 const reg_mcv = inst_entry.value;
775 const reg_mcv = self.getResolvedInstValue(spilled_inst);
773776 assert(reg == toCanonicalReg(reg_mcv.register));
774 inst_entry.value = stack_mcv;
777 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
778 try branch.inst_table.put(self.gpa, spilled_inst, stack_mcv);
775779 try self.genSetStack(src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv);
776780
777781 break :b reg;
......@@ -784,22 +788,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
784788 /// `reg_owner` is the instruction that gets associated with the register in the register table.
785789 /// This can have a side effect of spilling instructions to the stack to free up a register.
786790 fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue {
787 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
788 try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
791 try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1);
789792
790 const reg = branch.allocReg(reg_owner) orelse b: {
793 const reg = self.allocReg(reg_owner) orelse b: {
791794 // We'll take over the first register. Move the instruction that was previously
792795 // there to a stack allocation.
793796 const reg = callee_preserved_regs[0];
794 const regs_entry = branch.registers.getEntry(reg).?;
795 const spilled_inst = regs_entry.value.inst;
796 regs_entry.value = .{ .inst = reg_owner };
797 const regs_entry = self.registers.getEntry(reg).?;
798 const spilled_inst = regs_entry.value;
799 regs_entry.value = reg_owner;
797800
798801 const stack_mcv = try self.allocRegOrMem(spilled_inst, false);
799 const inst_entry = branch.inst_table.getEntry(spilled_inst).?;
800 const reg_mcv = inst_entry.value;
802 const reg_mcv = self.getResolvedInstValue(spilled_inst);
801803 assert(reg == toCanonicalReg(reg_mcv.register));
802 inst_entry.value = stack_mcv;
804 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
805 try branch.inst_table.put(self.gpa, spilled_inst, stack_mcv);
803806 try self.genSetStack(reg_owner.src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv);
804807
805808 break :b reg;
......@@ -934,9 +937,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
934937 .register => |reg| {
935938 // If it's in the registers table, need to associate the register with the
936939 // new instruction.
937 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
938 if (branch.registers.getEntry(toCanonicalReg(reg))) |entry| {
939 entry.value = .{ .inst = inst };
940 if (self.registers.getEntry(toCanonicalReg(reg))) |entry| {
941 entry.value = inst;
940942 }
941943 log.debug("reusing {} => {*}", .{reg, inst});
942944 },
......@@ -950,6 +952,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
950952 // Prevent the operand deaths processing code from deallocating it.
951953 inst.clearOperandDeath(op_index);
952954
955 // That makes us responsible for doing the rest of the stuff that processDeath would have done.
956 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
957 branch.inst_table.putAssumeCapacity(inst.getOperand(op_index).?, .dead);
958
953959 return true;
954960 }
955961
......@@ -1231,8 +1237,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
12311237 if (inst.base.isUnused())
12321238 return MCValue.dead;
12331239
1234 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
1235 try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
1240 try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1);
12361241
12371242 const result = self.args[self.arg_index];
12381243 self.arg_index += 1;
......@@ -1240,8 +1245,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
12401245 const name_with_null = inst.name[0..mem.lenZ(inst.name) + 1];
12411246 switch (result) {
12421247 .register => |reg| {
1243 branch.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), .{ .inst = &inst.base });
1244 branch.markRegUsed(reg);
1248 self.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), &inst.base);
1249 self.markRegUsed(reg);
12451250
12461251 try self.dbg_info.ensureCapacity(self.dbg_info.items.len + 8 + name_with_null.len);
12471252 self.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter);
......@@ -1536,18 +1541,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
15361541
15371542 fn genDbgStmt(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
15381543 try self.dbgAdvancePCAndLine(inst.base.src);
1539 return MCValue.none;
1544 assert(inst.base.isUnused());
1545 return MCValue.dead;
15401546 }
15411547
15421548 fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue {
1543 // TODO Rework this so that the arch-independent logic isn't buried and duplicated.
1544 switch (arch) {
1545 .x86_64 => {
1549 const cond = try self.resolveInst(inst.condition);
1550
1551 const reloc: Reloc = switch (arch) {
1552 .i386, .x86_64 => reloc: {
15461553 try self.code.ensureCapacity(self.code.items.len + 6);
15471554
1548 const cond = try self.resolveInst(inst.condition);
1549 switch (cond) {
1550 .compare_flags_signed => |cmp_op| {
1555 const opcode: u8 = switch (cond) {
1556 .compare_flags_signed => |cmp_op| blk: {
15511557 // Here we map to the opposite opcode because the jump is to the false branch.
15521558 const opcode: u8 = switch (cmp_op) {
15531559 .gte => 0x8c,
......@@ -1557,9 +1563,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
15571563 .lte => 0x8f,
15581564 .eq => 0x85,
15591565 };
1560 return self.genX86CondBr(inst, opcode);
1566 break :blk opcode;
15611567 },
1562 .compare_flags_unsigned => |cmp_op| {
1568 .compare_flags_unsigned => |cmp_op| blk: {
15631569 // Here we map to the opposite opcode because the jump is to the false branch.
15641570 const opcode: u8 = switch (cmp_op) {
15651571 .gte => 0x82,
......@@ -1569,9 +1575,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
15691575 .lte => 0x87,
15701576 .eq => 0x85,
15711577 };
1572 return self.genX86CondBr(inst, opcode);
1578 break :blk opcode;
15731579 },
1574 .register => |reg| {
1580 .register => |reg| blk: {
15751581 // test reg, 1
15761582 // TODO detect al, ax, eax
15771583 try self.code.ensureCapacity(self.code.items.len + 4);
......@@ -1583,23 +1589,128 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
15831589 @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()),
15841590 0x01,
15851591 });
1586 return self.genX86CondBr(inst, 0x84);
1592 break :blk 0x84;
15871593 },
15881594 else => return self.fail(inst.base.src, "TODO implement condbr {} when condition is {}", .{ self.target.cpu.arch, @tagName(cond) }),
1589 }
1595 };
1596 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode });
1597 const reloc = Reloc{ .rel32 = self.code.items.len };
1598 self.code.items.len += 4;
1599 break :reloc reloc;
15901600 },
1591 else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}),
1592 }
1593 }
1601 else => return self.fail(inst.base.src, "TODO implement condbr {}", .{ self.target.cpu.arch }),
1602 };
1603
1604 // Capture the state of register and stack allocation state so that we can revert to it.
1605 const parent_next_stack_offset = self.next_stack_offset;
1606 const parent_free_registers = self.free_registers;
1607 var parent_stack = try self.stack.clone(self.gpa);
1608 defer parent_stack.deinit(self.gpa);
1609 var parent_registers = try self.registers.clone(self.gpa);
1610 defer parent_registers.deinit(self.gpa);
15941611
1595 fn genX86CondBr(self: *Self, inst: *ir.Inst.CondBr, opcode: u8) !MCValue {
1596 // TODO deal with liveness / deaths condbr's then_entry_deaths and else_entry_deaths
1597 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode });
1598 const reloc = Reloc{ .rel32 = self.code.items.len };
1599 self.code.items.len += 4;
1612 try self.branch_stack.append(.{});
1613
1614 const then_deaths = inst.thenDeaths();
1615 try self.ensureProcessDeathCapacity(then_deaths.len);
1616 for (then_deaths) |operand| {
1617 self.processDeath(operand);
1618 }
16001619 try self.genBody(inst.then_body);
1620
1621 // Revert to the previous register and stack allocation state.
1622
1623 var saved_then_branch = self.branch_stack.pop();
1624 defer saved_then_branch.deinit(self.gpa);
1625
1626 self.registers.deinit(self.gpa);
1627 self.registers = parent_registers;
1628 parent_registers = .{};
1629
1630 self.stack.deinit(self.gpa);
1631 self.stack = parent_stack;
1632 parent_stack = .{};
1633
1634 self.next_stack_offset = parent_next_stack_offset;
1635 self.free_registers = parent_free_registers;
1636
16011637 try self.performReloc(inst.base.src, reloc);
1638 const else_branch = self.branch_stack.addOneAssumeCapacity();
1639 else_branch.* = .{};
1640
1641 const else_deaths = inst.elseDeaths();
1642 try self.ensureProcessDeathCapacity(else_deaths.len);
1643 for (else_deaths) |operand| {
1644 self.processDeath(operand);
1645 }
16021646 try self.genBody(inst.else_body);
1647
1648 // At this point, each branch will possibly have conflicting values for where
1649 // each instruction is stored. They agree, however, on which instructions are alive/dead.
1650 // We use the first ("then") branch as canonical, and here emit
1651 // instructions into the second ("else") branch to make it conform.
1652 // We continue respect the data structure semantic guarantees of the else_branch so
1653 // that we can use all the code emitting abstractions. This is why at the bottom we
1654 // assert that parent_branch.free_registers equals the saved_then_branch.free_registers
1655 // rather than assigning it.
1656 const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 2];
1657 try parent_branch.inst_table.ensureCapacity(self.gpa, parent_branch.inst_table.items().len +
1658 else_branch.inst_table.items().len);
1659 for (else_branch.inst_table.items()) |else_entry| {
1660 const canon_mcv = if (saved_then_branch.inst_table.remove(else_entry.key)) |then_entry| blk: {
1661 // The instruction's MCValue is overridden in both branches.
1662 parent_branch.inst_table.putAssumeCapacity(else_entry.key, then_entry.value);
1663 if (else_entry.value == .dead) {
1664 assert(then_entry.value == .dead);
1665 continue;
1666 }
1667 break :blk then_entry.value;
1668 } else blk: {
1669 if (else_entry.value == .dead)
1670 continue;
1671 // The instruction is only overridden in the else branch.
1672 var i: usize = self.branch_stack.items.len - 2;
1673 while (true) {
1674 i -= 1; // If this overflows, the question is: why wasn't the instruction marked dead?
1675 if (self.branch_stack.items[i].inst_table.get(else_entry.key)) |mcv| {
1676 assert(mcv != .dead);
1677 break :blk mcv;
1678 }
1679 }
1680 };
1681 log.debug("consolidating else_entry {*} {}=>{}", .{else_entry.key, else_entry.value, canon_mcv});
1682 // TODO make sure the destination stack offset / register does not already have something
1683 // going on there.
1684 try self.setRegOrMem(inst.base.src, else_entry.key.ty, canon_mcv, else_entry.value);
1685 // TODO track the new register / stack allocation
1686 }
1687 try parent_branch.inst_table.ensureCapacity(self.gpa, parent_branch.inst_table.items().len +
1688 saved_then_branch.inst_table.items().len);
1689 for (saved_then_branch.inst_table.items()) |then_entry| {
1690 // We already deleted the items from this table that matched the else_branch.
1691 // So these are all instructions that are only overridden in the then branch.
1692 parent_branch.inst_table.putAssumeCapacity(then_entry.key, then_entry.value);
1693 if (then_entry.value == .dead)
1694 continue;
1695 const parent_mcv = blk: {
1696 var i: usize = self.branch_stack.items.len - 2;
1697 while (true) {
1698 i -= 1;
1699 if (self.branch_stack.items[i].inst_table.get(then_entry.key)) |mcv| {
1700 assert(mcv != .dead);
1701 break :blk mcv;
1702 }
1703 }
1704 };
1705 log.debug("consolidating then_entry {*} {}=>{}", .{then_entry.key, parent_mcv, then_entry.value});
1706 // TODO make sure the destination stack offset / register does not already have something
1707 // going on there.
1708 try self.setRegOrMem(inst.base.src, then_entry.key.ty, parent_mcv, then_entry.value);
1709 // TODO track the new register / stack allocation
1710 }
1711
1712 self.branch_stack.pop().deinit(self.gpa);
1713
16031714 return MCValue.unreach;
16041715 }
16051716
......@@ -1673,11 +1784,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
16731784 switch (reloc) {
16741785 .rel32 => |pos| {
16751786 const amt = self.code.items.len - (pos + 4);
1676 // If it wouldn't jump at all, elide it.
1677 if (amt == 0) {
1678 self.code.items.len -= 5;
1679 return;
1680 }
1787 // Here it would be tempting to implement testing for amt == 0 and then elide the
1788 // jump. However, that will cause a problem because other jumps may assume that they
1789 // can jump to this code. Or maybe I didn't understand something when I was debugging.
1790 // It could be worth another look. Anyway, that's why that isn't done here. Probably the
1791 // best place to elide jumps will be in semantic analysis, by inlining blocks that only
1792 // only have 1 break instruction.
16811793 const s32_amt = math.cast(i32, amt) catch
16821794 return self.fail(src, "unable to perform relocation: jump too far", .{});
16831795 mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt);
......@@ -2282,8 +2394,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
22822394 }
22832395
22842396 fn resolveInst(self: *Self, inst: *ir.Inst) !MCValue {
2397 // If the type has no codegen bits, no need to store it.
2398 if (!inst.ty.hasCodeGenBits())
2399 return MCValue.none;
2400
22852401 // Constants have static lifetimes, so they are always memoized in the outer most table.
2286 if (inst.cast(ir.Inst.Constant)) |const_inst| {
2402 if (inst.castTag(.constant)) |const_inst| {
22872403 const branch = &self.branch_stack.items[0];
22882404 const gop = try branch.inst_table.getOrPut(self.gpa, inst);
22892405 if (!gop.found_existing) {
......@@ -2292,6 +2408,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
22922408 return gop.entry.value;
22932409 }
22942410
2411 return self.getResolvedInstValue(inst);
2412 }
2413
2414 fn getResolvedInstValue(self: *Self, inst: *ir.Inst) MCValue {
22952415 // Treat each stack item as a "layer" on top of the previous one.
22962416 var i: usize = self.branch_stack.items.len;
22972417 while (true) {
src-self-hosted/link/Elf.zig+10-3
......@@ -17,6 +17,7 @@ const Type = @import("../type.zig").Type;
1717const link = @import("../link.zig");
1818const File = link.File;
1919const Elf = @This();
20const build_options = @import("build_options");
2021
2122const default_entry_addr = 0x8000000;
2223
......@@ -1640,9 +1641,15 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
16401641 else => false,
16411642 };
16421643 if (is_fn) {
1643 //if (mem.eql(u8, mem.spanZ(decl.name), "add")) {
1644 // typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*);
1645 //}
1644 const zir_dumps = if (std.builtin.is_test) &[0][]const u8{} else build_options.zir_dumps;
1645 if (zir_dumps.len != 0) {
1646 for (zir_dumps) |fn_name| {
1647 if (mem.eql(u8, mem.spanZ(decl.name), fn_name)) {
1648 std.debug.print("\n{}\n", .{decl.name});
1649 typed_value.val.cast(Value.Payload.Function).?.func.dump(module.*);
1650 }
1651 }
1652 }
16461653
16471654 // For functions we need to add a prologue to the debug line program.
16481655 try dbg_line_buffer.ensureCapacity(26);
src-self-hosted/type.zig+2-2
......@@ -771,8 +771,8 @@ pub const Type = extern union {
771771 .array => self.elemType().hasCodeGenBits() and self.arrayLen() != 0,
772772 .array_u8 => self.arrayLen() != 0,
773773 .array_sentinel, .single_const_pointer, .single_mut_pointer, .many_const_pointer, .many_mut_pointer, .c_const_pointer, .c_mut_pointer, .const_slice, .mut_slice, .pointer => self.elemType().hasCodeGenBits(),
774 .int_signed => self.cast(Payload.IntSigned).?.bits == 0,
775 .int_unsigned => self.cast(Payload.IntUnsigned).?.bits == 0,
774 .int_signed => self.cast(Payload.IntSigned).?.bits != 0,
775 .int_unsigned => self.cast(Payload.IntUnsigned).?.bits != 0,
776776
777777 .error_union => {
778778 const payload = self.cast(Payload.ErrorUnion).?;
src-self-hosted/zir.zig+11-1
......@@ -954,6 +954,7 @@ pub const Module = struct {
954954
955955 pub const MetaData = struct {
956956 deaths: ir.Inst.DeathsInt,
957 addr: usize,
957958 };
958959
959960 pub const BodyMetaData = struct {
......@@ -1152,6 +1153,12 @@ const Writer = struct {
11521153 try self.writeInstToStream(stream, inst);
11531154 if (self.module.metadata.get(inst)) |metadata| {
11541155 try stream.print(" ; deaths=0b{b}", .{metadata.deaths});
1156 // This is conditionally compiled in because addresses mess up the tests due
1157 // to Address Space Layout Randomization. It's super useful when debugging
1158 // codegen.zig though.
1159 if (!std.builtin.is_test) {
1160 try stream.print(" 0x{x}", .{metadata.addr});
1161 }
11551162 }
11561163 self.indent -= 2;
11571164 try stream.writeByte('\n');
......@@ -2417,7 +2424,10 @@ const EmitZIR = struct {
24172424
24182425 .varptr => @panic("TODO"),
24192426 };
2420 try self.metadata.put(new_inst, .{ .deaths = inst.deaths });
2427 try self.metadata.put(new_inst, .{
2428 .deaths = inst.deaths,
2429 .addr = @ptrToInt(inst),
2430 });
24212431 try instructions.append(new_inst);
24222432 try inst_table.put(inst, new_inst);
24232433 }
test/stage2/test.zig+62
......@@ -694,6 +694,68 @@ pub fn addCases(ctx: *TestContext) !void {
694694 "",
695695 );
696696
697 // Reusing the registers of dead operands playing nicely with conditional branching.
698 case.addCompareOutput(
699 \\export fn _start() noreturn {
700 \\ assert(add(3, 4) == 791);
701 \\ assert(add(4, 3) == 79);
702 \\
703 \\ exit();
704 \\}
705 \\
706 \\fn add(a: u32, b: u32) u32 {
707 \\ const x: u32 = if (a < b) blk: {
708 \\ const c = a + b; // 7
709 \\ const d = a + c; // 10
710 \\ const e = d + b; // 14
711 \\ const f = d + e; // 24
712 \\ const g = e + f; // 38
713 \\ const h = f + g; // 62
714 \\ const i = g + h; // 100
715 \\ const j = i + d; // 110
716 \\ const k = i + j; // 210
717 \\ const l = k + c; // 217
718 \\ const m = l + d; // 227
719 \\ const n = m + e; // 241
720 \\ const o = n + f; // 265
721 \\ const p = o + g; // 303
722 \\ const q = p + h; // 365
723 \\ const r = q + i; // 465
724 \\ const s = r + j; // 575
725 \\ const t = s + k; // 785
726 \\ break :blk t;
727 \\ } else blk: {
728 \\ const t = b + b + a; // 10
729 \\ const c = a + t; // 14
730 \\ const d = c + t; // 24
731 \\ const e = d + t; // 34
732 \\ const f = e + t; // 44
733 \\ const g = f + t; // 54
734 \\ const h = c + g; // 68
735 \\ break :blk h + b; // 71
736 \\ };
737 \\ const y = x + a; // 788, 75
738 \\ const z = y + a; // 791, 79
739 \\ return z;
740 \\}
741 \\
742 \\pub fn assert(ok: bool) void {
743 \\ if (!ok) unreachable; // assertion failure
744 \\}
745 \\
746 \\fn exit() noreturn {
747 \\ asm volatile ("syscall"
748 \\ :
749 \\ : [number] "{rax}" (231),
750 \\ [arg1] "{rdi}" (0)
751 \\ : "rcx", "r11", "memory"
752 \\ );
753 \\ unreachable;
754 \\}
755 ,
756 "",
757 );
758
697759 // Character literals and multiline strings.
698760 case.addCompareOutput(
699761 \\export fn _start() noreturn {