authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-05-09 14:16:40+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-05-09 14:16:40+02:00
log15d6efecfbe8be46e5dbd7765f0ef9726144db5d
tree52ae14da99142a28722fb11f83efcfd57ca81a9d
parent2299e5ff1dc924a2dca0c0c2def6f1e7b71a668f
parentf2a33688913cd36b1aee0b27a4a1de2cfb1cc04e
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8709 from joachimschmidt557/stage2-arm

stage2 ARM: Generate correct code when spilling registers

3 files changed, 261 insertions(+), 65 deletions(-)

src/codegen.zig+152-38
...@@ -928,7 +928,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -928,7 +928,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
928 // TODO separate architectures with registers from928 // TODO separate architectures with registers from
929 // stack-based architectures (spu_2)929 // stack-based architectures (spu_2)
930 if (callee_preserved_regs.len > 0) {930 if (callee_preserved_regs.len > 0) {
931 if (self.register_manager.tryAllocReg(inst)) |reg| {931 if (self.register_manager.tryAllocReg(inst, &.{})) |reg| {
932 return MCValue{ .register = registerAlias(reg, abi_size) };932 return MCValue{ .register = registerAlias(reg, abi_size) };
933 }933 }
934 }934 }
...@@ -940,6 +940,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -940,6 +940,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
940940
941 pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: Register, inst: *ir.Inst) !void {941 pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: Register, inst: *ir.Inst) !void {
942 const stack_mcv = try self.allocRegOrMem(inst, false);942 const stack_mcv = try self.allocRegOrMem(inst, false);
943 log.debug("spilling {*} to stack mcv {any}", .{ inst, stack_mcv });
943 const reg_mcv = self.getResolvedInstValue(inst);944 const reg_mcv = self.getResolvedInstValue(inst);
944 assert(reg == toCanonicalReg(reg_mcv.register));945 assert(reg == toCanonicalReg(reg_mcv.register));
945 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];946 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
...@@ -951,7 +952,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -951,7 +952,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
951 /// allocated. A second call to `copyToTmpRegister` may return the same register.952 /// allocated. A second call to `copyToTmpRegister` may return the same register.
952 /// This can have a side effect of spilling instructions to the stack to free up a register.953 /// This can have a side effect of spilling instructions to the stack to free up a register.
953 fn copyToTmpRegister(self: *Self, src: LazySrcLoc, ty: Type, mcv: MCValue) !Register {954 fn copyToTmpRegister(self: *Self, src: LazySrcLoc, ty: Type, mcv: MCValue) !Register {
954 const reg = try self.register_manager.allocRegWithoutTracking();955 const reg = try self.register_manager.allocRegWithoutTracking(&.{});
955 try self.genSetReg(src, ty, reg, mcv);956 try self.genSetReg(src, ty, reg, mcv);
956 return reg;957 return reg;
957 }958 }
...@@ -960,7 +961,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -960,7 +961,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
960 /// `reg_owner` is the instruction that gets associated with the register in the register table.961 /// `reg_owner` is the instruction that gets associated with the register in the register table.
961 /// This can have a side effect of spilling instructions to the stack to free up a register.962 /// This can have a side effect of spilling instructions to the stack to free up a register.
962 fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue {963 fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue {
963 const reg = try self.register_manager.allocReg(reg_owner);964 const reg = try self.register_manager.allocReg(reg_owner, &.{});
964 try self.genSetReg(reg_owner.src, reg_owner.ty, reg, mcv);965 try self.genSetReg(reg_owner.src, reg_owner.ty, reg, mcv);
965 return MCValue{ .register = reg };966 return MCValue{ .register = reg };
966 }967 }
...@@ -1380,36 +1381,124 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1380,36 +1381,124 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1380 }1381 }
1381 }1382 }
13821383
1384 fn armOperandShouldBeRegister(self: *Self, src: LazySrcLoc, mcv: MCValue) !bool {
1385 return switch (mcv) {
1386 .none => unreachable,
1387 .undef => unreachable,
1388 .dead, .unreach => unreachable,
1389 .compare_flags_unsigned => unreachable,
1390 .compare_flags_signed => unreachable,
1391 .ptr_stack_offset => unreachable,
1392 .ptr_embedded_in_code => unreachable,
1393 .immediate => |imm| blk: {
1394 if (imm > std.math.maxInt(u32)) return self.fail(src, "TODO ARM binary arithmetic immediate larger than u32", .{});
1395
1396 // Load immediate into register if it doesn't fit
1397 // in an operand
1398 break :blk Instruction.Operand.fromU32(@intCast(u32, imm)) == null;
1399 },
1400 .register => true,
1401 .stack_offset,
1402 .embedded_in_code,
1403 .memory,
1404 => true,
1405 };
1406 }
1407
1383 fn genArmBinOp(self: *Self, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, op: ir.Inst.Tag) !MCValue {1408 fn genArmBinOp(self: *Self, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, op: ir.Inst.Tag) !MCValue {
1384 const lhs = try self.resolveInst(op_lhs);1409 const lhs = try self.resolveInst(op_lhs);
1385 const rhs = try self.resolveInst(op_rhs);1410 const rhs = try self.resolveInst(op_rhs);
13861411
1412 const lhs_is_register = lhs == .register;
1413 const rhs_is_register = rhs == .register;
1414 const lhs_should_be_register = try self.armOperandShouldBeRegister(op_lhs.src, lhs);
1415 const rhs_should_be_register = try self.armOperandShouldBeRegister(op_rhs.src, rhs);
1416 const reuse_lhs = lhs_is_register and self.reuseOperand(inst, 0, lhs);
1417 const reuse_rhs = !reuse_lhs and rhs_is_register and self.reuseOperand(inst, 1, rhs);
1418
1387 // Destination must be a register1419 // Destination must be a register
1388 var dst_mcv: MCValue = undefined;1420 var dst_mcv: MCValue = undefined;
1389 var lhs_mcv: MCValue = undefined;1421 var lhs_mcv = lhs;
1390 var rhs_mcv: MCValue = undefined;1422 var rhs_mcv = rhs;
1391 if (self.reuseOperand(inst, 0, lhs)) {1423 var swap_lhs_and_rhs = false;
1392 // LHS is the destination1424
1393 // RHS is the source1425 // Allocate registers for operands and/or destination
1394 lhs_mcv = if (lhs != .register) try self.copyToNewRegister(inst, lhs) else lhs;1426 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
1395 rhs_mcv = rhs;1427 if (reuse_lhs) {
1396 dst_mcv = lhs_mcv;1428 // Allocate 0 or 1 registers
1397 } else if (self.reuseOperand(inst, 1, rhs)) {1429 if (!rhs_is_register and rhs_should_be_register) {
1398 // RHS is the destination1430 rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(op_rhs, &.{lhs.register}) };
1399 // LHS is the source1431 branch.inst_table.putAssumeCapacity(op_rhs, rhs_mcv);
1400 lhs_mcv = lhs;1432 }
1401 rhs_mcv = if (rhs != .register) try self.copyToNewRegister(inst, rhs) else rhs;1433 dst_mcv = lhs;
1402 dst_mcv = rhs_mcv;1434 } else if (reuse_rhs) {
1435 // Allocate 0 or 1 registers
1436 if (!lhs_is_register and lhs_should_be_register) {
1437 lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(op_lhs, &.{rhs.register}) };
1438 branch.inst_table.putAssumeCapacity(op_lhs, lhs_mcv);
1439 }
1440 dst_mcv = rhs;
1441
1442 swap_lhs_and_rhs = true;
1403 } else {1443 } else {
1404 // TODO save 1 copy instruction by directly allocating the destination register1444 // Allocate 1 or 2 registers
1405 // LHS is the destination1445 if (lhs_should_be_register and rhs_should_be_register) {
1406 // RHS is the source1446 if (lhs_is_register and rhs_is_register) {
1407 lhs_mcv = try self.copyToNewRegister(inst, lhs);1447 dst_mcv = MCValue{ .register = try self.register_manager.allocReg(inst, &.{ lhs.register, rhs.register }) };
1408 rhs_mcv = rhs;1448 } else if (lhs_is_register) {
1409 dst_mcv = lhs_mcv;1449 // Move RHS to register
1450 dst_mcv = MCValue{ .register = try self.register_manager.allocReg(inst, &.{lhs.register}) };
1451 rhs_mcv = dst_mcv;
1452 } else if (rhs_is_register) {
1453 // Move LHS to register
1454 dst_mcv = MCValue{ .register = try self.register_manager.allocReg(inst, &.{rhs.register}) };
1455 lhs_mcv = dst_mcv;
1456 } else {
1457 // Move LHS and RHS to register
1458 const regs = try self.register_manager.allocRegs(2, .{ inst, op_rhs }, &.{});
1459 lhs_mcv = MCValue{ .register = regs[0] };
1460 rhs_mcv = MCValue{ .register = regs[1] };
1461 dst_mcv = lhs_mcv;
1462
1463 branch.inst_table.putAssumeCapacity(op_rhs, rhs_mcv);
1464 }
1465 } else if (lhs_should_be_register) {
1466 // RHS is immediate
1467 if (lhs_is_register) {
1468 dst_mcv = MCValue{ .register = try self.register_manager.allocReg(inst, &.{lhs.register}) };
1469 } else {
1470 dst_mcv = MCValue{ .register = try self.register_manager.allocReg(inst, &.{}) };
1471 lhs_mcv = dst_mcv;
1472 }
1473 } else if (rhs_should_be_register) {
1474 // LHS is immediate
1475 if (rhs_is_register) {
1476 dst_mcv = MCValue{ .register = try self.register_manager.allocReg(inst, &.{rhs.register}) };
1477 } else {
1478 dst_mcv = MCValue{ .register = try self.register_manager.allocReg(inst, &.{}) };
1479 rhs_mcv = dst_mcv;
1480 }
1481
1482 swap_lhs_and_rhs = true;
1483 } else unreachable; // binary operation on two immediates
1410 }1484 }
14111485
1412 try self.genArmBinOpCode(inst.src, dst_mcv.register, lhs_mcv, rhs_mcv, op);1486 // Move the operands to the newly allocated registers
1487 if (lhs_mcv == .register and !lhs_is_register) {
1488 try self.genSetReg(op_lhs.src, op_lhs.ty, lhs_mcv.register, lhs);
1489 }
1490 if (rhs_mcv == .register and !rhs_is_register) {
1491 try self.genSetReg(op_rhs.src, op_rhs.ty, rhs_mcv.register, rhs);
1492 }
1493
1494 try self.genArmBinOpCode(
1495 inst.src,
1496 dst_mcv.register,
1497 lhs_mcv,
1498 rhs_mcv,
1499 swap_lhs_and_rhs,
1500 op,
1501 );
1413 return dst_mcv;1502 return dst_mcv;
1414 }1503 }
14151504
...@@ -1419,11 +1508,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1419,11 +1508,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1419 dst_reg: Register,1508 dst_reg: Register,
1420 lhs_mcv: MCValue,1509 lhs_mcv: MCValue,
1421 rhs_mcv: MCValue,1510 rhs_mcv: MCValue,
1511 swap_lhs_and_rhs: bool,
1422 op: ir.Inst.Tag,1512 op: ir.Inst.Tag,
1423 ) !void {1513 ) !void {
1424 assert(lhs_mcv == .register or lhs_mcv == .register);1514 assert(lhs_mcv == .register or rhs_mcv == .register);
14251515
1426 const swap_lhs_and_rhs = rhs_mcv == .register and lhs_mcv != .register;
1427 const op1 = if (swap_lhs_and_rhs) rhs_mcv.register else lhs_mcv.register;1516 const op1 = if (swap_lhs_and_rhs) rhs_mcv.register else lhs_mcv.register;
1428 const op2 = if (swap_lhs_and_rhs) lhs_mcv else rhs_mcv;1517 const op2 = if (swap_lhs_and_rhs) lhs_mcv else rhs_mcv;
14291518
...@@ -1435,19 +1524,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -1435,19 +1524,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
1435 .compare_flags_signed => unreachable,1524 .compare_flags_signed => unreachable,
1436 .ptr_stack_offset => unreachable,1525 .ptr_stack_offset => unreachable,
1437 .ptr_embedded_in_code => unreachable,1526 .ptr_embedded_in_code => unreachable,
1438 .immediate => |imm| blk: {1527 .immediate => |imm| Instruction.Operand.fromU32(@intCast(u32, imm)).?,
1439 if (imm > std.math.maxInt(u32)) return self.fail(src, "TODO ARM binary arithmetic immediate larger than u32", .{});
1440
1441 // Load immediate into register if it doesn't fit
1442 // as an operand
1443 break :blk Instruction.Operand.fromU32(@intCast(u32, imm)) orelse
1444 Instruction.Operand.reg(try self.copyToTmpRegister(src, Type.initTag(.u32), op2), Instruction.Operand.Shift.none);
1445 },
1446 .register => |reg| Instruction.Operand.reg(reg, Instruction.Operand.Shift.none),1528 .register => |reg| Instruction.Operand.reg(reg, Instruction.Operand.Shift.none),
1447 .stack_offset,1529 .stack_offset,
1448 .embedded_in_code,1530 .embedded_in_code,
1449 .memory,1531 .memory,
1450 => Instruction.Operand.reg(try self.copyToTmpRegister(src, Type.initTag(.u32), op2), Instruction.Operand.Shift.none),1532 => unreachable,
1451 };1533 };
14521534
1453 switch (op) {1535 switch (op) {
...@@ -2613,10 +2695,42 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {...@@ -2613,10 +2695,42 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
2613 const lhs = try self.resolveInst(inst.lhs);2695 const lhs = try self.resolveInst(inst.lhs);
2614 const rhs = try self.resolveInst(inst.rhs);2696 const rhs = try self.resolveInst(inst.rhs);
26152697
2616 const src_mcv = rhs;2698 const lhs_is_register = lhs == .register;
2617 const dst_mcv = if (lhs != .register) try self.copyToNewRegister(inst.lhs, lhs) else lhs;2699 const rhs_is_register = rhs == .register;
2700 // lhs should always be a register
2701 const rhs_should_be_register = try self.armOperandShouldBeRegister(inst.rhs.src, rhs);
2702
2703 var lhs_mcv = lhs;
2704 var rhs_mcv = rhs;
2705
2706 // Allocate registers
2707 if (rhs_should_be_register) {
2708 if (!lhs_is_register and !rhs_is_register) {
2709 const regs = try self.register_manager.allocRegs(2, .{ inst.rhs, inst.lhs }, &.{});
2710 lhs_mcv = MCValue{ .register = regs[0] };
2711 rhs_mcv = MCValue{ .register = regs[1] };
2712 } else if (!rhs_is_register) {
2713 rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(inst.rhs, &.{}) };
2714 }
2715 }
2716 if (!lhs_is_register) {
2717 lhs_mcv = MCValue{ .register = try self.register_manager.allocReg(inst.lhs, &.{}) };
2718 }
2719
2720 // Move the operands to the newly allocated registers
2721 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
2722 if (lhs_mcv == .register and !lhs_is_register) {
2723 try self.genSetReg(inst.lhs.src, inst.lhs.ty, lhs_mcv.register, lhs);
2724 branch.inst_table.putAssumeCapacity(inst.lhs, lhs);
2725 }
2726 if (rhs_mcv == .register and !rhs_is_register) {
2727 try self.genSetReg(inst.rhs.src, inst.rhs.ty, rhs_mcv.register, rhs);
2728 branch.inst_table.putAssumeCapacity(inst.rhs, rhs);
2729 }
2730
2731 // The destination register is not present in the cmp instruction
2732 try self.genArmBinOpCode(inst.base.src, undefined, lhs_mcv, rhs_mcv, false, .cmp_eq);
26182733
2619 try self.genArmBinOpCode(inst.base.src, dst_mcv.register, dst_mcv, src_mcv, .cmp_eq);
2620 const info = inst.lhs.ty.intInfo(self.target.*);2734 const info = inst.lhs.ty.intInfo(self.target.*);
2621 return switch (info.signedness) {2735 return switch (info.signedness) {
2622 .signed => MCValue{ .compare_flags_signed = op },2736 .signed => MCValue{ .compare_flags_signed = op },
src/register_manager.zig+56-27
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1const std = @import("std");1const std = @import("std");
2const math = std.math;2const math = std.math;
3const mem = std.mem;
3const assert = std.debug.assert;4const assert = std.debug.assert;
4const Allocator = std.mem.Allocator;5const Allocator = std.mem.Allocator;
5const ir = @import("ir.zig");6const ir = @import("ir.zig");
...@@ -66,8 +67,13 @@ pub fn RegisterManager(...@@ -66,8 +67,13 @@ pub fn RegisterManager(
66 }67 }
6768
68 /// Returns `null` if all registers are allocated.69 /// Returns `null` if all registers are allocated.
69 pub fn tryAllocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ?[count]Register {70 pub fn tryAllocRegs(
70 if (self.tryAllocRegsWithoutTracking(count)) |regs| {71 self: *Self,
72 comptime count: comptime_int,
73 insts: [count]*ir.Inst,
74 exceptions: []Register,
75 ) ?[count]Register {
76 if (self.tryAllocRegsWithoutTracking(count, exceptions)) |regs| {
71 for (regs) |reg, i| {77 for (regs) |reg, i| {
72 const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null78 const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null
73 self.registers[index] = insts[i];79 self.registers[index] = insts[i];
...@@ -81,21 +87,30 @@ pub fn RegisterManager(...@@ -81,21 +87,30 @@ pub fn RegisterManager(
81 }87 }
8288
83 /// Returns `null` if all registers are allocated.89 /// Returns `null` if all registers are allocated.
84 pub fn tryAllocReg(self: *Self, inst: *ir.Inst) ?Register {90 pub fn tryAllocReg(self: *Self, inst: *ir.Inst, exceptions: []Register) ?Register {
85 return if (tryAllocRegs(self, 1, .{inst})) |regs| regs[0] else null;91 return if (tryAllocRegs(self, 1, .{inst}, exceptions)) |regs| regs[0] else null;
86 }92 }
8793
88 pub fn allocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ![count]Register {94 pub fn allocRegs(
95 self: *Self,
96 comptime count: comptime_int,
97 insts: [count]*ir.Inst,
98 exceptions: []Register,
99 ) ![count]Register {
89 comptime assert(count > 0 and count <= callee_preserved_regs.len);100 comptime assert(count > 0 and count <= callee_preserved_regs.len);
101 assert(count + exceptions.len <= callee_preserved_regs.len);
90102
91 return self.tryAllocRegs(count, insts) orelse blk: {103 return self.tryAllocRegs(count, insts, exceptions) orelse blk: {
92 // We'll take over the first count registers. Spill104 // We'll take over the first count registers. Spill
93 // the instructions that were previously there to a105 // the instructions that were previously there to a
94 // stack allocations.106 // stack allocations.
95 var regs: [count]Register = undefined;107 var regs: [count]Register = undefined;
96 std.mem.copy(Register, &regs, callee_preserved_regs[0..count]);108 var i: usize = 0;
109 for (callee_preserved_regs) |reg| {
110 if (i >= count) break;
111 if (mem.indexOfScalar(Register, exceptions, reg) != null) continue;
112 regs[i] = reg;
97113
98 for (regs) |reg, i| {
99 const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null114 const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null
100 if (self.isRegFree(reg)) {115 if (self.isRegFree(reg)) {
101 self.markRegUsed(reg);116 self.markRegUsed(reg);
...@@ -104,21 +119,28 @@ pub fn RegisterManager(...@@ -104,21 +119,28 @@ pub fn RegisterManager(
104 try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);119 try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
105 }120 }
106 self.registers[index] = insts[i];121 self.registers[index] = insts[i];
122
123 i += 1;
107 }124 }
108125
109 break :blk regs;126 break :blk regs;
110 };127 };
111 }128 }
112129
113 pub fn allocReg(self: *Self, inst: *ir.Inst) !Register {130 pub fn allocReg(self: *Self, inst: *ir.Inst, exceptions: []Register) !Register {
114 return (try self.allocRegs(1, .{inst}))[0];131 return (try self.allocRegs(1, .{inst}, exceptions))[0];
115 }132 }
116133
117 /// Does not track the registers.134 /// Does not track the registers.
118 /// Returns `null` if not enough registers are free.135 /// Returns `null` if not enough registers are free.
119 pub fn tryAllocRegsWithoutTracking(self: *Self, comptime count: comptime_int) ?[count]Register {136 pub fn tryAllocRegsWithoutTracking(
137 self: *Self,
138 comptime count: comptime_int,
139 exceptions: []Register,
140 ) ?[count]Register {
120 comptime if (callee_preserved_regs.len == 0) return null;141 comptime if (callee_preserved_regs.len == 0) return null;
121 comptime assert(count > 0 and count <= callee_preserved_regs.len);142 comptime assert(count > 0 and count <= callee_preserved_regs.len);
143 assert(count + exceptions.len <= callee_preserved_regs.len);
122144
123 const free_registers = @popCount(FreeRegInt, self.free_registers);145 const free_registers = @popCount(FreeRegInt, self.free_registers);
124 if (free_registers < count) return null;146 if (free_registers < count) return null;
...@@ -127,30 +149,35 @@ pub fn RegisterManager(...@@ -127,30 +149,35 @@ pub fn RegisterManager(
127 var i: usize = 0;149 var i: usize = 0;
128 for (callee_preserved_regs) |reg| {150 for (callee_preserved_regs) |reg| {
129 if (i >= count) break;151 if (i >= count) break;
152 if (mem.indexOfScalar(Register, exceptions, reg) != null) continue;
130 if (self.isRegFree(reg)) {153 if (self.isRegFree(reg)) {
131 regs[i] = reg;154 regs[i] = reg;
132 i += 1;155 i += 1;
133 }156 }
134 }157 }
135 return regs;158
159 return if (i < count) null else regs;
136 }160 }
137161
138 /// Does not track the register.162 /// Does not track the register.
139 /// Returns `null` if all registers are allocated.163 /// Returns `null` if all registers are allocated.
140 pub fn tryAllocRegWithoutTracking(self: *Self) ?Register {164 pub fn tryAllocRegWithoutTracking(self: *Self, exceptions: []Register) ?Register {
141 return if (self.tryAllocRegsWithoutTracking(1)) |regs| regs[0] else null;165 return if (self.tryAllocRegsWithoutTracking(1, exceptions)) |regs| regs[0] else null;
142 }166 }
143167
144 /// Does not track the registers168 /// Does not track the registers
145 pub fn allocRegsWithoutTracking(self: *Self, comptime count: comptime_int) ![count]Register {169 pub fn allocRegsWithoutTracking(self: *Self, comptime count: comptime_int, exceptions: []Register) ![count]Register {
146 return self.tryAllocRegsWithoutTracking(count) orelse blk: {170 return self.tryAllocRegsWithoutTracking(count, exceptions) orelse blk: {
147 // We'll take over the first count registers. Spill171 // We'll take over the first count registers. Spill
148 // the instructions that were previously there to a172 // the instructions that were previously there to a
149 // stack allocations.173 // stack allocations.
150 var regs: [count]Register = undefined;174 var regs: [count]Register = undefined;
151 std.mem.copy(Register, &regs, callee_preserved_regs[0..count]);175 var i: usize = 0;
176 for (callee_preserved_regs) |reg| {
177 if (i >= count) break;
178 if (mem.indexOfScalar(Register, exceptions, reg) != null) continue;
179 regs[i] = reg;
152180
153 for (regs) |reg, i| {
154 const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null181 const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null
155 if (!self.isRegFree(reg)) {182 if (!self.isRegFree(reg)) {
156 const spilled_inst = self.registers[index].?;183 const spilled_inst = self.registers[index].?;
...@@ -158,6 +185,8 @@ pub fn RegisterManager(...@@ -158,6 +185,8 @@ pub fn RegisterManager(
158 self.registers[index] = null;185 self.registers[index] = null;
159 self.markRegFree(reg);186 self.markRegFree(reg);
160 }187 }
188
189 i += 1;
161 }190 }
162191
163 break :blk regs;192 break :blk regs;
...@@ -165,8 +194,8 @@ pub fn RegisterManager(...@@ -165,8 +194,8 @@ pub fn RegisterManager(
165 }194 }
166195
167 /// Does not track the register.196 /// Does not track the register.
168 pub fn allocRegWithoutTracking(self: *Self) !Register {197 pub fn allocRegWithoutTracking(self: *Self, exceptions: []Register) !Register {
169 return (try self.allocRegsWithoutTracking(1))[0];198 return (try self.allocRegsWithoutTracking(1, exceptions))[0];
170 }199 }
171200
172 /// Allocates the specified register with the specified201 /// Allocates the specified register with the specified
...@@ -270,9 +299,9 @@ test "tryAllocReg: no spilling" {...@@ -270,9 +299,9 @@ test "tryAllocReg: no spilling" {
270 try std.testing.expect(!function.register_manager.isRegAllocated(.r2));299 try std.testing.expect(!function.register_manager.isRegAllocated(.r2));
271 try std.testing.expect(!function.register_manager.isRegAllocated(.r3));300 try std.testing.expect(!function.register_manager.isRegAllocated(.r3));
272301
273 try std.testing.expectEqual(@as(?MockRegister, .r2), function.register_manager.tryAllocReg(&mock_instruction));302 try std.testing.expectEqual(@as(?MockRegister, .r2), function.register_manager.tryAllocReg(&mock_instruction, &.{}));
274 try std.testing.expectEqual(@as(?MockRegister, .r3), function.register_manager.tryAllocReg(&mock_instruction));303 try std.testing.expectEqual(@as(?MockRegister, .r3), function.register_manager.tryAllocReg(&mock_instruction, &.{}));
275 try std.testing.expectEqual(@as(?MockRegister, null), function.register_manager.tryAllocReg(&mock_instruction));304 try std.testing.expectEqual(@as(?MockRegister, null), function.register_manager.tryAllocReg(&mock_instruction, &.{}));
276305
277 try std.testing.expect(function.register_manager.isRegAllocated(.r2));306 try std.testing.expect(function.register_manager.isRegAllocated(.r2));
278 try std.testing.expect(function.register_manager.isRegAllocated(.r3));307 try std.testing.expect(function.register_manager.isRegAllocated(.r3));
...@@ -301,16 +330,16 @@ test "allocReg: spilling" {...@@ -301,16 +330,16 @@ test "allocReg: spilling" {
301 try std.testing.expect(!function.register_manager.isRegAllocated(.r2));330 try std.testing.expect(!function.register_manager.isRegAllocated(.r2));
302 try std.testing.expect(!function.register_manager.isRegAllocated(.r3));331 try std.testing.expect(!function.register_manager.isRegAllocated(.r3));
303332
304 try std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction));333 try std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction, &.{}));
305 try std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction));334 try std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction, &.{}));
306335
307 // Spill a register336 // Spill a register
308 try std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction));337 try std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction, &.{}));
309 try std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);338 try std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);
310339
311 // No spilling necessary340 // No spilling necessary
312 function.register_manager.freeReg(.r3);341 function.register_manager.freeReg(.r3);
313 try std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction));342 try std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction, &.{}));
314 try std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);343 try std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);
315}344}
316345
test/stage2/arm.zig+53
...@@ -458,4 +458,57 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -458,4 +458,57 @@ pub fn addCases(ctx: *TestContext) !void {
458 "",458 "",
459 );459 );
460 }460 }
461
462 {
463 var case = ctx.exe("spilling registers", linux_arm);
464 case.addCompareOutput(
465 \\export fn _start() noreturn {
466 \\ assert(add(3, 4) == 791);
467 \\ exit();
468 \\}
469 \\
470 \\fn add(a: u32, b: u32) u32 {
471 \\ const x: u32 = blk: {
472 \\ const c = a + b; // 7
473 \\ const d = a + c; // 10
474 \\ const e = d + b; // 14
475 \\ const f = d + e; // 24
476 \\ const g = e + f; // 38
477 \\ const h = f + g; // 62
478 \\ const i = g + h; // 100
479 \\ const j = i + d; // 110
480 \\ const k = i + j; // 210
481 \\ const l = k + c; // 217
482 \\ const m = l + d; // 227
483 \\ const n = m + e; // 241
484 \\ const o = n + f; // 265
485 \\ const p = o + g; // 303
486 \\ const q = p + h; // 365
487 \\ const r = q + i; // 465
488 \\ const s = r + j; // 575
489 \\ const t = s + k; // 785
490 \\ break :blk t;
491 \\ };
492 \\ const y = x + a; // 788
493 \\ const z = y + a; // 791
494 \\ return z;
495 \\}
496 \\
497 \\fn assert(ok: bool) void {
498 \\ if (!ok) unreachable;
499 \\}
500 \\
501 \\fn exit() noreturn {
502 \\ asm volatile ("svc #0"
503 \\ :
504 \\ : [number] "{r7}" (1),
505 \\ [arg1] "{r0}" (0)
506 \\ : "memory"
507 \\ );
508 \\ unreachable;
509 \\}
510 ,
511 "",
512 );
513 }
461}514}