| author | |
| committer | |
| log | 15d6efecfbe8be46e5dbd7765f0ef9726144db5d |
| tree | 52ae14da99142a28722fb11f83efcfd57ca81a9d |
| parent | 2299e5ff1dc924a2dca0c0c2def6f1e7b71a668f |
| parent | f2a33688913cd36b1aee0b27a4a1de2cfb1cc04e |
| signature |
stage2 ARM: Generate correct code when spilling registers3 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 | 928 | // TODO separate architectures with registers from |
| 929 | 929 | // stack-based architectures (spu_2) |
| 930 | 930 | if (callee_preserved_regs.len > 0) { |
| 931 | if (self.register_manager.tryAllocReg(inst)) |reg| { | |
| 931 | if (self.register_manager.tryAllocReg(inst, &.{})) |reg| { | |
| 932 | 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 | 940 | |
| 941 | 941 | pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: Register, inst: *ir.Inst) !void { |
| 942 | 942 | const stack_mcv = try self.allocRegOrMem(inst, false); |
| 943 | log.debug("spilling {*} to stack mcv {any}", .{ inst, stack_mcv }); | |
| 943 | 944 | const reg_mcv = self.getResolvedInstValue(inst); |
| 944 | 945 | assert(reg == toCanonicalReg(reg_mcv.register)); |
| 945 | 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 | 952 | /// allocated. A second call to `copyToTmpRegister` may return the same register. |
| 952 | 953 | /// This can have a side effect of spilling instructions to the stack to free up a register. |
| 953 | 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 | 956 | try self.genSetReg(src, ty, reg, mcv); |
| 956 | 957 | return reg; |
| 957 | 958 | } |
| ... | ... | @@ -960,7 +961,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 960 | 961 | /// `reg_owner` is the instruction that gets associated with the register in the register table. |
| 961 | 962 | /// This can have a side effect of spilling instructions to the stack to free up a register. |
| 962 | 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 | 965 | try self.genSetReg(reg_owner.src, reg_owner.ty, reg, mcv); |
| 965 | 966 | return MCValue{ .register = reg }; |
| 966 | 967 | } |
| ... | ... | @@ -1380,36 +1381,124 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1380 | 1381 | } |
| 1381 | 1382 | } |
| 1382 | 1383 | |
| 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 | 1408 | fn genArmBinOp(self: *Self, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, op: ir.Inst.Tag) !MCValue { |
| 1384 | 1409 | const lhs = try self.resolveInst(op_lhs); |
| 1385 | 1410 | const rhs = try self.resolveInst(op_rhs); |
| 1386 | 1411 | |
| 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 | 1419 | // Destination must be a register |
| 1388 | 1420 | 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; | |
| 1403 | 1443 | } 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 | |
| 1410 | 1484 | } |
| 1411 | 1485 | |
| 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 | 1502 | return dst_mcv; |
| 1414 | 1503 | } |
| 1415 | 1504 | |
| ... | ... | @@ -1419,11 +1508,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1419 | 1508 | dst_reg: Register, |
| 1420 | 1509 | lhs_mcv: MCValue, |
| 1421 | 1510 | rhs_mcv: MCValue, |
| 1511 | swap_lhs_and_rhs: bool, | |
| 1422 | 1512 | op: ir.Inst.Tag, |
| 1423 | 1513 | ) !void { |
| 1424 | assert(lhs_mcv == .register or lhs_mcv == .register); | |
| 1514 | assert(lhs_mcv == .register or rhs_mcv == .register); | |
| 1425 | 1515 | |
| 1426 | const swap_lhs_and_rhs = rhs_mcv == .register and lhs_mcv != .register; | |
| 1427 | 1516 | const op1 = if (swap_lhs_and_rhs) rhs_mcv.register else lhs_mcv.register; |
| 1428 | 1517 | const op2 = if (swap_lhs_and_rhs) lhs_mcv else rhs_mcv; |
| 1429 | 1518 | |
| ... | ... | @@ -1435,19 +1524,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1435 | 1524 | .compare_flags_signed => unreachable, |
| 1436 | 1525 | .ptr_stack_offset => unreachable, |
| 1437 | 1526 | .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)).?, | |
| 1446 | 1528 | .register => |reg| Instruction.Operand.reg(reg, Instruction.Operand.Shift.none), |
| 1447 | 1529 | .stack_offset, |
| 1448 | 1530 | .embedded_in_code, |
| 1449 | 1531 | .memory, |
| 1450 | => Instruction.Operand.reg(try self.copyToTmpRegister(src, Type.initTag(.u32), op2), Instruction.Operand.Shift.none), | |
| 1532 | => unreachable, | |
| 1451 | 1533 | }; |
| 1452 | 1534 | |
| 1453 | 1535 | switch (op) { |
| ... | ... | @@ -2613,10 +2695,42 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2613 | 2695 | const lhs = try self.resolveInst(inst.lhs); |
| 2614 | 2696 | const rhs = try self.resolveInst(inst.rhs); |
| 2615 | 2697 | |
| 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); | |
| 2618 | 2733 | |
| 2619 | try self.genArmBinOpCode(inst.base.src, dst_mcv.register, dst_mcv, src_mcv, .cmp_eq); | |
| 2620 | 2734 | const info = inst.lhs.ty.intInfo(self.target.*); |
| 2621 | 2735 | return switch (info.signedness) { |
| 2622 | 2736 | .signed => MCValue{ .compare_flags_signed = op }, |
src/register_manager.zig+56-27| ... | ... | @@ -1,5 +1,6 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const math = std.math; |
| 3 | const mem = std.mem; | |
| 3 | 4 | const assert = std.debug.assert; |
| 4 | 5 | const Allocator = std.mem.Allocator; |
| 5 | 6 | const ir = @import("ir.zig"); |
| ... | ... | @@ -66,8 +67,13 @@ pub fn RegisterManager( |
| 66 | 67 | } |
| 67 | 68 | |
| 68 | 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 | 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| { | |
| 71 | 77 | for (regs) |reg, i| { |
| 72 | 78 | const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null |
| 73 | 79 | self.registers[index] = insts[i]; |
| ... | ... | @@ -81,21 +87,30 @@ pub fn RegisterManager( |
| 81 | 87 | } |
| 82 | 88 | |
| 83 | 89 | /// 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; | |
| 86 | 92 | } |
| 87 | 93 | |
| 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 | 100 | comptime assert(count > 0 and count <= callee_preserved_regs.len); |
| 101 | assert(count + exceptions.len <= callee_preserved_regs.len); | |
| 90 | 102 | |
| 91 | return self.tryAllocRegs(count, insts) orelse blk: { | |
| 103 | return self.tryAllocRegs(count, insts, exceptions) orelse blk: { | |
| 92 | 104 | // We'll take over the first count registers. Spill |
| 93 | 105 | // the instructions that were previously there to a |
| 94 | 106 | // stack allocations. |
| 95 | 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; | |
| 97 | 113 | |
| 98 | for (regs) |reg, i| { | |
| 99 | 114 | const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null |
| 100 | 115 | if (self.isRegFree(reg)) { |
| 101 | 116 | self.markRegUsed(reg); |
| ... | ... | @@ -104,21 +119,28 @@ pub fn RegisterManager( |
| 104 | 119 | try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst); |
| 105 | 120 | } |
| 106 | 121 | self.registers[index] = insts[i]; |
| 122 | ||
| 123 | i += 1; | |
| 107 | 124 | } |
| 108 | 125 | |
| 109 | 126 | break :blk regs; |
| 110 | 127 | }; |
| 111 | 128 | } |
| 112 | 129 | |
| 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]; | |
| 115 | 132 | } |
| 116 | 133 | |
| 117 | 134 | /// Does not track the registers. |
| 118 | 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 | 141 | comptime if (callee_preserved_regs.len == 0) return null; |
| 121 | 142 | comptime assert(count > 0 and count <= callee_preserved_regs.len); |
| 143 | assert(count + exceptions.len <= callee_preserved_regs.len); | |
| 122 | 144 | |
| 123 | 145 | const free_registers = @popCount(FreeRegInt, self.free_registers); |
| 124 | 146 | if (free_registers < count) return null; |
| ... | ... | @@ -127,30 +149,35 @@ pub fn RegisterManager( |
| 127 | 149 | var i: usize = 0; |
| 128 | 150 | for (callee_preserved_regs) |reg| { |
| 129 | 151 | if (i >= count) break; |
| 152 | if (mem.indexOfScalar(Register, exceptions, reg) != null) continue; | |
| 130 | 153 | if (self.isRegFree(reg)) { |
| 131 | 154 | regs[i] = reg; |
| 132 | 155 | i += 1; |
| 133 | 156 | } |
| 134 | 157 | } |
| 135 | return regs; | |
| 158 | ||
| 159 | return if (i < count) null else regs; | |
| 136 | 160 | } |
| 137 | 161 | |
| 138 | 162 | /// Does not track the register. |
| 139 | 163 | /// 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; | |
| 142 | 166 | } |
| 143 | 167 | |
| 144 | 168 | /// 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: { | |
| 147 | 171 | // We'll take over the first count registers. Spill |
| 148 | 172 | // the instructions that were previously there to a |
| 149 | 173 | // stack allocations. |
| 150 | 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; | |
| 152 | 180 | |
| 153 | for (regs) |reg, i| { | |
| 154 | 181 | const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null |
| 155 | 182 | if (!self.isRegFree(reg)) { |
| 156 | 183 | const spilled_inst = self.registers[index].?; |
| ... | ... | @@ -158,6 +185,8 @@ pub fn RegisterManager( |
| 158 | 185 | self.registers[index] = null; |
| 159 | 186 | self.markRegFree(reg); |
| 160 | 187 | } |
| 188 | ||
| 189 | i += 1; | |
| 161 | 190 | } |
| 162 | 191 | |
| 163 | 192 | break :blk regs; |
| ... | ... | @@ -165,8 +194,8 @@ pub fn RegisterManager( |
| 165 | 194 | } |
| 166 | 195 | |
| 167 | 196 | /// 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]; | |
| 170 | 199 | } |
| 171 | 200 | |
| 172 | 201 | /// Allocates the specified register with the specified |
| ... | ... | @@ -270,9 +299,9 @@ test "tryAllocReg: no spilling" { |
| 270 | 299 | try std.testing.expect(!function.register_manager.isRegAllocated(.r2)); |
| 271 | 300 | try std.testing.expect(!function.register_manager.isRegAllocated(.r3)); |
| 272 | 301 | |
| 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, &.{})); | |
| 276 | 305 | |
| 277 | 306 | try std.testing.expect(function.register_manager.isRegAllocated(.r2)); |
| 278 | 307 | try std.testing.expect(function.register_manager.isRegAllocated(.r3)); |
| ... | ... | @@ -301,16 +330,16 @@ test "allocReg: spilling" { |
| 301 | 330 | try std.testing.expect(!function.register_manager.isRegAllocated(.r2)); |
| 302 | 331 | try std.testing.expect(!function.register_manager.isRegAllocated(.r3)); |
| 303 | 332 | |
| 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, &.{})); | |
| 306 | 335 | |
| 307 | 336 | // 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 | 338 | try std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items); |
| 310 | 339 | |
| 311 | 340 | // No spilling necessary |
| 312 | 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 | 343 | try std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items); |
| 315 | 344 | } |
| 316 | 345 |
test/stage2/arm.zig+53| ... | ... | @@ -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 | } |