authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-21 23:36:21-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-08-21 23:36:21-07:00
logf18b92ef3ab689749efd135968f0834478f9fffa
treec15a49a80dd35c40df2f8f0976d7462304f8c301
parentdad7af0b37f352c8c390438877ef1552a316ffde

stage2: implement spilling registers to the stack


2 files changed, 227 insertions(+), 60 deletions(-)

src-self-hosted/codegen.zig+175-60
......@@ -14,6 +14,7 @@ const Allocator = mem.Allocator;
1414const trace = @import("tracy.zig").trace;
1515const DW = std.dwarf;
1616const leb128 = std.debug.leb;
17const log = std.log.scoped(.codegen);
1718
1819// TODO Turn back on zig fmt when https://github.com/ziglang/zig/issues/5948 is implemented.
1920// zig fmt: off
......@@ -344,6 +345,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
344345
345346 const Branch = struct {
346347 inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{},
348 /// The key must be canonical register.
347349 registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{},
348350 free_registers: FreeRegInt = math.maxInt(FreeRegInt),
349351
......@@ -381,9 +383,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
381383 self.free_registers &= ~(@as(FreeRegInt, 1) << free_index);
382384 const reg = callee_preserved_regs[free_index];
383385 self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst });
386 log.debug("alloc {} => {*}", .{reg, inst});
384387 return reg;
385388 }
386389
390 /// Does not track the register.
391 fn findUnusedReg(self: *Branch) ?Register {
392 const free_index = @ctz(FreeRegInt, self.free_registers);
393 if (free_index >= callee_preserved_regs.len) {
394 return null;
395 }
396 return callee_preserved_regs[free_index];
397 }
398
387399 fn deinit(self: *Branch, gpa: *Allocator) void {
388400 self.inst_table.deinit(gpa);
389401 self.registers.deinit(gpa);
......@@ -570,8 +582,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
570582 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
571583 const inst_table = &branch.inst_table;
572584 for (body.instructions) |inst| {
573 const new_inst = try self.genFuncInst(inst);
574 try inst_table.putNoClobber(self.gpa, inst, new_inst);
585 const mcv = try self.genFuncInst(inst);
586 log.debug("{*} => {}", .{inst, mcv});
587 // TODO don't put void or dead things in here
588 try inst_table.putNoClobber(self.gpa, inst, mcv);
575589
576590 var i: ir.Inst.DeathsBitIndex = 0;
577591 while (inst.getOperand(i)) |operand| : (i += 1) {
......@@ -714,7 +728,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
714728 return self.allocMem(inst, abi_size, abi_align);
715729 }
716730
717 fn allocRegOrMem(self: *Self, inst: *ir.Inst) !MCValue {
731 fn allocRegOrMem(self: *Self, inst: *ir.Inst, reg_ok: bool) !MCValue {
718732 const elem_ty = inst.ty;
719733 const abi_size = math.cast(u32, elem_ty.abiSize(self.target.*)) catch {
720734 return self.fail(inst.src, "type '{}' too big to fit into stack frame", .{elem_ty});
......@@ -724,30 +738,73 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
724738 self.stack_align = abi_align;
725739 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
726740
727 // Make sure the type can fit in a register before we try to allocate one.
728 const ptr_bits = arch.ptrBitWidth();
729 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
730 if (abi_size <= ptr_bytes) {
731 try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
732 if (branch.allocReg(inst)) |reg| {
733 return MCValue{ .register = registerAlias(reg, abi_size) };
741 if (reg_ok) {
742 // Make sure the type can fit in a register before we try to allocate one.
743 const ptr_bits = arch.ptrBitWidth();
744 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
745 if (abi_size <= ptr_bytes) {
746 try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
747 if (branch.allocReg(inst)) |reg| {
748 return MCValue{ .register = registerAlias(reg, abi_size) };
749 }
734750 }
735751 }
736752 const stack_offset = try self.allocMem(inst, abi_size, abi_align);
737753 return MCValue{ .stack_offset = stack_offset };
738754 }
739755
740 /// Does not "move" the instruction.
741 fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue {
756 /// Copies a value to a register without tracking the register. The register is not considered
757 /// allocated. A second call to `copyToTmpRegister` may return the same register.
758 /// This can have a side effect of spilling instructions to the stack to free up a register.
759 fn copyToTmpRegister(self: *Self, src: usize, mcv: MCValue) !Register {
760 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
761
762 const reg = branch.findUnusedReg() orelse b: {
763 // We'll take over the first register. Move the instruction that was previously
764 // there to a stack allocation.
765 const reg = callee_preserved_regs[0];
766 const regs_entry = branch.registers.remove(reg).?;
767 const spilled_inst = regs_entry.value.inst;
768
769 const stack_mcv = try self.allocRegOrMem(spilled_inst, false);
770 const inst_entry = branch.inst_table.getEntry(spilled_inst).?;
771 const reg_mcv = inst_entry.value;
772 assert(reg == toCanonicalReg(reg_mcv.register));
773 inst_entry.value = stack_mcv;
774 try self.genSetStack(src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv);
775
776 break :b reg;
777 };
778 try self.genSetReg(src, reg, mcv);
779 return reg;
780 }
781
782 /// Allocates a new register and copies `mcv` into it.
783 /// `reg_owner` is the instruction that gets associated with the register in the register table.
784 /// This can have a side effect of spilling instructions to the stack to free up a register.
785 fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue {
742786 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
743787 try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1);
744788
745 const reg = branch.allocReg(inst) orelse
746 return self.fail(inst.src, "TODO implement spilling register to stack", .{});
747 const old_mcv = branch.inst_table.get(inst).?;
748 const new_mcv: MCValue = .{ .register = reg };
749 try self.genSetReg(inst.src, reg, old_mcv);
750 return new_mcv;
789 const reg = branch.allocReg(reg_owner) orelse b: {
790 // We'll take over the first register. Move the instruction that was previously
791 // there to a stack allocation.
792 const reg = callee_preserved_regs[0];
793 const regs_entry = branch.registers.getEntry(reg).?;
794 const spilled_inst = regs_entry.value.inst;
795 regs_entry.value = .{ .inst = reg_owner };
796
797 const stack_mcv = try self.allocRegOrMem(spilled_inst, false);
798 const inst_entry = branch.inst_table.getEntry(spilled_inst).?;
799 const reg_mcv = inst_entry.value;
800 assert(reg == toCanonicalReg(reg_mcv.register));
801 inst_entry.value = stack_mcv;
802 try self.genSetStack(reg_owner.src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv);
803
804 break :b reg;
805 };
806 try self.genSetReg(reg_owner.src, reg, mcv);
807 return MCValue{ .register = reg };
751808 }
752809
753810 fn genAlloc(self: *Self, inst: *ir.Inst.NoOp) !MCValue {
......@@ -868,13 +925,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
868925 }
869926 }
870927
871 fn reuseOperand(inst: *ir.Inst, op_index: ir.Inst.DeathsBitIndex, mcv: MCValue) bool {
872 if (!inst.operandDies(op_index) or !mcv.isMutable())
928 fn reuseOperand(self: *Self, inst: *ir.Inst, op_index: ir.Inst.DeathsBitIndex, mcv: MCValue) bool {
929 if (!inst.operandDies(op_index))
873930 return false;
874931
875 // OK we're going to do it, but we need to clear the operand death bit so that
876 // it stays allocated.
932 switch (mcv) {
933 .register => |reg| {
934 // If it's in the registers table, need to associate the register with the
935 // new instruction.
936 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
937 const entry = branch.registers.getEntry(toCanonicalReg(reg)).?;
938 entry.value = .{ .inst = inst };
939 log.debug("reusing {} => {*}", .{reg, inst});
940 },
941 .stack_offset => |off| {
942 log.debug("reusing stack offset {} => {*}", .{off, inst});
943 return true;
944 },
945 else => return false,
946 }
947
948 // Prevent the operand deaths processing code from deallocating it.
877949 inst.clearOperandDeath(op_index);
950
878951 return true;
879952 }
880953
......@@ -887,11 +960,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
887960 if (inst.base.isUnused() and !is_volatile)
888961 return MCValue.dead;
889962 const dst_mcv: MCValue = blk: {
890 if (reuseOperand(&inst.base, 0, ptr)) {
963 if (self.reuseOperand(&inst.base, 0, ptr)) {
891964 // The MCValue that holds the pointer can be re-used as the value.
892965 break :blk ptr;
893966 } else {
894 break :blk try self.allocRegOrMem(&inst.base);
967 break :blk try self.allocRegOrMem(&inst.base, true);
895968 }
896969 };
897970 switch (ptr) {
......@@ -985,23 +1058,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
9851058 var dst_mcv: MCValue = undefined;
9861059 var src_mcv: MCValue = undefined;
9871060 var src_inst: *ir.Inst = undefined;
988 if (reuseOperand(inst, 0, lhs)) {
1061 if (self.reuseOperand(inst, 0, lhs)) {
9891062 // LHS dies; use it as the destination.
9901063 // Both operands cannot be memory.
9911064 src_inst = op_rhs;
9921065 if (lhs.isMemory() and rhs.isMemory()) {
993 dst_mcv = try self.copyToNewRegister(op_lhs);
1066 dst_mcv = try self.copyToNewRegister(inst, lhs);
9941067 src_mcv = rhs;
9951068 } else {
9961069 dst_mcv = lhs;
9971070 src_mcv = rhs;
9981071 }
999 } else if (reuseOperand(inst, 1, rhs)) {
1072 } else if (self.reuseOperand(inst, 1, rhs)) {
10001073 // RHS dies; use it as the destination.
10011074 // Both operands cannot be memory.
10021075 src_inst = op_lhs;
10031076 if (lhs.isMemory() and rhs.isMemory()) {
1004 dst_mcv = try self.copyToNewRegister(op_rhs);
1077 dst_mcv = try self.copyToNewRegister(inst, rhs);
10051078 src_mcv = lhs;
10061079 } else {
10071080 dst_mcv = rhs;
......@@ -1009,11 +1082,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
10091082 }
10101083 } else {
10111084 if (lhs.isMemory()) {
1012 dst_mcv = try self.copyToNewRegister(op_lhs);
1085 dst_mcv = try self.copyToNewRegister(inst, lhs);
10131086 src_mcv = rhs;
10141087 src_inst = op_rhs;
10151088 } else {
1016 dst_mcv = try self.copyToNewRegister(op_rhs);
1089 dst_mcv = try self.copyToNewRegister(inst, rhs);
10171090 src_mcv = lhs;
10181091 src_inst = op_lhs;
10191092 }
......@@ -1026,18 +1099,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
10261099 switch (src_mcv) {
10271100 .immediate => |imm| {
10281101 if (imm > math.maxInt(u31)) {
1029 src_mcv = try self.copyToNewRegister(src_inst);
1102 src_mcv = MCValue{ .register = try self.copyToTmpRegister(src_inst.src, src_mcv) };
10301103 }
10311104 },
10321105 else => {},
10331106 }
10341107
1035 try self.genX8664BinMathCode(inst.src, dst_mcv, src_mcv, opx, mr);
1108 try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, opx, mr);
10361109
10371110 return dst_mcv;
10381111 }
10391112
1040 fn genX8664BinMathCode(self: *Self, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void {
1113 fn genX8664BinMathCode(
1114 self: *Self,
1115 src: usize,
1116 dst_ty: Type,
1117 dst_mcv: MCValue,
1118 src_mcv: MCValue,
1119 opx: u8,
1120 mr: u8,
1121 ) !void {
10411122 switch (dst_mcv) {
10421123 .none => unreachable,
10431124 .undef => unreachable,
......@@ -1087,12 +1168,60 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
10871168 },
10881169 }
10891170 },
1090 .embedded_in_code, .memory, .stack_offset => {
1171 .stack_offset => |off| {
1172 switch (src_mcv) {
1173 .none => unreachable,
1174 .undef => return self.genSetStack(src, dst_ty, off, .undef),
1175 .dead, .unreach => unreachable,
1176 .ptr_stack_offset => unreachable,
1177 .ptr_embedded_in_code => unreachable,
1178 .register => |src_reg| {
1179 try self.genX8664ModRMRegToStack(src, dst_ty, off, src_reg, mr + 0x1);
1180 },
1181 .immediate => |imm| {
1182 return self.fail(src, "TODO implement x86 ADD/SUB/CMP source immediate", .{});
1183 },
1184 .embedded_in_code, .memory, .stack_offset => {
1185 return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{});
1186 },
1187 .compare_flags_unsigned => {
1188 return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{});
1189 },
1190 .compare_flags_signed => {
1191 return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{});
1192 },
1193 }
1194 },
1195 .embedded_in_code, .memory => {
10911196 return self.fail(src, "TODO implement x86 ADD/SUB/CMP destination memory", .{});
10921197 },
10931198 }
10941199 }
10951200
1201 fn genX8664ModRMRegToStack(self: *Self, src: usize, ty: Type, off: u32, reg: Register, opcode: u8) !void {
1202 const abi_size = ty.abiSize(self.target.*);
1203 const adj_off = off + abi_size;
1204 try self.code.ensureCapacity(self.code.items.len + 7);
1205 self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() });
1206 const reg_id: u8 = @truncate(u3, reg.id());
1207 if (adj_off <= 128) {
1208 // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx
1209 const RM = @as(u8, 0b01_000_101) | (reg_id << 3);
1210 const negative_offset = @intCast(i8, -@intCast(i32, adj_off));
1211 const twos_comp = @bitCast(u8, negative_offset);
1212 self.code.appendSliceAssumeCapacity(&[_]u8{ opcode, RM, twos_comp });
1213 } else if (adj_off <= 2147483648) {
1214 // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx
1215 const RM = @as(u8, 0b10_000_101) | (reg_id << 3);
1216 const negative_offset = @intCast(i32, -@intCast(i33, adj_off));
1217 const twos_comp = @bitCast(u32, negative_offset);
1218 self.code.appendSliceAssumeCapacity(&[_]u8{ opcode, RM });
1219 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp);
1220 } else {
1221 return self.fail(src, "stack offset too large", .{});
1222 }
1223 }
1224
10961225 fn genArg(self: *Self, inst: *ir.Inst.Arg) !MCValue {
10971226 if (FreeRegInt == u0) {
10981227 return self.fail(inst.base.src, "TODO implement Register enum for {}", .{self.target.cpu.arch});
......@@ -1109,7 +1238,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
11091238 const name_with_null = inst.name[0..mem.lenZ(inst.name) + 1];
11101239 switch (result) {
11111240 .register => |reg| {
1112 branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = &inst.base });
1241 branch.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), .{ .inst = &inst.base });
11131242 branch.markRegUsed(reg);
11141243
11151244 try self.dbg_info.ensureCapacity(self.dbg_info.items.len + 8 + name_with_null.len);
......@@ -1304,13 +1433,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
13041433 // Either one, but not both, can be a memory operand.
13051434 // Source operand can be an immediate, 8 bits or 32 bits.
13061435 const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory()))
1307 try self.copyToNewRegister(inst.lhs)
1436 try self.copyToNewRegister(&inst.base, lhs)
13081437 else
13091438 lhs;
13101439 // This instruction supports only signed 32-bit immediates at most.
13111440 const src_mcv = try self.limitImmediateType(inst.rhs, i32);
13121441
1313 try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38);
1442 try self.genX8664BinMathCode(inst.base.src, inst.base.ty, dst_mcv, src_mcv, 7, 0x38);
13141443 const info = inst.lhs.ty.intInfo(self.target.*);
13151444 if (info.signed) {
13161445 return MCValue{ .compare_flags_signed = op };
......@@ -1584,6 +1713,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
15841713 /// resulting REX is meaningful, but will remain the same if it is not.
15851714 /// * Deliberately inserting a "meaningless REX" requires explicit usage of
15861715 /// 0x40, and cannot be done via this function.
1716 /// W => 64 bit mode
1717 /// R => extension to the MODRM.reg field
1718 /// X => extension to the SIB.index field
1719 /// B => extension to the MODRM.rm field or the SIB.base field
15871720 fn rex(self: *Self, arg: struct { b: bool = false, w: bool = false, x: bool = false, r: bool = false }) void {
15881721 // From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB.
15891722 var value: u8 = 0x40;
......@@ -1681,27 +1814,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
16811814 return self.fail(src, "TODO implement set stack variable from embedded_in_code", .{});
16821815 },
16831816 .register => |reg| {
1684 const abi_size = ty.abiSize(self.target.*);
1685 const adj_off = stack_offset + abi_size;
1686 try self.code.ensureCapacity(self.code.items.len + 7);
1687 self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() });
1688 const reg_id: u8 = @truncate(u3, reg.id());
1689 if (adj_off <= 128) {
1690 // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx
1691 const RM = @as(u8, 0b01_000_101) | (reg_id << 3);
1692 const negative_offset = @intCast(i8, -@intCast(i32, adj_off));
1693 const twos_comp = @bitCast(u8, negative_offset);
1694 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM, twos_comp });
1695 } else if (adj_off <= 2147483648) {
1696 // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx
1697 const RM = @as(u8, 0b10_000_101) | (reg_id << 3);
1698 const negative_offset = @intCast(i32, -@intCast(i33, adj_off));
1699 const twos_comp = @bitCast(u32, negative_offset);
1700 self.code.appendSliceAssumeCapacity(&[_]u8{ 0x89, RM });
1701 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp);
1702 } else {
1703 return self.fail(src, "stack offset too large", .{});
1704 }
1817 try self.genX8664ModRMRegToStack(src, ty, stack_offset, reg, 0x89);
17051818 },
17061819 .memory => |vaddr| {
17071820 return self.fail(src, "TODO implement set stack variable from memory vaddr", .{});
......@@ -1709,7 +1822,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
17091822 .stack_offset => |off| {
17101823 if (stack_offset == off)
17111824 return; // Copy stack variable to itself; nothing to do.
1712 return self.fail(src, "TODO implement copy stack variable to stack variable", .{});
1825
1826 const reg = try self.copyToTmpRegister(src, mcv);
1827 return self.genSetStack(src, ty, stack_offset, MCValue{ .register = reg });
17131828 },
17141829 },
17151830 else => return self.fail(src, "TODO implement getSetStack for {}", .{self.target.cpu.arch}),
......@@ -2027,7 +2142,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
20272142 },
20282143 });
20292144 if (imm >= math.maxInt(U)) {
2030 return self.copyToNewRegister(inst);
2145 return MCValue{ .register = try self.copyToTmpRegister(inst.src, mcv) };
20312146 }
20322147 },
20332148 else => {},
test/stage2/test.zig+52
......@@ -600,6 +600,58 @@ pub fn addCases(ctx: *TestContext) !void {
600600 "",
601601 );
602602
603 // Spilling registers to the stack.
604 case.addCompareOutput(
605 \\export fn _start() noreturn {
606 \\ assert(add(3, 4) == 791);
607 \\
608 \\ exit();
609 \\}
610 \\
611 \\fn add(a: u32, b: u32) u32 {
612 \\ const x: u32 = blk: {
613 \\ const c = a + b; // 7
614 \\ const d = a + c; // 10
615 \\ const e = d + b; // 14
616 \\ const f = d + e; // 24
617 \\ const g = e + f; // 38
618 \\ const h = f + g; // 62
619 \\ const i = g + h; // 100
620 \\ const j = i + d; // 110
621 \\ const k = i + j; // 210
622 \\ const l = k + c; // 217
623 \\ const m = l + d; // 227
624 \\ const n = m + e; // 241
625 \\ const o = n + f; // 265
626 \\ const p = o + g; // 303
627 \\ const q = p + h; // 365
628 \\ const r = q + i; // 465
629 \\ const s = r + j; // 575
630 \\ const t = s + k; // 785
631 \\ break :blk t;
632 \\ };
633 \\ const y = x + a; // 788
634 \\ const z = y + a; // 791
635 \\ return z;
636 \\}
637 \\
638 \\pub fn assert(ok: bool) void {
639 \\ if (!ok) unreachable; // assertion failure
640 \\}
641 \\
642 \\fn exit() noreturn {
643 \\ asm volatile ("syscall"
644 \\ :
645 \\ : [number] "{rax}" (231),
646 \\ [arg1] "{rdi}" (0)
647 \\ : "rcx", "r11", "memory"
648 \\ );
649 \\ unreachable;
650 \\}
651 ,
652 "",
653 );
654
603655 // Character literals and multiline strings.
604656 case.addCompareOutput(
605657 \\export fn _start() noreturn {