| ... | ... | @@ -328,6 +328,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 328 | 328 | self.free_registers |= @as(FreeRegInt, 1) << shift; |
| 329 | 329 | } |
| 330 | 330 | |
| 331 | /// Before calling, must ensureCapacity + 1 on branch.registers. |
| 332 | /// Returns `null` if all registers are allocated. |
| 333 | fn allocReg(self: *Branch, inst: *ir.Inst) ?Register { |
| 334 | const free_index = @ctz(FreeRegInt, self.free_registers); |
| 335 | if (free_index >= callee_preserved_regs.len) { |
| 336 | return null; |
| 337 | } |
| 338 | self.free_registers &= ~(@as(FreeRegInt, 1) << free_index); |
| 339 | const reg = callee_preserved_regs[free_index]; |
| 340 | self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); |
| 341 | return reg; |
| 342 | } |
| 343 | |
| 331 | 344 | fn deinit(self: *Branch, gpa: *Allocator) void { |
| 332 | 345 | self.inst_table.deinit(gpa); |
| 333 | 346 | self.registers.deinit(gpa); |
| ... | ... | @@ -502,8 +515,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 502 | 515 | entry.value = .dead; |
| 503 | 516 | switch (prev_value) { |
| 504 | 517 | .register => |reg| { |
| 505 | | _ = branch.registers.remove(reg); |
| 506 | | branch.markRegFree(reg); |
| 518 | const reg64 = reg.to64(); |
| 519 | _ = branch.registers.remove(reg64); |
| 520 | branch.markRegFree(reg64); |
| 507 | 521 | }, |
| 508 | 522 | else => {}, // TODO process stack allocation death |
| 509 | 523 | } |
| ... | ... | @@ -582,30 +596,26 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 582 | 596 | self.stack_align = abi_align; |
| 583 | 597 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 584 | 598 | |
| 585 | | // TODO Make sure the type can fit in a register before we try to allocate one. |
| 586 | | const free_index = @ctz(FreeRegInt, branch.free_registers); |
| 587 | | if (free_index >= callee_preserved_regs.len) { |
| 588 | | const stack_offset = try self.allocMem(inst, abi_size, abi_align); |
| 589 | | return MCValue{ .stack_offset = stack_offset }; |
| 599 | // Make sure the type can fit in a register before we try to allocate one. |
| 600 | const ptr_bits = arch.ptrBitWidth(); |
| 601 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 602 | if (abi_size <= ptr_bytes) { |
| 603 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); |
| 604 | if (branch.allocReg(inst)) |reg| { |
| 605 | return MCValue{ .register = registerAlias(reg, abi_size) }; |
| 606 | } |
| 590 | 607 | } |
| 591 | | branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index); |
| 592 | | const reg = callee_preserved_regs[free_index]; |
| 593 | | try branch.registers.putNoClobber(self.gpa, reg, .{ .inst = inst }); |
| 594 | | return MCValue{ .register = reg }; |
| 608 | const stack_offset = try self.allocMem(inst, abi_size, abi_align); |
| 609 | return MCValue{ .stack_offset = stack_offset }; |
| 595 | 610 | } |
| 596 | 611 | |
| 597 | 612 | /// Does not "move" the instruction. |
| 598 | 613 | fn copyToNewRegister(self: *Self, inst: *ir.Inst) !MCValue { |
| 599 | 614 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 600 | 615 | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); |
| 601 | | try branch.inst_table.ensureCapacity(self.gpa, branch.inst_table.items().len + 1); |
| 602 | 616 | |
| 603 | | const free_index = @ctz(FreeRegInt, branch.free_registers); |
| 604 | | if (free_index >= callee_preserved_regs.len) |
| 617 | const reg = branch.allocReg(inst) orelse |
| 605 | 618 | return self.fail(inst.src, "TODO implement spilling register to stack", .{}); |
| 606 | | branch.free_registers &= ~(@as(FreeRegInt, 1) << free_index); |
| 607 | | const reg = callee_preserved_regs[free_index]; |
| 608 | | branch.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); |
| 609 | 619 | const old_mcv = branch.inst_table.get(inst).?; |
| 610 | 620 | const new_mcv: MCValue = .{ .register = reg }; |
| 611 | 621 | try self.genSetReg(inst.src, reg, old_mcv); |
| ... | ... | @@ -1131,7 +1141,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1131 | 1141 | // test reg, 1 |
| 1132 | 1142 | // TODO detect al, ax, eax |
| 1133 | 1143 | try self.code.ensureCapacity(self.code.items.len + 4); |
| 1134 | | self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 }); |
| 1144 | // TODO audit this codegen: we force w = true here to make |
| 1145 | // the value affect the big register |
| 1146 | self.rex(.{ .b = reg.isExtended(), .w = true }); |
| 1135 | 1147 | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 1136 | 1148 | 0xf6, |
| 1137 | 1149 | @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()), |
| ... | ... | @@ -1319,7 +1331,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1319 | 1331 | if (!self.wantSafety()) |
| 1320 | 1332 | return; // The already existing value will do just fine. |
| 1321 | 1333 | // TODO Upgrade this to a memset call when we have that available. |
| 1322 | | return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa }); |
| 1334 | switch (ty.abiSize(self.target.*)) { |
| 1335 | 1 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaa }), |
| 1336 | 2 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaa }), |
| 1337 | 4 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaa }), |
| 1338 | 8 => return self.genSetStack(src, ty, stack_offset, .{ .immediate = 0xaaaaaaaaaaaaaaaa }), |
| 1339 | else => return self.fail(src, "TODO implement memset", .{}), |
| 1340 | } |
| 1323 | 1341 | }, |
| 1324 | 1342 | .compare_flags_unsigned => |op| { |
| 1325 | 1343 | return self.fail(src, "TODO implement set stack variable with compare flags value (unsigned)", .{}); |
| ... | ... | @@ -1328,24 +1346,35 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1328 | 1346 | return self.fail(src, "TODO implement set stack variable with compare flags value (signed)", .{}); |
| 1329 | 1347 | }, |
| 1330 | 1348 | .immediate => |x_big| { |
| 1331 | | if (ty.abiSize(self.target.*) != 4) { |
| 1332 | | // TODO after fixing this, need to update the undef case above |
| 1333 | | return self.fail(src, "TODO implement set non 4 abi size stack variable with immediate", .{}); |
| 1349 | if (stack_offset > 128) { |
| 1350 | return self.fail(src, "TODO implement set stack variable with large stack offset", .{}); |
| 1334 | 1351 | } |
| 1335 | | try self.code.ensureCapacity(self.code.items.len + 7); |
| 1336 | | if (x_big <= math.maxInt(u32)) { |
| 1337 | | const x = @intCast(u32, x_big); |
| 1338 | | if (stack_offset > 128) { |
| 1339 | | return self.fail(src, "TODO implement set stack variable with large stack offset", .{}); |
| 1340 | | } |
| 1341 | | // We have a positive stack offset value but we want a twos complement negative |
| 1342 | | // offset from rbp, which is at the top of the stack frame. |
| 1343 | | const negative_offset = @intCast(i8, -@intCast(i32, stack_offset)); |
| 1344 | | const twos_comp = @bitCast(u8, negative_offset); |
| 1345 | | // mov DWORD PTR [rbp+offset], immediate |
| 1346 | | self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp }); |
| 1347 | | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); |
| 1348 | | } else { |
| 1352 | try self.code.ensureCapacity(self.code.items.len + 8); |
| 1353 | switch (ty.abiSize(self.target.*)) { |
| 1354 | 1 => { |
| 1355 | return self.fail(src, "TODO implement set abi_size=1 stack variable with immediate", .{}); |
| 1356 | }, |
| 1357 | 2 => { |
| 1358 | return self.fail(src, "TODO implement set abi_size=2 stack variable with immediate", .{}); |
| 1359 | }, |
| 1360 | 4 => { |
| 1361 | const x = @intCast(u32, x_big); |
| 1362 | // We have a positive stack offset value but we want a twos complement negative |
| 1363 | // offset from rbp, which is at the top of the stack frame. |
| 1364 | const negative_offset = @intCast(i8, -@intCast(i32, stack_offset)); |
| 1365 | const twos_comp = @bitCast(u8, negative_offset); |
| 1366 | // mov DWORD PTR [rbp+offset], immediate |
| 1367 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0xc7, 0x45, twos_comp }); |
| 1368 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); |
| 1369 | }, |
| 1370 | 8 => { |
| 1371 | return self.fail(src, "TODO implement set abi_size=8 stack variable with immediate", .{}); |
| 1372 | }, |
| 1373 | else => { |
| 1374 | return self.fail(src, "TODO implement set abi_size=large stack variable with immediate", .{}); |
| 1375 | }, |
| 1376 | } |
| 1377 | if (x_big <= math.maxInt(u32)) {} else { |
| 1349 | 1378 | return self.fail(src, "TODO implement set stack variable with large immediate", .{}); |
| 1350 | 1379 | } |
| 1351 | 1380 | }, |
| ... | ... | @@ -1407,7 +1436,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1407 | 1436 | }, |
| 1408 | 1437 | .compare_flags_unsigned => |op| { |
| 1409 | 1438 | try self.code.ensureCapacity(self.code.items.len + 3); |
| 1410 | | self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 }); |
| 1439 | // TODO audit this codegen: we force w = true here to make |
| 1440 | // the value affect the big register |
| 1441 | self.rex(.{ .b = reg.isExtended(), .w = true }); |
| 1411 | 1442 | const opcode: u8 = switch (op) { |
| 1412 | 1443 | .gte => 0x93, |
| 1413 | 1444 | .gt => 0x97, |
| ... | ... | @@ -1423,9 +1454,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1423 | 1454 | return self.fail(src, "TODO set register with compare flags value (signed)", .{}); |
| 1424 | 1455 | }, |
| 1425 | 1456 | .immediate => |x| { |
| 1426 | | if (reg.size() != 64) { |
| 1427 | | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); |
| 1428 | | } |
| 1429 | 1457 | // 32-bit moves zero-extend to 64-bit, so xoring the 32-bit |
| 1430 | 1458 | // register is the fastest way to zero a register. |
| 1431 | 1459 | if (x == 0) { |
| ... | ... | @@ -1478,16 +1506,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1478 | 1506 | // |
| 1479 | 1507 | // In this case, the encoding of the REX byte is 0b0100100B |
| 1480 | 1508 | try self.code.ensureCapacity(self.code.items.len + 10); |
| 1481 | | self.rex(.{ .w = true, .b = reg.isExtended() }); |
| 1509 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); |
| 1482 | 1510 | self.code.items.len += 9; |
| 1483 | 1511 | self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111); |
| 1484 | 1512 | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; |
| 1485 | 1513 | mem.writeIntLittle(u64, imm_ptr, x); |
| 1486 | 1514 | }, |
| 1487 | 1515 | .embedded_in_code => |code_offset| { |
| 1488 | | if (reg.size() != 64) { |
| 1489 | | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); |
| 1490 | | } |
| 1491 | 1516 | // We need the offset from RIP in a signed i32 twos complement. |
| 1492 | 1517 | // The instruction is 7 bytes long and RIP points to the next instruction. |
| 1493 | 1518 | try self.code.ensureCapacity(self.code.items.len + 7); |
| ... | ... | @@ -1495,7 +1520,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1495 | 1520 | // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three |
| 1496 | 1521 | // bits as five. |
| 1497 | 1522 | // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id. |
| 1498 | | self.rex(.{ .w = true, .b = reg.isExtended() }); |
| 1523 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); |
| 1499 | 1524 | self.code.items.len += 6; |
| 1500 | 1525 | const rip = self.code.items.len; |
| 1501 | 1526 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); |
| ... | ... | @@ -1507,12 +1532,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1507 | 1532 | }, |
| 1508 | 1533 | .register => |src_reg| { |
| 1509 | 1534 | // If the registers are the same, nothing to do. |
| 1510 | | if (src_reg == reg) |
| 1535 | if (src_reg.id() == reg.id()) |
| 1511 | 1536 | return; |
| 1512 | 1537 | |
| 1513 | | if (reg.size() != 64) { |
| 1514 | | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); |
| 1515 | | } |
| 1516 | 1538 | // This is a variant of 8B /r. Since we're using 64-bit moves, we require a REX. |
| 1517 | 1539 | // This is thus three bytes: REX 0x8B R/M. |
| 1518 | 1540 | // If the destination is extended, the R field must be 1. |
| ... | ... | @@ -1520,14 +1542,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1520 | 1542 | // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle |
| 1521 | 1543 | // three bits) contain the destination, and the R/M field (the lower three bits) contain the source. |
| 1522 | 1544 | try self.code.ensureCapacity(self.code.items.len + 3); |
| 1523 | | self.rex(.{ .w = true, .r = reg.isExtended(), .b = src_reg.isExtended() }); |
| 1545 | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended(), .b = src_reg.isExtended() }); |
| 1524 | 1546 | const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111); |
| 1525 | 1547 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R }); |
| 1526 | 1548 | }, |
| 1527 | 1549 | .memory => |x| { |
| 1528 | | if (reg.size() != 64) { |
| 1529 | | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); |
| 1530 | | } |
| 1531 | 1550 | if (x <= math.maxInt(u32)) { |
| 1532 | 1551 | // Moving from memory to a register is a variant of `8B /r`. |
| 1533 | 1552 | // Since we're using 64-bit moves, we require a REX. |
| ... | ... | @@ -1537,7 +1556,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1537 | 1556 | // 0b00RRR100, where RRR is the lower three bits of the register ID. |
| 1538 | 1557 | // The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32. |
| 1539 | 1558 | try self.code.ensureCapacity(self.code.items.len + 8); |
| 1540 | | self.rex(.{ .w = true, .b = reg.isExtended() }); |
| 1559 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); |
| 1541 | 1560 | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 1542 | 1561 | 0x8B, |
| 1543 | 1562 | 0x04 | (@as(u8, reg.id() & 0b111) << 3), // R |
| ... | ... | @@ -1580,18 +1599,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1580 | 1599 | // |
| 1581 | 1600 | // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both* |
| 1582 | 1601 | // register operands need to be marked as extended. |
| 1583 | | self.rex(.{ .w = true, .b = reg.isExtended(), .r = reg.isExtended() }); |
| 1602 | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), .r = reg.isExtended() }); |
| 1584 | 1603 | const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); |
| 1585 | 1604 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM }); |
| 1586 | 1605 | } |
| 1587 | 1606 | } |
| 1588 | 1607 | }, |
| 1589 | 1608 | .stack_offset => |off| { |
| 1590 | | if (reg.size() != 64) { |
| 1591 | | return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); |
| 1592 | | } |
| 1593 | 1609 | try self.code.ensureCapacity(self.code.items.len + 7); |
| 1594 | | self.rex(.{ .w = true, .r = reg.isExtended() }); |
| 1610 | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() }); |
| 1595 | 1611 | const reg_id: u8 = @truncate(u3, reg.id()); |
| 1596 | 1612 | if (off <= 128) { |
| 1597 | 1613 | // Example: 48 8b 4d 7f mov rcx,QWORD PTR [rbp+0x7f] |
| ... | ... | @@ -1750,11 +1766,16 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1750 | 1766 | for (param_types) |ty, i| { |
| 1751 | 1767 | switch (ty.zigTypeTag()) { |
| 1752 | 1768 | .Bool, .Int => { |
| 1769 | const param_size = @intCast(u32, ty.abiSize(self.target.*)); |
| 1753 | 1770 | if (next_int_reg >= c_abi_int_param_regs.len) { |
| 1754 | 1771 | result.args[i] = .{ .stack_offset = next_stack_offset }; |
| 1755 | | next_stack_offset += @intCast(u32, ty.abiSize(self.target.*)); |
| 1772 | next_stack_offset += param_size; |
| 1756 | 1773 | } else { |
| 1757 | | result.args[i] = .{ .register = c_abi_int_param_regs[next_int_reg] }; |
| 1774 | const aliased_reg = registerAlias( |
| 1775 | c_abi_int_param_regs[next_int_reg], |
| 1776 | param_size, |
| 1777 | ); |
| 1778 | result.args[i] = .{ .register = aliased_reg }; |
| 1758 | 1779 | next_int_reg += 1; |
| 1759 | 1780 | } |
| 1760 | 1781 | }, |
| ... | ... | @@ -1778,7 +1799,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1778 | 1799 | .x86_64 => switch (cc) { |
| 1779 | 1800 | .Naked => unreachable, |
| 1780 | 1801 | .Unspecified, .C => { |
| 1781 | | result.return_value = .{ .register = c_abi_int_return_regs[0] }; |
| 1802 | const ret_ty_size = @intCast(u32, ret_ty.abiSize(self.target.*)); |
| 1803 | const aliased_reg = registerAlias(c_abi_int_return_regs[0], ret_ty_size); |
| 1804 | result.return_value = .{ .register = aliased_reg }; |
| 1782 | 1805 | }, |
| 1783 | 1806 | else => return self.fail(src, "TODO implement function return values for {}", .{cc}), |
| 1784 | 1807 | }, |
| ... | ... | @@ -1825,5 +1848,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1825 | 1848 | fn parseRegName(name: []const u8) ?Register { |
| 1826 | 1849 | return std.meta.stringToEnum(Register, name); |
| 1827 | 1850 | } |
| 1851 | |
| 1852 | fn registerAlias(reg: Register, size_bytes: u32) Register { |
| 1853 | switch (arch) { |
| 1854 | // For x86_64 we have to pick a smaller register alias depending on abi size. |
| 1855 | .x86_64 => switch (size_bytes) { |
| 1856 | 1 => return reg.to8(), |
| 1857 | 2 => return reg.to16(), |
| 1858 | 4 => return reg.to32(), |
| 1859 | 8 => return reg.to64(), |
| 1860 | else => unreachable, |
| 1861 | }, |
| 1862 | else => return reg, |
| 1863 | } |
| 1864 | } |
| 1828 | 1865 | }; |
| 1829 | 1866 | } |