| author | |
| committer | |
| log | bc59a630ab7814ac0ca5487c780a03d7492648f7 |
| tree | 7a998dd9f553f346636fb1103fd38627bdeab328 |
| parent | 149bc79486907745c8ffb12a69f65189b26c6dbe |
Previously, we have confused callee-saved with caller-saved registers
(the actual register sets were swapped). This commit fixes that
for both `.x86` and `.x86_64` native backends.
This commit also fixes the register allocation logic in `genBinMathOp`
for `.x86_64` native backend where in a situation such that we require
to spill a register, we would end up spilling the register that is
already involved in the instruction as the other operand. In such a
case, we make a note of this and spill a subsequent register instead.4 files changed, 58 insertions(+), 25 deletions(-)
src/arch/x86/bits.zig+8-6| ... | @@ -32,11 +32,9 @@ pub const Register = enum(u8) { | ... | @@ -32,11 +32,9 @@ pub const Register = enum(u8) { |
| 32 | /// Returns the index into `callee_preserved_regs`. | 32 | /// Returns the index into `callee_preserved_regs`. |
| 33 | pub fn allocIndex(self: Register) ?u4 { | 33 | pub fn allocIndex(self: Register) ?u4 { |
| 34 | return switch (self) { | 34 | return switch (self) { |
| 35 | .eax, .ax, .al => 0, | 35 | .ebx, .bx, .bl => 0, |
| 36 | .ecx, .cx, .cl => 1, | 36 | .esi, .si => 1, |
| 37 | .edx, .dx, .dl => 2, | 37 | .edi, .di => 2, |
| 38 | .esi, .si => 3, | ||
| 39 | .edi, .di => 4, | ||
| 40 | else => null, | 38 | else => null, |
| 41 | }; | 39 | }; |
| 42 | } | 40 | } |
| ... | @@ -74,7 +72,11 @@ pub const Register = enum(u8) { | ... | @@ -74,7 +72,11 @@ pub const Register = enum(u8) { |
| 74 | 72 | ||
| 75 | // zig fmt: on | 73 | // zig fmt: on |
| 76 | 74 | ||
| 77 | pub 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. | ||
| 79 | pub const callee_preserved_regs = [_]Register{ .ebx, .esi, .edi }; | ||
| 78 | 80 | ||
| 79 | // TODO add these to Register enum and corresponding dwarfLocOp | 81 | // TODO add these to Register enum and corresponding dwarfLocOp |
| 80 | // // Return Address register. This is stored in `0(%esp, "")` and is not a physical register. | 82 | // // Return Address register. This is stored in `0(%esp, "")` and is not a physical register. |
src/arch/x86_64/CodeGen.zig+34-7| ... | @@ -159,6 +159,13 @@ pub const MCValue = union(enum) { | ... | @@ -159,6 +159,13 @@ pub const MCValue = union(enum) { |
| 159 | => true, | 159 | => true, |
| 160 | }; | 160 | }; |
| 161 | } | 161 | } |
| 162 | |||
| 163 | fn isRegister(mcv: MCValue) bool { | ||
| 164 | return switch (mcv) { | ||
| 165 | .register => true, | ||
| 166 | else => false, | ||
| 167 | }; | ||
| 168 | } | ||
| 162 | }; | 169 | }; |
| 163 | 170 | ||
| 164 | const Branch = struct { | 171 | const Branch = struct { |
| ... | @@ -760,6 +767,19 @@ fn copyToNewRegister(self: *Self, reg_owner: Air.Inst.Index, mcv: MCValue) !MCVa | ... | @@ -760,6 +767,19 @@ fn copyToNewRegister(self: *Self, reg_owner: Air.Inst.Index, mcv: MCValue) !MCVa |
| 760 | return MCValue{ .register = reg }; | 767 | return MCValue{ .register = reg }; |
| 761 | } | 768 | } |
| 762 | 769 | ||
| 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. | ||
| 772 | fn 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 | |||
| 763 | fn airAlloc(self: *Self, inst: Air.Inst.Index) !void { | 783 | fn airAlloc(self: *Self, inst: Air.Inst.Index) !void { |
| 764 | const stack_offset = try self.allocMemPtr(inst); | 784 | const stack_offset = try self.allocMemPtr(inst); |
| 765 | return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none }); | 785 | return self.finishAir(inst, .{ .ptr_stack_offset = stack_offset }, .{ .none, .none, .none }); |
| ... | @@ -1450,11 +1470,9 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: | ... | @@ -1450,11 +1470,9 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: |
| 1450 | // as the result MCValue. | 1470 | // as the result MCValue. |
| 1451 | var dst_mcv: MCValue = undefined; | 1471 | var dst_mcv: MCValue = undefined; |
| 1452 | var src_mcv: MCValue = undefined; | 1472 | var src_mcv: MCValue = undefined; |
| 1453 | var src_inst: Air.Inst.Ref = undefined; | ||
| 1454 | if (self.reuseOperand(inst, op_lhs, 0, lhs)) { | 1473 | if (self.reuseOperand(inst, op_lhs, 0, lhs)) { |
| 1455 | // LHS dies; use it as the destination. | 1474 | // LHS dies; use it as the destination. |
| 1456 | // Both operands cannot be memory. | 1475 | // Both operands cannot be memory. |
| 1457 | src_inst = op_rhs; | ||
| 1458 | if (lhs.isMemory() and rhs.isMemory()) { | 1476 | if (lhs.isMemory() and rhs.isMemory()) { |
| 1459 | dst_mcv = try self.copyToNewRegister(inst, lhs); | 1477 | dst_mcv = try self.copyToNewRegister(inst, lhs); |
| 1460 | src_mcv = rhs; | 1478 | src_mcv = rhs; |
| ... | @@ -1465,7 +1483,6 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: | ... | @@ -1465,7 +1483,6 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: |
| 1465 | } else if (self.reuseOperand(inst, op_rhs, 1, rhs)) { | 1483 | } else if (self.reuseOperand(inst, op_rhs, 1, rhs)) { |
| 1466 | // RHS dies; use it as the destination. | 1484 | // RHS dies; use it as the destination. |
| 1467 | // Both operands cannot be memory. | 1485 | // Both operands cannot be memory. |
| 1468 | src_inst = op_lhs; | ||
| 1469 | if (lhs.isMemory() and rhs.isMemory()) { | 1486 | if (lhs.isMemory() and rhs.isMemory()) { |
| 1470 | dst_mcv = try self.copyToNewRegister(inst, rhs); | 1487 | dst_mcv = try self.copyToNewRegister(inst, rhs); |
| 1471 | src_mcv = lhs; | 1488 | src_mcv = lhs; |
| ... | @@ -1475,13 +1492,23 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: | ... | @@ -1475,13 +1492,23 @@ fn genBinMathOp(self: *Self, inst: Air.Inst.Index, op_lhs: Air.Inst.Ref, op_rhs: |
| 1475 | } | 1492 | } |
| 1476 | } else { | 1493 | } else { |
| 1477 | if (lhs.isMemory()) { | 1494 | if (lhs.isMemory()) { |
| 1478 | 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); | ||
| 1479 | src_mcv = rhs; | 1502 | src_mcv = rhs; |
| 1480 | src_inst = op_rhs; | ||
| 1481 | } else { | 1503 | } else { |
| 1482 | 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); | ||
| 1483 | src_mcv = lhs; | 1511 | src_mcv = lhs; |
| 1484 | src_inst = op_lhs; | ||
| 1485 | } | 1512 | } |
| 1486 | } | 1513 | } |
| 1487 | // This instruction supports only signed 32-bit immediates at most. If the immediate | 1514 | // This instruction supports only signed 32-bit immediates at most. If the immediate |
src/arch/x86_64/bits.zig+14-10| ... | @@ -84,13 +84,11 @@ pub const Register = enum(u7) { | ... | @@ -84,13 +84,11 @@ pub const Register = enum(u7) { |
| 84 | /// Returns the index into `callee_preserved_regs`. | 84 | /// Returns the index into `callee_preserved_regs`. |
| 85 | pub fn allocIndex(self: Register) ?u4 { | 85 | pub fn allocIndex(self: Register) ?u4 { |
| 86 | return switch (self) { | 86 | return switch (self) { |
| 87 | .rcx, .ecx, .cx, .cl => 0, | 87 | .rbx, .ebx, .bx, .bl => 0, |
| 88 | .rsi, .esi, .si => 1, | 88 | .r12, .r12d, .r12w, .r12b => 1, |
| 89 | .rdi, .edi, .di => 2, | 89 | .r13, .r13d, .r13w, .r13b => 2, |
| 90 | .r8, .r8d, .r8w, .r8b => 3, | 90 | .r14, .r14d, .r14w, .r14b => 3, |
| 91 | .r9, .r9d, .r9w, .r9b => 4, | 91 | .r15, .r15d, .r15w, .r15b => 4, |
| 92 | .r10, .r10d, .r10w, .r10b => 5, | ||
| 93 | .r11, .r11d, .r11w, .r11b => 6, | ||
| 94 | else => null, | 92 | else => null, |
| 95 | }; | 93 | }; |
| 96 | } | 94 | } |
| ... | @@ -142,9 +140,15 @@ pub const Register = enum(u7) { | ... | @@ -142,9 +140,15 @@ pub const Register = enum(u7) { |
| 142 | 140 | ||
| 143 | // zig fmt: on | 141 | // zig fmt: on |
| 144 | 142 | ||
| 145 | /// These registers belong to the called function. | 143 | /// These registers need to be preserved (saved on the stack) and restored by the callee before getting clobbered |
| 146 | /// TODO should the return_regs be in this array? | 144 | /// and when the callee returns. |
| 147 | pub const callee_preserved_regs = [_]Register{ .rcx, .rsi, .rdi, .r8, .r9, .r10, .r11 }; | 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. | ||
| 147 | pub 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. | ||
| 151 | pub const caller_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 }; | ||
| 148 | pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; | 152 | pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; |
| 149 | pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx }; | 153 | pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx }; |
| 150 | 154 |
src/link/Elf.zig+2-2| ... | @@ -2604,12 +2604,12 @@ fn addDbgInfoType(self: *Elf, ty: Type, dbg_info_buffer: *std.ArrayList(u8)) !vo | ... | @@ -2604,12 +2604,12 @@ fn addDbgInfoType(self: *Elf, ty: Type, dbg_info_buffer: *std.ArrayList(u8)) !vo |
| 2604 | // DW.AT.name, DW.FORM.string | 2604 | // DW.AT.name, DW.FORM.string |
| 2605 | try dbg_info_buffer.writer().print("{}\x00", .{ty}); | 2605 | try dbg_info_buffer.writer().print("{}\x00", .{ty}); |
| 2606 | } else { | 2606 | } else { |
| 2607 | log.warn("TODO implement .debug_info for type '{}'", .{ty}); | 2607 | log.debug("TODO implement .debug_info for type '{}'", .{ty}); |
| 2608 | try dbg_info_buffer.append(abbrev_pad1); | 2608 | try dbg_info_buffer.append(abbrev_pad1); |
| 2609 | } | 2609 | } |
| 2610 | }, | 2610 | }, |
| 2611 | else => { | 2611 | else => { |
| 2612 | log.err("TODO implement .debug_info for type '{}'", .{ty}); | 2612 | log.debug("TODO implement .debug_info for type '{}'", .{ty}); |
| 2613 | try dbg_info_buffer.append(abbrev_pad1); | 2613 | try dbg_info_buffer.append(abbrev_pad1); |
| 2614 | }, | 2614 | }, |
| 2615 | } | 2615 | } |