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 {
928928 // TODO separate architectures with registers from
929929 // stack-based architectures (spu_2)
930930 if (callee_preserved_regs.len > 0) {
931 if (self.register_manager.tryAllocReg(inst)) |reg| {
931 if (self.register_manager.tryAllocReg(inst, &.{})) |reg| {
932932 return MCValue{ .register = registerAlias(reg, abi_size) };
933933 }
934934 }
......@@ -940,6 +940,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
940940
941941 pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: Register, inst: *ir.Inst) !void {
942942 const stack_mcv = try self.allocRegOrMem(inst, false);
943 log.debug("spilling {*} to stack mcv {any}", .{ inst, stack_mcv });
943944 const reg_mcv = self.getResolvedInstValue(inst);
944945 assert(reg == toCanonicalReg(reg_mcv.register));
945946 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 {
951952 /// allocated. A second call to `copyToTmpRegister` may return the same register.
952953 /// This can have a side effect of spilling instructions to the stack to free up a register.
953954 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(&.{});
955956 try self.genSetReg(src, ty, reg, mcv);
956957 return reg;
957958 }
......@@ -960,7 +961,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
960961 /// `reg_owner` is the instruction that gets associated with the register in the register table.
961962 /// This can have a side effect of spilling instructions to the stack to free up a register.
962963 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, &.{});
964965 try self.genSetReg(reg_owner.src, reg_owner.ty, reg, mcv);
965966 return MCValue{ .register = reg };
966967 }
......@@ -1380,36 +1381,124 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
13801381 }
13811382 }
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
13831408 fn genArmBinOp(self: *Self, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, op: ir.Inst.Tag) !MCValue {
13841409 const lhs = try self.resolveInst(op_lhs);
13851410 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
13871419 // Destination must be a register
13881420 var dst_mcv: MCValue = undefined;
1389 var lhs_mcv: MCValue = undefined;
1390 var rhs_mcv: MCValue = undefined;
1391 if (self.reuseOperand(inst, 0, lhs)) {
1392 // LHS is the destination
1393 // RHS is the source
1394 lhs_mcv = if (lhs != .register) try self.copyToNewRegister(inst, lhs) else lhs;
1395 rhs_mcv = rhs;
1396 dst_mcv = lhs_mcv;
1397 } else if (self.reuseOperand(inst, 1, rhs)) {
1398 // RHS is the destination
1399 // LHS is the source
1400 lhs_mcv = lhs;
1401 rhs_mcv = if (rhs != .register) try self.copyToNewRegister(inst, rhs) else rhs;
1402 dst_mcv = rhs_mcv;
1421 var lhs_mcv = lhs;
1422 var rhs_mcv = rhs;
1423 var swap_lhs_and_rhs = false;
1424
1425 // Allocate registers for operands and/or destination
1426 const branch = &self.branch_stack.items[self.branch_stack.items.len - 1];
1427 if (reuse_lhs) {
1428 // Allocate 0 or 1 registers
1429 if (!rhs_is_register and rhs_should_be_register) {
1430 rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(op_rhs, &.{lhs.register}) };
1431 branch.inst_table.putAssumeCapacity(op_rhs, rhs_mcv);
1432 }
1433 dst_mcv = lhs;
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;
14031443 } else {
1404 // TODO save 1 copy instruction by directly allocating the destination register
1405 // LHS is the destination
1406 // RHS is the source
1407 lhs_mcv = try self.copyToNewRegister(inst, lhs);
1408 rhs_mcv = rhs;
1409 dst_mcv = lhs_mcv;
1444 // Allocate 1 or 2 registers
1445 if (lhs_should_be_register and rhs_should_be_register) {
1446 if (lhs_is_register and rhs_is_register) {
1447 dst_mcv = MCValue{ .register = try self.register_manager.allocReg(inst, &.{ lhs.register, rhs.register }) };
1448 } else if (lhs_is_register) {
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
14101484 }
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 );
14131502 return dst_mcv;
14141503 }
14151504
......@@ -1419,11 +1508,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
14191508 dst_reg: Register,
14201509 lhs_mcv: MCValue,
14211510 rhs_mcv: MCValue,
1511 swap_lhs_and_rhs: bool,
14221512 op: ir.Inst.Tag,
14231513 ) !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;
14271516 const op1 = if (swap_lhs_and_rhs) rhs_mcv.register else lhs_mcv.register;
14281517 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 {
14351524 .compare_flags_signed => unreachable,
14361525 .ptr_stack_offset => unreachable,
14371526 .ptr_embedded_in_code => unreachable,
1438 .immediate => |imm| blk: {
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 },
1527 .immediate => |imm| Instruction.Operand.fromU32(@intCast(u32, imm)).?,
14461528 .register => |reg| Instruction.Operand.reg(reg, Instruction.Operand.Shift.none),
14471529 .stack_offset,
14481530 .embedded_in_code,
14491531 .memory,
1450 => Instruction.Operand.reg(try self.copyToTmpRegister(src, Type.initTag(.u32), op2), Instruction.Operand.Shift.none),
1532 => unreachable,
14511533 };
14521534
14531535 switch (op) {
......@@ -2613,10 +2695,42 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
26132695 const lhs = try self.resolveInst(inst.lhs);
26142696 const rhs = try self.resolveInst(inst.rhs);
26152697
2616 const src_mcv = rhs;
2617 const dst_mcv = if (lhs != .register) try self.copyToNewRegister(inst.lhs, lhs) else lhs;
2698 const lhs_is_register = lhs == .register;
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);
26202734 const info = inst.lhs.ty.intInfo(self.target.*);
26212735 return switch (info.signedness) {
26222736 .signed => MCValue{ .compare_flags_signed = op },
src/register_manager.zig+56-27
......@@ -1,5 +1,6 @@
11const std = @import("std");
22const math = std.math;
3const mem = std.mem;
34const assert = std.debug.assert;
45const Allocator = std.mem.Allocator;
56const ir = @import("ir.zig");
......@@ -66,8 +67,13 @@ pub fn RegisterManager(
6667 }
6768
6869 /// Returns `null` if all registers are allocated.
69 pub fn tryAllocRegs(self: *Self, comptime count: comptime_int, insts: [count]*ir.Inst) ?[count]Register {
70 if (self.tryAllocRegsWithoutTracking(count)) |regs| {
70 pub fn tryAllocRegs(
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| {
7177 for (regs) |reg, i| {
7278 const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null
7379 self.registers[index] = insts[i];
......@@ -81,21 +87,30 @@ pub fn RegisterManager(
8187 }
8288
8389 /// Returns `null` if all registers are allocated.
84 pub fn tryAllocReg(self: *Self, inst: *ir.Inst) ?Register {
85 return if (tryAllocRegs(self, 1, .{inst})) |regs| regs[0] else null;
90 pub fn tryAllocReg(self: *Self, inst: *ir.Inst, exceptions: []Register) ?Register {
91 return if (tryAllocRegs(self, 1, .{inst}, exceptions)) |regs| regs[0] else null;
8692 }
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 {
89100 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: {
92104 // We'll take over the first count registers. Spill
93105 // the instructions that were previously there to a
94106 // stack allocations.
95107 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| {
99114 const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null
100115 if (self.isRegFree(reg)) {
101116 self.markRegUsed(reg);
......@@ -104,21 +119,28 @@ pub fn RegisterManager(
104119 try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
105120 }
106121 self.registers[index] = insts[i];
122
123 i += 1;
107124 }
108125
109126 break :blk regs;
110127 };
111128 }
112129
113 pub fn allocReg(self: *Self, inst: *ir.Inst) !Register {
114 return (try self.allocRegs(1, .{inst}))[0];
130 pub fn allocReg(self: *Self, inst: *ir.Inst, exceptions: []Register) !Register {
131 return (try self.allocRegs(1, .{inst}, exceptions))[0];
115132 }
116133
117134 /// Does not track the registers.
118135 /// 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 {
120141 comptime if (callee_preserved_regs.len == 0) return null;
121142 comptime assert(count > 0 and count <= callee_preserved_regs.len);
143 assert(count + exceptions.len <= callee_preserved_regs.len);
122144
123145 const free_registers = @popCount(FreeRegInt, self.free_registers);
124146 if (free_registers < count) return null;
......@@ -127,30 +149,35 @@ pub fn RegisterManager(
127149 var i: usize = 0;
128150 for (callee_preserved_regs) |reg| {
129151 if (i >= count) break;
152 if (mem.indexOfScalar(Register, exceptions, reg) != null) continue;
130153 if (self.isRegFree(reg)) {
131154 regs[i] = reg;
132155 i += 1;
133156 }
134157 }
135 return regs;
158
159 return if (i < count) null else regs;
136160 }
137161
138162 /// Does not track the register.
139163 /// Returns `null` if all registers are allocated.
140 pub fn tryAllocRegWithoutTracking(self: *Self) ?Register {
141 return if (self.tryAllocRegsWithoutTracking(1)) |regs| regs[0] else null;
164 pub fn tryAllocRegWithoutTracking(self: *Self, exceptions: []Register) ?Register {
165 return if (self.tryAllocRegsWithoutTracking(1, exceptions)) |regs| regs[0] else null;
142166 }
143167
144168 /// Does not track the registers
145 pub fn allocRegsWithoutTracking(self: *Self, comptime count: comptime_int) ![count]Register {
146 return self.tryAllocRegsWithoutTracking(count) orelse blk: {
169 pub fn allocRegsWithoutTracking(self: *Self, comptime count: comptime_int, exceptions: []Register) ![count]Register {
170 return self.tryAllocRegsWithoutTracking(count, exceptions) orelse blk: {
147171 // We'll take over the first count registers. Spill
148172 // the instructions that were previously there to a
149173 // stack allocations.
150174 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| {
154181 const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null
155182 if (!self.isRegFree(reg)) {
156183 const spilled_inst = self.registers[index].?;
......@@ -158,6 +185,8 @@ pub fn RegisterManager(
158185 self.registers[index] = null;
159186 self.markRegFree(reg);
160187 }
188
189 i += 1;
161190 }
162191
163192 break :blk regs;
......@@ -165,8 +194,8 @@ pub fn RegisterManager(
165194 }
166195
167196 /// Does not track the register.
168 pub fn allocRegWithoutTracking(self: *Self) !Register {
169 return (try self.allocRegsWithoutTracking(1))[0];
197 pub fn allocRegWithoutTracking(self: *Self, exceptions: []Register) !Register {
198 return (try self.allocRegsWithoutTracking(1, exceptions))[0];
170199 }
171200
172201 /// Allocates the specified register with the specified
......@@ -270,9 +299,9 @@ test "tryAllocReg: no spilling" {
270299 try std.testing.expect(!function.register_manager.isRegAllocated(.r2));
271300 try std.testing.expect(!function.register_manager.isRegAllocated(.r3));
272301
273 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));
275 try std.testing.expectEqual(@as(?MockRegister, null), function.register_manager.tryAllocReg(&mock_instruction));
302 try std.testing.expectEqual(@as(?MockRegister, .r2), function.register_manager.tryAllocReg(&mock_instruction, &.{}));
303 try std.testing.expectEqual(@as(?MockRegister, .r3), function.register_manager.tryAllocReg(&mock_instruction, &.{}));
304 try std.testing.expectEqual(@as(?MockRegister, null), function.register_manager.tryAllocReg(&mock_instruction, &.{}));
276305
277306 try std.testing.expect(function.register_manager.isRegAllocated(.r2));
278307 try std.testing.expect(function.register_manager.isRegAllocated(.r3));
......@@ -301,16 +330,16 @@ test "allocReg: spilling" {
301330 try std.testing.expect(!function.register_manager.isRegAllocated(.r2));
302331 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));
305 try std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction));
333 try std.testing.expectEqual(@as(?MockRegister, .r2), try function.register_manager.allocReg(&mock_instruction, &.{}));
334 try std.testing.expectEqual(@as(?MockRegister, .r3), try function.register_manager.allocReg(&mock_instruction, &.{}));
306335
307336 // 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, &.{}));
309338 try std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);
310339
311340 // No spilling necessary
312341 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, &.{}));
314343 try std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items);
315344}
316345
test/stage2/arm.zig+53
......@@ -458,4 +458,57 @@ pub fn addCases(ctx: *TestContext) !void {
458458 "",
459459 );
460460 }
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 }
461514}