| 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,7 +928,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 928 | // TODO separate architectures with registers from | 928 | // 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 { |
| 940 | 940 | ||
| 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 | } |
| 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 | 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); |
| 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 | // Destination must be a register | 1419 | // 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 destination | 1424 | |
| 1393 | // RHS is the source | 1425 | // 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 destination | 1430 | rhs_mcv = MCValue{ .register = try self.register_manager.allocReg(op_rhs, &.{lhs.register}) }; |
| 1399 | // LHS is the source | 1431 | 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 register | 1444 | // Allocate 1 or 2 registers |
| 1405 | // LHS is the destination | 1445 | if (lhs_should_be_register and rhs_should_be_register) { |
| 1406 | // RHS is the source | 1446 | 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 | } |
| 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 | return dst_mcv; | 1502 | return dst_mcv; |
| 1414 | } | 1503 | } |
| 1415 | 1504 | ||
| ... | @@ -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); |
| 1425 | 1515 | ||
| 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; |
| 1429 | 1518 | ||
| ... | @@ -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 | }; |
| 1452 | 1534 | ||
| 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); |
| 2615 | 2697 | ||
| 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); | ||
| 2618 | 2733 | ||
| 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 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const math = std.math; | 2 | const math = std.math; |
| 3 | const mem = std.mem; | ||
| 3 | const assert = std.debug.assert; | 4 | const assert = std.debug.assert; |
| 4 | const Allocator = std.mem.Allocator; | 5 | const Allocator = std.mem.Allocator; |
| 5 | const ir = @import("ir.zig"); | 6 | const ir = @import("ir.zig"); |
| ... | @@ -66,8 +67,13 @@ pub fn RegisterManager( | ... | @@ -66,8 +67,13 @@ pub fn RegisterManager( |
| 66 | } | 67 | } |
| 67 | 68 | ||
| 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 null | 78 | 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 | } |
| 82 | 88 | ||
| 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 | } |
| 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 | 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); | ||
| 90 | 102 | ||
| 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. Spill | 104 | // We'll take over the first count registers. Spill |
| 93 | // the instructions that were previously there to a | 105 | // 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; | ||
| 97 | 113 | ||
| 98 | for (regs) |reg, i| { | ||
| 99 | const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null | 114 | 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 | } |
| 108 | 125 | ||
| 109 | break :blk regs; | 126 | break :blk regs; |
| 110 | }; | 127 | }; |
| 111 | } | 128 | } |
| 112 | 129 | ||
| 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 | } |
| 116 | 133 | ||
| 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); | ||
| 122 | 144 | ||
| 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 | } |
| 137 | 161 | ||
| 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 | } |
| 143 | 167 | ||
| 144 | /// Does not track the registers | 168 | /// 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. Spill | 171 | // We'll take over the first count registers. Spill |
| 148 | // the instructions that were previously there to a | 172 | // 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; | ||
| 152 | 180 | ||
| 153 | for (regs) |reg, i| { | ||
| 154 | const index = reg.allocIndex().?; // allocIndex() on a callee-preserved reg should never return null | 181 | 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 | } |
| 162 | 191 | ||
| 163 | break :blk regs; | 192 | break :blk regs; |
| ... | @@ -165,8 +194,8 @@ pub fn RegisterManager( | ... | @@ -165,8 +194,8 @@ pub fn RegisterManager( |
| 165 | } | 194 | } |
| 166 | 195 | ||
| 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 | } |
| 171 | 200 | ||
| 172 | /// Allocates the specified register with the specified | 201 | /// 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)); |
| 272 | 301 | ||
| 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, &.{})); |
| 276 | 305 | ||
| 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)); |
| 303 | 332 | ||
| 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, &.{})); |
| 306 | 335 | ||
| 307 | // Spill a register | 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 | try std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items); | 338 | try std.testing.expectEqualSlices(MockRegister, &[_]MockRegister{.r2}, function.spilled.items); |
| 310 | 339 | ||
| 311 | // No spilling necessary | 340 | // 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 | } |
| 316 | 345 |
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 | } |