authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-11-19 20:30:20+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-11-19 20:30:20+01:00
log18a909f61d87d6b8e89147b7b46d8d0785b24b4d
tree7a998dd9f553f346636fb1103fd38627bdeab328
parent6cf8a49bb0e5a6df3154aa7c41602ef21057ce50
parentbc59a630ab7814ac0ca5487c780a03d7492648f7

Merge branch 'g-w1-callee-preserved'


7 files changed, 167 insertions(+), 29 deletions(-)

src/arch/x86/bits.zig+8-6
......@@ -32,11 +32,9 @@ pub const Register = enum(u8) {
3232 /// Returns the index into `callee_preserved_regs`.
3333 pub fn allocIndex(self: Register) ?u4 {
3434 return switch (self) {
35 .eax, .ax, .al => 0,
36 .ecx, .cx, .cl => 1,
37 .edx, .dx, .dl => 2,
38 .esi, .si => 3,
39 .edi, .di => 4,
35 .ebx, .bx, .bl => 0,
36 .esi, .si => 1,
37 .edi, .di => 2,
4038 else => null,
4139 };
4240 }
......@@ -74,7 +72,11 @@ pub const Register = enum(u8) {
7472
7573// zig fmt: on
7674
77pub const callee_preserved_regs = [_]Register{ .eax, .ecx, .edx, .esi, .edi };
75/// These registers need to be preserved (saved on the stack) and restored by the callee before getting clobbered
76/// and when the callee returns.
77/// Note that .esp and .ebp also belong to this set, however, we never expect to use them
78/// for anything else but stack offset tracking therefore we exclude them from this set.
79pub const callee_preserved_regs = [_]Register{ .ebx, .esi, .edi };
7880
7981// TODO add these to Register enum and corresponding dwarfLocOp
8082// // Return Address register. This is stored in `0(%esp, "")` and is not a physical register.
src/arch/x86_64/CodeGen.zig+59-9
......@@ -159,6 +159,13 @@ pub const MCValue = union(enum) {
159159 => true,
160160 };
161161 }
162
163 fn isRegister(mcv: MCValue) bool {
164 return switch (mcv) {
165 .register => true,
166 else => false,
167 };
168 }
162169};
163170
164171const Branch = struct {
......@@ -349,6 +356,13 @@ pub fn addExtraAssumeCapacity(self: *Self, extra: anytype) u32 {
349356fn gen(self: *Self) InnerError!void {
350357 const cc = self.fn_type.fnCallingConvention();
351358 if (cc != .Naked) {
359 // push the callee_preserved_regs that were used
360 const backpatch_push_callee_preserved_regs_i = try self.addInst(.{
361 .tag = .push_regs_from_callee_preserved_regs,
362 .ops = undefined,
363 .data = .{ .regs_to_push_or_pop = undefined }, // to be backpatched
364 });
365
352366 _ = try self.addInst(.{
353367 .tag = .push,
354368 .ops = (Mir.Ops{
......@@ -423,6 +437,22 @@ fn gen(self: *Self) InnerError!void {
423437 }).encode(),
424438 .data = undefined,
425439 });
440 // calculate the data for callee_preserved_regs to be pushed and popped
441 var callee_preserved_regs_push_data: u32 = 0x0;
442 inline for (callee_preserved_regs) |reg, i| {
443 if (self.register_manager.isRegAllocated(reg)) {
444 callee_preserved_regs_push_data |= 1 << @intCast(u5, i);
445 }
446 }
447 const data = self.mir_instructions.items(.data);
448 // backpatch the push instruction
449 data[backpatch_push_callee_preserved_regs_i].regs_to_push_or_pop = callee_preserved_regs_push_data;
450 // pop the callee_preserved_regs
451 _ = try self.addInst(.{
452 .tag = .pop_regs_from_callee_preserved_regs,
453 .ops = undefined,
454 .data = .{ .regs_to_push_or_pop = callee_preserved_regs_push_data },
455 });
426456 _ = try self.addInst(.{
427457 .tag = .ret,
428458 .ops = (Mir.Ops{
......@@ -737,6 +767,19 @@ fn copyToNewRegister(self: *Self, reg_owner: Air.Inst.Index, mcv: MCValue) !MCVa
737767 return MCValue{ .register = reg };
738768}
739769
770/// Like `copyToNewRegister` but allows to specify a list of excluded registers which
771/// will not be selected for allocation. This can be done via `exceptions` slice.
772fn copyToNewRegisterWithExceptions(
773 self: *Self,
774 reg_owner: Air.Inst.Index,
775 mcv: MCValue,
776 exceptions: []const Register,
777) !MCValue {
778 const reg = try self.register_manager.allocReg(reg_owner, exceptions);
779 try self.genSetReg(self.air.typeOfIndex(reg_owner), reg, mcv);
780 return MCValue{ .register = reg };
781}
782
740783fn airAlloc(self: *Self, inst: Air.Inst.Index) !void {
741784 const stack_offset = try self.allocMemPtr(inst);
742785 return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none });
......@@ -1427,11 +1470,9 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs:
14271470 // as the result MCValue.
14281471 var dst_mcv: MCValue = undefined;
14291472 var src_mcv: MCValue = undefined;
1430 var src_inst: Air.Inst.Ref = undefined;
14311473 if (self.reuseOperand(inst, op_lhs, 0, lhs)) {
14321474 // LHS dies; use it as the destination.
14331475 // Both operands cannot be memory.
1434 src_inst = op_rhs;
14351476 if (lhs.isMemory() and rhs.isMemory()) {
14361477 dst_mcv = try self.copyToNewRegister(inst, lhs);
14371478 src_mcv = rhs;
......@@ -1442,7 +1483,6 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs:
14421483 } else if (self.reuseOperand(inst, op_rhs, 1, rhs)) {
14431484 // RHS dies; use it as the destination.
14441485 // Both operands cannot be memory.
1445 src_inst = op_lhs;
14461486 if (lhs.isMemory() and rhs.isMemory()) {
14471487 dst_mcv = try self.copyToNewRegister(inst, rhs);
14481488 src_mcv = lhs;
......@@ -1452,13 +1492,23 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs:
14521492 }
14531493 } else {
14541494 if (lhs.isMemory()) {
1455 dst_mcv = try self.copyToNewRegister(inst, lhs);
1495 dst_mcv = if (rhs.isRegister())
1496 // If the allocated register is the same as the rhs register, don't allocate that one
1497 // and instead spill a subsequent one. Otherwise, this can result in a miscompilation
1498 // in the presence of several binary operations performed in a single block.
1499 try self.copyToNewRegisterWithExceptions(inst, lhs, &.{rhs.register})
1500 else
1501 try self.copyToNewRegister(inst, lhs);
14561502 src_mcv = rhs;
1457 src_inst = op_rhs;
14581503 } else {
1459 dst_mcv = try self.copyToNewRegister(inst, rhs);
1504 dst_mcv = if (lhs.isRegister())
1505 // If the allocated register is the same as the rhs register, don't allocate that one
1506 // and instead spill a subsequent one. Otherwise, this can result in a miscompilation
1507 // in the presence of several binary operations performed in a single block.
1508 try self.copyToNewRegisterWithExceptions(inst, rhs, &.{lhs.register})
1509 else
1510 try self.copyToNewRegister(inst, rhs);
14601511 src_mcv = lhs;
1461 src_inst = op_lhs;
14621512 }
14631513 }
14641514 // This instruction supports only signed 32-bit immediates at most. If the immediate
......@@ -1902,10 +1952,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void {
19021952 try self.register_manager.getReg(reg, null);
19031953 try self.genSetReg(arg_ty, reg, arg_mcv);
19041954 },
1905 .stack_offset => {
1955 .stack_offset => |off| {
19061956 // Here we need to emit instructions like this:
19071957 // mov qword ptr [rsp + stack_offset], x
1908 return self.fail("TODO implement calling with parameters in memory", .{});
1958 try self.genSetStack(arg_ty, off, arg_mcv);
19091959 },
19101960 .ptr_stack_offset => {
19111961 return self.fail("TODO implement calling with MCValue.ptr_stack_offset arg", .{});
src/arch/x86_64/Emit.zig+36
......@@ -142,6 +142,9 @@ pub fn emitMir(emit: *Emit) InnerError!void {
142142 .dbg_epilogue_begin => try emit.mirDbgEpilogueBegin(inst),
143143 .arg_dbg_info => try emit.mirArgDbgInfo(inst),
144144
145 .push_regs_from_callee_preserved_regs => try emit.mirPushPopRegsFromCalleePreservedRegs(.push, inst),
146 .pop_regs_from_callee_preserved_regs => try emit.mirPushPopRegsFromCalleePreservedRegs(.pop, inst),
147
145148 else => {
146149 return emit.fail("Implement MIR->Isel lowering for x86_64 for pseudo-inst: {s}", .{tag});
147150 },
......@@ -244,6 +247,39 @@ fn mirPushPop(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!v
244247 0b11 => unreachable,
245248 }
246249}
250fn mirPushPopRegsFromCalleePreservedRegs(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void {
251 const callee_preserved_regs = bits.callee_preserved_regs;
252 // PUSH/POP reg
253 const opc: u8 = switch (tag) {
254 .push => 0x50,
255 .pop => 0x58,
256 else => unreachable,
257 };
258
259 const regs = emit.mir.instructions.items(.data)[inst].regs_to_push_or_pop;
260 if (tag == .push) {
261 for (callee_preserved_regs) |reg, i| {
262 if ((regs >> @intCast(u5, i)) & 1 == 0) continue;
263 const encoder = try Encoder.init(emit.code, 2);
264 encoder.rex(.{
265 .b = reg.isExtended(),
266 });
267 encoder.opcode_withReg(opc, reg.lowId());
268 }
269 } else {
270 // pop in the reverse direction
271 var i = callee_preserved_regs.len;
272 while (i > 0) : (i -= 1) {
273 const reg = callee_preserved_regs[i - 1];
274 if ((regs >> @intCast(u5, i - 1)) & 1 == 0) continue;
275 const encoder = try Encoder.init(emit.code, 2);
276 encoder.rex(.{
277 .b = reg.isExtended(),
278 });
279 encoder.opcode_withReg(opc, reg.lowId());
280 }
281 }
282}
247283
248284fn mirJmpCall(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void {
249285 const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]);
src/arch/x86_64/Mir.zig+16-1
......@@ -264,8 +264,21 @@ pub const Inst = struct {
264264
265265 /// arg debug info
266266 arg_dbg_info,
267 };
268267
268 /// push registers from the callee_preserved_regs
269 /// data is the bitfield of which regs to push
270 /// for example on x86_64, the callee_preserved_regs are [_]Register{ .rcx, .rsi, .rdi, .r8, .r9, .r10, .r11 }; };
271 /// so to push rcx and r8 one would make data 0b00000000_00000000_00000000_00001001 (the first and fourth bits are set)
272 /// ops is unused
273 push_regs_from_callee_preserved_regs,
274
275 /// pop registers from the callee_preserved_regs
276 /// data is the bitfield of which regs to pop
277 /// for example on x86_64, the callee_preserved_regs are [_]Register{ .rcx, .rsi, .rdi, .r8, .r9, .r10, .r11 }; };
278 /// so to pop rcx and r8 one would make data 0b00000000_00000000_00000000_00001001 (the first and fourth bits are set)
279 /// ops is unused
280 pop_regs_from_callee_preserved_regs,
281 };
269282 /// The position of an MIR instruction within the `Mir` instructions array.
270283 pub const Index = u32;
271284
......@@ -284,6 +297,8 @@ pub const Inst = struct {
284297 got_entry: u32,
285298 /// Index into `extra`. Meaning of what can be found there is context-dependent.
286299 payload: u32,
300 /// A bitfield of which callee_preserved_regs to push
301 regs_to_push_or_pop: u32,
287302 };
288303
289304 // Make sure we don't accidentally make instructions bigger than expected.
src/arch/x86_64/bits.zig+14-11
......@@ -84,15 +84,11 @@ pub const Register = enum(u7) {
8484 /// Returns the index into `callee_preserved_regs`.
8585 pub fn allocIndex(self: Register) ?u4 {
8686 return switch (self) {
87 .rax, .eax, .ax, .al => 0,
88 .rcx, .ecx, .cx, .cl => 1,
89 .rdx, .edx, .dx, .dl => 2,
90 .rsi, .esi, .si => 3,
91 .rdi, .edi, .di => 4,
92 .r8, .r8d, .r8w, .r8b => 5,
93 .r9, .r9d, .r9w, .r9b => 6,
94 .r10, .r10d, .r10w, .r10b => 7,
95 .r11, .r11d, .r11w, .r11b => 8,
87 .rbx, .ebx, .bx, .bl => 0,
88 .r12, .r12d, .r12w, .r12b => 1,
89 .r13, .r13d, .r13w, .r13b => 2,
90 .r14, .r14d, .r14w, .r14b => 3,
91 .r15, .r15d, .r15w, .r15b => 4,
9692 else => null,
9793 };
9894 }
......@@ -144,8 +140,15 @@ pub const Register = enum(u7) {
144140
145141// zig fmt: on
146142
147/// These registers belong to the called function.
148pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 };
143/// These registers need to be preserved (saved on the stack) and restored by the callee before getting clobbered
144/// and when the callee returns.
145/// Note that .rsp and .rbp also belong to this set, however, we never expect to use them
146/// for anything else but stack offset tracking therefore we exclude them from this set.
147pub const callee_preserved_regs = [_]Register{ .rbx, .r12, .r13, .r14, .r15 };
148/// These registers need to be preserved (saved on the stack) and restored by the caller before
149/// the caller relinquishes control to a subroutine via call instruction (or similar).
150/// In other words, these registers are free to use by the callee.
151pub const caller_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 };
149152pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
150153pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };
151154
src/link/Elf.zig+2-2
......@@ -2604,12 +2604,12 @@ fn addDbgInfoType(self: *Elf, ty: Type, dbg_info_buffer: *std.ArrayList(u8)) !vo
26042604 // DW.AT.name, DW.FORM.string
26052605 try dbg_info_buffer.writer().print("{}\x00", .{ty});
26062606 } else {
2607 log.err("TODO implement .debug_info for type '{}'", .{ty});
2607 log.debug("TODO implement .debug_info for type '{}'", .{ty});
26082608 try dbg_info_buffer.append(abbrev_pad1);
26092609 }
26102610 },
26112611 else => {
2612 log.err("TODO implement .debug_info for type '{}'", .{ty});
2612 log.debug("TODO implement .debug_info for type '{}'", .{ty});
26132613 try dbg_info_buffer.append(abbrev_pad1);
26142614 },
26152615 }
test/cases.zig+32
......@@ -1819,4 +1819,36 @@ pub fn addCases(ctx: *TestContext) !void {
18191819 ":2:28: error: cannot set address space of local variable 'foo'",
18201820 });
18211821 }
1822 {
1823 var case = ctx.exe("issue 10138: callee preserved regs working", linux_x64);
1824 case.addCompareOutput(
1825 \\pub fn main() void {
1826 \\ const fd = open();
1827 \\ _ = write(fd, "a", 1);
1828 \\ _ = close(fd);
1829 \\}
1830 \\
1831 \\fn open() usize {
1832 \\ return 42;
1833 \\}
1834 \\
1835 \\fn write(fd: usize, a: [*]const u8, len: usize) usize {
1836 \\ return syscall4(.WRITE, fd, @ptrToInt(a), len);
1837 \\}
1838 \\
1839 \\fn syscall4(n: enum { WRITE }, a: usize, b: usize, c: usize) usize {
1840 \\ _ = n;
1841 \\ _ = a;
1842 \\ _ = b;
1843 \\ _ = c;
1844 \\ return 23;
1845 \\}
1846 \\
1847 \\fn close(fd: usize) usize {
1848 \\ if (fd != 42)
1849 \\ unreachable;
1850 \\ return 0;
1851 \\}
1852 , "");
1853 }
18221854}