| ... | ... | @@ -1034,7 +1034,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1034 | 1034 | }, |
| 1035 | 1035 | .val = Value.initTag(.bool_true), |
| 1036 | 1036 | }; |
| 1037 | | return try self.genX8664BinMath(&inst.base, inst.operand, &imm.base, 6, 0x30); |
| 1037 | return try self.genX8664BinMath(&inst.base, inst.operand, &imm.base); |
| 1038 | 1038 | }, |
| 1039 | 1039 | .arm, .armeb => { |
| 1040 | 1040 | var imm = ir.Inst.Constant{ |
| ... | ... | @@ -1058,7 +1058,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1058 | 1058 | return MCValue.dead; |
| 1059 | 1059 | switch (arch) { |
| 1060 | 1060 | .x86_64 => { |
| 1061 | | return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 0, 0x00); |
| 1061 | return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs); |
| 1062 | 1062 | }, |
| 1063 | 1063 | .arm, .armeb => return try self.genArmBinOp(&inst.base, inst.lhs, inst.rhs, .add), |
| 1064 | 1064 | else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}), |
| ... | ... | @@ -1352,7 +1352,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1352 | 1352 | return MCValue.dead; |
| 1353 | 1353 | switch (arch) { |
| 1354 | 1354 | .x86_64 => { |
| 1355 | | return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 5, 0x28); |
| 1355 | return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs); |
| 1356 | 1356 | }, |
| 1357 | 1357 | .arm, .armeb => return try self.genArmBinOp(&inst.base, inst.lhs, inst.rhs, .sub), |
| 1358 | 1358 | else => return self.fail(inst.base.src, "TODO implement sub for {}", .{self.target.cpu.arch}), |
| ... | ... | @@ -1497,8 +1497,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1497 | 1497 | return dst_mcv; |
| 1498 | 1498 | } |
| 1499 | 1499 | |
| 1500 | /// Perform "binary" operators, excluding comparisons. |
| 1501 | /// Currently, the following ops are supported: |
| 1500 | 1502 | /// ADD, SUB, XOR, OR, AND |
| 1501 | | fn genX8664BinMath(self: *Self, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, opx: u8, mr: u8) !MCValue { |
| 1503 | fn genX8664BinMath(self: *Self, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst) !MCValue { |
| 1504 | // We'll handle these ops in two steps. |
| 1505 | // 1) Prepare an output register, and put one of the arguments in it |
| 1506 | // 2) Perform the op with the other argument |
| 1507 | |
| 1502 | 1508 | try self.code.ensureCapacity(self.code.items.len + 8); |
| 1503 | 1509 | |
| 1504 | 1510 | const lhs = try self.resolveInst(op_lhs); |
| ... | ... | @@ -1559,18 +1565,108 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1559 | 1565 | else => {}, |
| 1560 | 1566 | } |
| 1561 | 1567 | |
| 1562 | | try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, opx, mr); |
| 1568 | // Now for step 2, we perform the actual op |
| 1569 | switch (inst.tag) { |
| 1570 | // TODO: Generate wrapping and non-wrapping versions separately |
| 1571 | .add, .addwrap => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 0, 0x00), |
| 1572 | .bool_or, .bit_or => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 1, 0x08), |
| 1573 | .bool_and, .bit_and => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 4, 0x20), |
| 1574 | .sub, .subwrap => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 5, 0x28), |
| 1575 | .xor, .not => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 6, 0x30), |
| 1576 | |
| 1577 | else => unreachable, |
| 1578 | } |
| 1563 | 1579 | |
| 1564 | 1580 | return dst_mcv; |
| 1565 | 1581 | } |
| 1566 | 1582 | |
| 1583 | /// Wrap over Instruction.encodeInto to translate errors |
| 1584 | fn encodeX8664Instruction( |
| 1585 | self: *Self, |
| 1586 | src: LazySrcLoc, |
| 1587 | inst: Instruction, |
| 1588 | ) !void { |
| 1589 | inst.encodeInto(self.code) catch |err| { |
| 1590 | if (err == error.OutOfMemory) |
| 1591 | return error.OutOfMemory |
| 1592 | else |
| 1593 | return self.fail(src, "Instruction.encodeInto failed because {s}", .{@errorName(err)}); |
| 1594 | }; |
| 1595 | } |
| 1596 | |
| 1597 | /// This function encodes a binary operation for x86_64 |
| 1598 | /// intended for use with the following opcode ranges |
| 1599 | /// because they share the same structure. |
| 1600 | /// |
| 1601 | /// Thus not all binary operations can be used here |
| 1602 | /// -- multiplication needs to be done with imul, |
| 1603 | /// which doesn't have as convenient an interface. |
| 1604 | /// |
| 1605 | /// "opx"-style instructions use the opcode extension field to indicate which instruction to execute: |
| 1606 | /// |
| 1607 | /// opx = /0: add |
| 1608 | /// opx = /1: or |
| 1609 | /// opx = /2: adc |
| 1610 | /// opx = /3: sbb |
| 1611 | /// opx = /4: and |
| 1612 | /// opx = /5: sub |
| 1613 | /// opx = /6: xor |
| 1614 | /// opx = /7: cmp |
| 1615 | /// |
| 1616 | /// opcode | operand shape |
| 1617 | /// --------+---------------------- |
| 1618 | /// 80 /opx | r/m8, imm8 |
| 1619 | /// 81 /opx | r/m16/32/64, imm16/32 |
| 1620 | /// 83 /opx | r/m16/32/64, imm8 |
| 1621 | /// |
| 1622 | /// "mr"-style instructions use the low bits of opcode to indicate shape of instruction: |
| 1623 | /// |
| 1624 | /// mr = 00: add |
| 1625 | /// mr = 08: or |
| 1626 | /// mr = 10: adc |
| 1627 | /// mr = 18: sbb |
| 1628 | /// mr = 20: and |
| 1629 | /// mr = 28: sub |
| 1630 | /// mr = 30: xor |
| 1631 | /// mr = 38: cmp |
| 1632 | /// |
| 1633 | /// opcode | operand shape |
| 1634 | /// -------+------------------------- |
| 1635 | /// mr + 0 | r/m8, r8 |
| 1636 | /// mr + 1 | r/m16/32/64, r16/32/64 |
| 1637 | /// mr + 2 | r8, r/m8 |
| 1638 | /// mr + 3 | r16/32/64, r/m16/32/64 |
| 1639 | /// mr + 4 | AL, imm8 |
| 1640 | /// mr + 5 | rAX, imm16/32 |
| 1641 | /// |
| 1642 | /// TODO: rotates and shifts share the same structure, so we can potentially implement them |
| 1643 | /// at a later date with very similar code. |
| 1644 | /// They have "opx"-style instructions, but no "mr"-style instructions. |
| 1645 | /// |
| 1646 | /// opx = /0: rol, |
| 1647 | /// opx = /1: ror, |
| 1648 | /// opx = /2: rcl, |
| 1649 | /// opx = /3: rcr, |
| 1650 | /// opx = /4: shl sal, |
| 1651 | /// opx = /5: shr, |
| 1652 | /// opx = /6: sal shl, |
| 1653 | /// opx = /7: sar, |
| 1654 | /// |
| 1655 | /// opcode | operand shape |
| 1656 | /// --------+------------------ |
| 1657 | /// c0 /opx | r/m8, imm8 |
| 1658 | /// c1 /opx | r/m16/32/64, imm8 |
| 1659 | /// d0 /opx | r/m8, 1 |
| 1660 | /// d1 /opx | r/m16/32/64, 1 |
| 1661 | /// d2 /opx | r/m8, CL (for context, CL is register 1) |
| 1662 | /// d3 /opx | r/m16/32/64, CL (for context, CL is register 1) |
| 1567 | 1663 | fn genX8664BinMathCode( |
| 1568 | 1664 | self: *Self, |
| 1569 | 1665 | src: LazySrcLoc, |
| 1570 | 1666 | dst_ty: Type, |
| 1571 | 1667 | dst_mcv: MCValue, |
| 1572 | 1668 | src_mcv: MCValue, |
| 1573 | | opx: u8, |
| 1669 | opx: u3, |
| 1574 | 1670 | mr: u8, |
| 1575 | 1671 | ) !void { |
| 1576 | 1672 | switch (dst_mcv) { |
| ... | ... | @@ -1589,31 +1685,78 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1589 | 1685 | .ptr_stack_offset => unreachable, |
| 1590 | 1686 | .ptr_embedded_in_code => unreachable, |
| 1591 | 1687 | .register => |src_reg| { |
| 1592 | | self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); |
| 1593 | | self.code.appendSliceAssumeCapacity(&[_]u8{ mr + 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) }); |
| 1688 | // register, register use mr + 1 addressing mode: r/m16/32/64, r16/32/64 |
| 1689 | try self.encodeX8664Instruction(src, Instruction{ |
| 1690 | .operand_size_64 = dst_reg.size() == 64, |
| 1691 | .primary_opcode_1b = mr + 1, |
| 1692 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 1693 | // https://github.com/ziglang/zig/issues/6515 |
| 1694 | .modrm = @as( |
| 1695 | ?Instruction.ModrmEffectiveAddress, |
| 1696 | Instruction.ModrmEffectiveAddress{ .reg = dst_reg }, |
| 1697 | ), |
| 1698 | .reg = src_reg, |
| 1699 | }); |
| 1594 | 1700 | }, |
| 1595 | 1701 | .immediate => |imm| { |
| 1702 | // register, immediate use opx = 81 or 83 addressing modes: |
| 1703 | // opx = 81: r/m16/32/64, imm16/32 |
| 1704 | // opx = 83: r/m16/32/64, imm8 |
| 1596 | 1705 | const imm32 = @intCast(u31, imm); // This case must be handled before calling genX8664BinMathCode. |
| 1597 | | // 81 /opx id |
| 1598 | 1706 | if (imm32 <= math.maxInt(u7)) { |
| 1599 | | self.rex(.{ .b = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); |
| 1600 | | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 1601 | | 0x83, |
| 1602 | | 0xC0 | (opx << 3) | @truncate(u3, dst_reg.id()), |
| 1603 | | @intCast(u8, imm32), |
| 1707 | try self.encodeX8664Instruction(src, Instruction{ |
| 1708 | .operand_size_64 = dst_reg.size() == 64, |
| 1709 | .primary_opcode_1b = 0x83, |
| 1710 | .opcode_extension = opx, |
| 1711 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 1712 | // https://github.com/ziglang/zig/issues/6515 |
| 1713 | .modrm = @as( |
| 1714 | ?Instruction.ModrmEffectiveAddress, |
| 1715 | Instruction.ModrmEffectiveAddress{ .reg = dst_reg }, |
| 1716 | ), |
| 1717 | .immediate_bytes = 1, |
| 1718 | .immediate = imm32, |
| 1604 | 1719 | }); |
| 1605 | 1720 | } else { |
| 1606 | | self.rex(.{ .r = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); |
| 1607 | | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 1608 | | 0x81, |
| 1609 | | 0xC0 | (opx << 3) | @truncate(u3, dst_reg.id()), |
| 1721 | try self.encodeX8664Instruction(src, Instruction{ |
| 1722 | .operand_size_64 = dst_reg.size() == 64, |
| 1723 | .primary_opcode_1b = 0x81, |
| 1724 | .opcode_extension = opx, |
| 1725 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 1726 | // https://github.com/ziglang/zig/issues/6515 |
| 1727 | .modrm = @as( |
| 1728 | ?Instruction.ModrmEffectiveAddress, |
| 1729 | Instruction.ModrmEffectiveAddress{ .reg = dst_reg }, |
| 1730 | ), |
| 1731 | .immediate_bytes = 4, |
| 1732 | .immediate = imm32, |
| 1610 | 1733 | }); |
| 1611 | | std.mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), imm32); |
| 1612 | 1734 | } |
| 1613 | 1735 | }, |
| 1614 | | .embedded_in_code, .memory, .stack_offset => { |
| 1736 | .embedded_in_code, .memory => { |
| 1615 | 1737 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{}); |
| 1616 | 1738 | }, |
| 1739 | .stack_offset => |off| { |
| 1740 | const abi_size = dst_ty.abiSize(self.target.*); |
| 1741 | const adj_off = off + abi_size; |
| 1742 | if (off > math.maxInt(i32)) { |
| 1743 | return self.fail(src, "stack offset too large", .{}); |
| 1744 | } |
| 1745 | try self.encodeX8664Instruction(src, Instruction{ |
| 1746 | .operand_size_64 = dst_reg.size() == 64, |
| 1747 | .primary_opcode_1b = mr + 0x3, |
| 1748 | .reg = dst_reg, |
| 1749 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 1750 | // https://github.com/ziglang/zig/issues/6515 |
| 1751 | .modrm = @as( |
| 1752 | ?Instruction.ModrmEffectiveAddress, |
| 1753 | Instruction.ModrmEffectiveAddress{ .mem_disp = .{ |
| 1754 | .reg = Register.ebp, |
| 1755 | .disp = -@intCast(i32, adj_off), |
| 1756 | } }, |
| 1757 | ), |
| 1758 | }); |
| 1759 | }, |
| 1617 | 1760 | .compare_flags_unsigned => { |
| 1618 | 1761 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{}); |
| 1619 | 1762 | }, |
| ... | ... | @@ -1655,25 +1798,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1655 | 1798 | fn genX8664ModRMRegToStack(self: *Self, src: LazySrcLoc, ty: Type, off: u32, reg: Register, opcode: u8) !void { |
| 1656 | 1799 | const abi_size = ty.abiSize(self.target.*); |
| 1657 | 1800 | const adj_off = off + abi_size; |
| 1658 | | try self.code.ensureCapacity(self.code.items.len + 7); |
| 1659 | | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() }); |
| 1660 | | const reg_id: u8 = @truncate(u3, reg.id()); |
| 1661 | | if (adj_off <= 128) { |
| 1662 | | // example: 48 89 55 7f mov QWORD PTR [rbp+0x7f],rdx |
| 1663 | | const RM = @as(u8, 0b01_000_101) | (reg_id << 3); |
| 1664 | | const negative_offset = @intCast(i8, -@intCast(i32, adj_off)); |
| 1665 | | const twos_comp = @bitCast(u8, negative_offset); |
| 1666 | | self.code.appendSliceAssumeCapacity(&[_]u8{ opcode, RM, twos_comp }); |
| 1667 | | } else if (adj_off <= 2147483648) { |
| 1668 | | // example: 48 89 95 80 00 00 00 mov QWORD PTR [rbp+0x80],rdx |
| 1669 | | const RM = @as(u8, 0b10_000_101) | (reg_id << 3); |
| 1670 | | const negative_offset = @intCast(i32, -@intCast(i33, adj_off)); |
| 1671 | | const twos_comp = @bitCast(u32, negative_offset); |
| 1672 | | self.code.appendSliceAssumeCapacity(&[_]u8{ opcode, RM }); |
| 1673 | | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp); |
| 1674 | | } else { |
| 1801 | if (off > math.maxInt(i32)) { |
| 1675 | 1802 | return self.fail(src, "stack offset too large", .{}); |
| 1676 | 1803 | } |
| 1804 | try self.encodeX8664Instruction(src, Instruction{ |
| 1805 | .operand_size_64 = reg.size() == 64, |
| 1806 | .primary_opcode_1b = opcode, |
| 1807 | .reg = reg, |
| 1808 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 1809 | // https://github.com/ziglang/zig/issues/6515 |
| 1810 | .modrm = @as( |
| 1811 | ?Instruction.ModrmEffectiveAddress, |
| 1812 | Instruction.ModrmEffectiveAddress{ .mem_disp = .{ |
| 1813 | .reg = Register.ebp, |
| 1814 | .disp = -@intCast(i32, adj_off), |
| 1815 | } }, |
| 1816 | ), |
| 1817 | }); |
| 1677 | 1818 | } |
| 1678 | 1819 | |
| 1679 | 1820 | fn genArgDbgInfo(self: *Self, inst: *ir.Inst.Arg, mcv: MCValue) !void { |
| ... | ... | @@ -2340,15 +2481,24 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2340 | 2481 | }, |
| 2341 | 2482 | .register => |reg| blk: { |
| 2342 | 2483 | // test reg, 1 |
| 2343 | | // TODO detect al, ax, eax |
| 2344 | | try self.code.ensureCapacity(self.code.items.len + 4); |
| 2345 | | // TODO audit this codegen: we force w = true here to make |
| 2346 | | // the value affect the big register |
| 2347 | | self.rex(.{ .b = reg.isExtended(), .w = true }); |
| 2348 | | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| 2349 | | 0xf6, |
| 2350 | | @as(u8, 0xC0) | (0 << 3) | @truncate(u3, reg.id()), |
| 2351 | | 0x01, |
| 2484 | try self.encodeX8664Instruction(inst.base.src, Instruction{ |
| 2485 | // TODO audit this codegen: we force w = true here to make |
| 2486 | // the value affect the big register |
| 2487 | .operand_size_64 = true, |
| 2488 | |
| 2489 | .primary_opcode_1b = 0xf6, // f6/0 is TEST r/m8, imm8 |
| 2490 | .opcode_extension = 0, |
| 2491 | |
| 2492 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 2493 | // https://github.com/ziglang/zig/issues/6515 |
| 2494 | // TODO detect al, ax, eax, there's another opcode 0xa8 for that |
| 2495 | .modrm = @as( |
| 2496 | ?Instruction.ModrmEffectiveAddress, |
| 2497 | Instruction.ModrmEffectiveAddress{ .reg = reg }, |
| 2498 | ), |
| 2499 | |
| 2500 | .immediate_bytes = 1, |
| 2501 | .immediate = 1, |
| 2352 | 2502 | }); |
| 2353 | 2503 | break :blk 0x84; |
| 2354 | 2504 | }, |
| ... | ... | @@ -2662,9 +2812,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2662 | 2812 | switch (arch) { |
| 2663 | 2813 | .x86_64 => switch (inst.base.tag) { |
| 2664 | 2814 | // lhs AND rhs |
| 2665 | | .bool_and => return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 4, 0x20), |
| 2815 | .bool_and => return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs), |
| 2666 | 2816 | // lhs OR rhs |
| 2667 | | .bool_or => return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs, 1, 0x08), |
| 2817 | .bool_or => return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs), |
| 2668 | 2818 | else => unreachable, // Not a boolean operation |
| 2669 | 2819 | }, |
| 2670 | 2820 | .arm, .armeb => switch (inst.base.tag) { |
| ... | ... | @@ -3451,20 +3601,27 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3451 | 3601 | } |
| 3452 | 3602 | }, |
| 3453 | 3603 | .compare_flags_unsigned => |op| { |
| 3454 | | try self.code.ensureCapacity(self.code.items.len + 3); |
| 3455 | | // TODO audit this codegen: we force w = true here to make |
| 3456 | | // the value affect the big register |
| 3457 | | self.rex(.{ .b = reg.isExtended(), .w = true }); |
| 3458 | | const opcode: u8 = switch (op) { |
| 3459 | | .gte => 0x93, |
| 3460 | | .gt => 0x97, |
| 3461 | | .neq => 0x95, |
| 3462 | | .lt => 0x92, |
| 3463 | | .lte => 0x96, |
| 3464 | | .eq => 0x94, |
| 3465 | | }; |
| 3466 | | const id = @as(u8, reg.id() & 0b111); |
| 3467 | | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x0f, opcode, 0xC0 | id }); |
| 3604 | try self.encodeX8664Instruction(src, Instruction{ |
| 3605 | // TODO audit this codegen: we force w = true here to make |
| 3606 | // the value affect the big register |
| 3607 | .operand_size_64 = true, |
| 3608 | |
| 3609 | .primary_opcode_2b = switch (op) { |
| 3610 | .gte => 0x93, |
| 3611 | .gt => 0x97, |
| 3612 | .neq => 0x95, |
| 3613 | .lt => 0x92, |
| 3614 | .lte => 0x96, |
| 3615 | .eq => 0x94, |
| 3616 | }, |
| 3617 | |
| 3618 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 3619 | // https://github.com/ziglang/zig/issues/6515 |
| 3620 | .modrm = @as( |
| 3621 | ?Instruction.ModrmEffectiveAddress, |
| 3622 | Instruction.ModrmEffectiveAddress{ .reg = reg }, |
| 3623 | ), |
| 3624 | }); |
| 3468 | 3625 | }, |
| 3469 | 3626 | .compare_flags_signed => |op| { |
| 3470 | 3627 | return self.fail(src, "TODO set register with compare flags value (signed)", .{}); |
| ... | ... | @@ -3476,38 +3633,32 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3476 | 3633 | // The encoding for `xor r32, r32` is `0x31 /r`. |
| 3477 | 3634 | // Section 3.1.1.1 of the Intel x64 Manual states that "/r indicates that the |
| 3478 | 3635 | // ModR/M byte of the instruction contains a register operand and an r/m operand." |
| 3479 | | // |
| 3480 | | // R/M bytes are composed of two bits for the mode, then three bits for the register, |
| 3481 | | // then three bits for the operand. Since we're zeroing a register, the two three-bit |
| 3482 | | // values will be identical, and the mode is three (the raw register value). |
| 3483 | | // |
| 3484 | | // If we're accessing e.g. r8d, we need to use a REX prefix before the actual operation. Since |
| 3485 | | // this is a 32-bit operation, the W flag is set to zero. X is also zero, as we're not using a SIB. |
| 3486 | | // Both R and B are set, as we're extending, in effect, the register bits *and* the operand. |
| 3487 | | try self.code.ensureCapacity(self.code.items.len + 3); |
| 3488 | | self.rex(.{ .r = reg.isExtended(), .b = reg.isExtended() }); |
| 3489 | | const id = @as(u8, reg.id() & 0b111); |
| 3490 | | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x31, 0xC0 | id << 3 | id }); |
| 3636 | try self.encodeX8664Instruction(src, Instruction{ |
| 3637 | .primary_opcode_1b = 0x31, |
| 3638 | |
| 3639 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 3640 | // https://github.com/ziglang/zig/issues/6515 |
| 3641 | .reg = @as(?Register, reg), |
| 3642 | .modrm = @as( |
| 3643 | ?Instruction.ModrmEffectiveAddress, |
| 3644 | Instruction.ModrmEffectiveAddress{ .reg = reg }, |
| 3645 | ), |
| 3646 | }); |
| 3491 | 3647 | return; |
| 3492 | 3648 | } |
| 3493 | 3649 | if (x <= math.maxInt(u32)) { |
| 3494 | 3650 | // Next best case: if we set the lower four bytes, the upper four will be zeroed. |
| 3495 | 3651 | // |
| 3496 | 3652 | // The encoding for `mov IMM32 -> REG` is (0xB8 + R) IMM. |
| 3497 | | if (reg.isExtended()) { |
| 3498 | | // Just as with XORing, we need a REX prefix. This time though, we only |
| 3499 | | // need the B bit set, as we're extending the opcode's register field, |
| 3500 | | // and there is no Mod R/M byte. |
| 3501 | | // |
| 3502 | | // Thus, we need b01000001, or 0x41. |
| 3503 | | try self.code.resize(self.code.items.len + 6); |
| 3504 | | self.code.items[self.code.items.len - 6] = 0x41; |
| 3505 | | } else { |
| 3506 | | try self.code.resize(self.code.items.len + 5); |
| 3507 | | } |
| 3508 | | self.code.items[self.code.items.len - 5] = 0xB8 | @as(u8, reg.id() & 0b111); |
| 3509 | | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; |
| 3510 | | mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x)); |
| 3653 | try self.encodeX8664Instruction(src, Instruction{ |
| 3654 | // B8 + R |
| 3655 | .primary_opcode_1b = 0xB8, |
| 3656 | .opcode_reg = @as(?Register, reg), |
| 3657 | |
| 3658 | // IMM32 |
| 3659 | .immediate_bytes = 4, |
| 3660 | .immediate = x, |
| 3661 | }); |
| 3511 | 3662 | return; |
| 3512 | 3663 | } |
| 3513 | 3664 | // Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls |
| ... | ... | @@ -3517,50 +3668,58 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3517 | 3668 | // This encoding is, in fact, the *same* as the one used for 32-bit loads. The only |
| 3518 | 3669 | // difference is that we set REX.W before the instruction, which extends the load to |
| 3519 | 3670 | // 64-bit and uses the full bit-width of the register. |
| 3520 | | // |
| 3521 | | // Since we always need a REX here, let's just check if we also need to set REX.B. |
| 3522 | | // |
| 3523 | | // In this case, the encoding of the REX byte is 0b0100100B |
| 3524 | | try self.code.ensureCapacity(self.code.items.len + 10); |
| 3525 | | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); |
| 3526 | | self.code.items.len += 9; |
| 3527 | | self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111); |
| 3528 | | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; |
| 3529 | | mem.writeIntLittle(u64, imm_ptr, x); |
| 3671 | try self.encodeX8664Instruction(src, Instruction{ |
| 3672 | .operand_size_64 = true, |
| 3673 | // B8 + R |
| 3674 | .primary_opcode_1b = 0xB8, |
| 3675 | .opcode_reg = @as(?Register, reg), |
| 3676 | |
| 3677 | // IMM64 |
| 3678 | .immediate_bytes = 8, |
| 3679 | .immediate = x, |
| 3680 | }); |
| 3530 | 3681 | }, |
| 3531 | 3682 | .embedded_in_code => |code_offset| { |
| 3532 | | // We need the offset from RIP in a signed i32 twos complement. |
| 3533 | | // The instruction is 7 bytes long and RIP points to the next instruction. |
| 3534 | | try self.code.ensureCapacity(self.code.items.len + 7); |
| 3535 | | // 64-bit LEA is encoded as REX.W 8D /r. If the register is extended, the REX byte is modified, |
| 3536 | | // but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three |
| 3537 | | // bits as five. |
| 3538 | | // REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id. |
| 3539 | | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended() }); |
| 3540 | | self.code.items.len += 6; |
| 3683 | // 64-bit LEA is encoded as REX.W 8D /r. |
| 3541 | 3684 | const rip = self.code.items.len; |
| 3542 | 3685 | const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip); |
| 3543 | 3686 | const offset = @intCast(i32, big_offset); |
| 3544 | | self.code.items[self.code.items.len - 6] = 0x8D; |
| 3545 | | self.code.items[self.code.items.len - 5] = 0b101 | (@as(u8, reg.id() & 0b111) << 3); |
| 3546 | | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; |
| 3547 | | mem.writeIntLittle(i32, imm_ptr, offset); |
| 3687 | try self.encodeX8664Instruction(src, Instruction{ |
| 3688 | .operand_size_64 = true, |
| 3689 | |
| 3690 | // LEA |
| 3691 | .primary_opcode_1b = 0x8D, |
| 3692 | |
| 3693 | .reg = reg, |
| 3694 | |
| 3695 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 3696 | // https://github.com/ziglang/zig/issues/6515 |
| 3697 | .modrm = @as( |
| 3698 | ?Instruction.ModrmEffectiveAddress, |
| 3699 | Instruction.ModrmEffectiveAddress{ .disp32 = @bitCast(i32, offset) }, |
| 3700 | ), |
| 3701 | }); |
| 3548 | 3702 | }, |
| 3549 | 3703 | .register => |src_reg| { |
| 3550 | 3704 | // If the registers are the same, nothing to do. |
| 3551 | 3705 | if (src_reg.id() == reg.id()) |
| 3552 | 3706 | return; |
| 3553 | 3707 | |
| 3554 | | // This is a variant of 8B /r. Since we're using 64-bit moves, we require a REX. |
| 3555 | | // This is thus three bytes: REX 0x8B R/M. |
| 3556 | | // If the destination is extended, the R field must be 1. |
| 3557 | | // If the *source* is extended, the B field must be 1. |
| 3558 | | // Since the register is being accessed directly, the R/M mode is three. The reg field (the middle |
| 3559 | | // three bits) contain the destination, and the R/M field (the lower three bits) contain the source. |
| 3560 | | try self.code.ensureCapacity(self.code.items.len + 3); |
| 3561 | | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended(), .b = src_reg.isExtended() }); |
| 3562 | | const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @as(u8, src_reg.id() & 0b111); |
| 3563 | | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, R }); |
| 3708 | // This is a variant of 8B /r. |
| 3709 | try self.encodeX8664Instruction(src, Instruction{ |
| 3710 | .operand_size_64 = reg.size() == 64, |
| 3711 | |
| 3712 | .primary_opcode_1b = 0x8B, |
| 3713 | |
| 3714 | .reg = reg, |
| 3715 | |
| 3716 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 3717 | // https://github.com/ziglang/zig/issues/6515 |
| 3718 | .modrm = @as( |
| 3719 | ?Instruction.ModrmEffectiveAddress, |
| 3720 | Instruction.ModrmEffectiveAddress{ .reg = src_reg }, |
| 3721 | ), |
| 3722 | }); |
| 3564 | 3723 | }, |
| 3565 | 3724 | .memory => |x| { |
| 3566 | 3725 | if (self.bin_file.options.pie) { |
| ... | ... | @@ -3577,6 +3736,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3577 | 3736 | } else { |
| 3578 | 3737 | return self.fail(src, "TODO implement genSetReg for PIE GOT indirection on this platform", .{}); |
| 3579 | 3738 | } |
| 3739 | |
| 3740 | // LEA reg, [<offset>] |
| 3741 | // manually do this instruction to make sure the offset into the disp32 field won't change. |
| 3580 | 3742 | try self.code.ensureCapacity(self.code.items.len + 7); |
| 3581 | 3743 | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() }); |
| 3582 | 3744 | self.code.appendSliceAssumeCapacity(&[_]u8{ |
| ... | ... | @@ -3585,10 +3747,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3585 | 3747 | }); |
| 3586 | 3748 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), 0); |
| 3587 | 3749 | |
| 3588 | | try self.code.ensureCapacity(self.code.items.len + 3); |
| 3589 | | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), .r = reg.isExtended() }); |
| 3590 | | const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); |
| 3591 | | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM }); |
| 3750 | // MOV reg, [reg] |
| 3751 | try self.encodeX8664Instruction(src, Instruction{ |
| 3752 | .operand_size_64 = reg.size() == 64, |
| 3753 | |
| 3754 | .primary_opcode_1b = 0x8B, |
| 3755 | |
| 3756 | .reg = reg, |
| 3757 | |
| 3758 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 3759 | // https://github.com/ziglang/zig/issues/6515 |
| 3760 | .modrm = @as( |
| 3761 | ?Instruction.ModrmEffectiveAddress, |
| 3762 | Instruction.ModrmEffectiveAddress{ .mem = reg }, |
| 3763 | ), |
| 3764 | }); |
| 3592 | 3765 | } else if (x <= math.maxInt(u32)) { |
| 3593 | 3766 | // Moving from memory to a register is a variant of `8B /r`. |
| 3594 | 3767 | // Since we're using 64-bit moves, we require a REX. |
| ... | ... | @@ -3612,12 +3785,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3612 | 3785 | // REX.W 0xA1 moffs64* |
| 3613 | 3786 | // moffs64* is a 64-bit offset "relative to segment base", which really just means the |
| 3614 | 3787 | // absolute address for all practical purposes. |
| 3615 | | try self.code.resize(self.code.items.len + 10); |
| 3616 | | // REX.W == 0x48 |
| 3617 | | self.code.items[self.code.items.len - 10] = 0x48; |
| 3618 | | self.code.items[self.code.items.len - 9] = 0xA1; |
| 3619 | | const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8]; |
| 3620 | | mem.writeIntLittle(u64, imm_ptr, x); |
| 3788 | |
| 3789 | try self.encodeX8664Instruction(src, Instruction{ |
| 3790 | .operand_size_64 = true, |
| 3791 | .primary_opcode_1b = 0xa1, |
| 3792 | .immediate_bytes = 8, |
| 3793 | .immediate = x, |
| 3794 | }); |
| 3621 | 3795 | } else { |
| 3622 | 3796 | // This requires two instructions; a move imm as used above, followed by an indirect load using the register |
| 3623 | 3797 | // as the address and the register as the destination. |
| ... | ... | @@ -3634,41 +3808,41 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 3634 | 3808 | // Now, the register contains the address of the value to load into it |
| 3635 | 3809 | // Currently, we're only allowing 64-bit registers, so we need the `REX.W 8B /r` variant. |
| 3636 | 3810 | // TODO: determine whether to allow other sized registers, and if so, handle them properly. |
| 3637 | | // This operation requires three bytes: REX 0x8B R/M |
| 3638 | | try self.code.ensureCapacity(self.code.items.len + 3); |
| 3639 | | // For this operation, we want R/M mode *zero* (use register indirectly), and the two register |
| 3640 | | // values must match. Thus, it's 00ABCABC where ABC is the lower three bits of the register ID. |
| 3641 | | // |
| 3642 | | // Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both* |
| 3643 | | // register operands need to be marked as extended. |
| 3644 | | self.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), .r = reg.isExtended() }); |
| 3645 | | const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()); |
| 3646 | | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8B, RM }); |
| 3811 | try self.encodeX8664Instruction(src, Instruction{ |
| 3812 | .operand_size_64 = reg.size() == 64, |
| 3813 | .primary_opcode_1b = 0x8B, |
| 3814 | .reg = reg, |
| 3815 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 3816 | // https://github.com/ziglang/zig/issues/6515 |
| 3817 | .modrm = @as( |
| 3818 | ?Instruction.ModrmEffectiveAddress, |
| 3819 | Instruction.ModrmEffectiveAddress{ .mem = reg }, |
| 3820 | ), |
| 3821 | }); |
| 3647 | 3822 | } |
| 3648 | 3823 | } |
| 3649 | 3824 | }, |
| 3650 | 3825 | .stack_offset => |unadjusted_off| { |
| 3651 | | try self.code.ensureCapacity(self.code.items.len + 7); |
| 3652 | 3826 | const size_bytes = @divExact(reg.size(), 8); |
| 3653 | 3827 | const off = unadjusted_off + size_bytes; |
| 3654 | | self.rex(.{ .w = reg.size() == 64, .r = reg.isExtended() }); |
| 3655 | | const reg_id: u8 = @truncate(u3, reg.id()); |
| 3656 | | if (off <= 128) { |
| 3657 | | // Example: 48 8b 4d 7f mov rcx,QWORD PTR [rbp+0x7f] |
| 3658 | | const RM = @as(u8, 0b01_000_101) | (reg_id << 3); |
| 3659 | | const negative_offset = @intCast(i8, -@intCast(i32, off)); |
| 3660 | | const twos_comp = @bitCast(u8, negative_offset); |
| 3661 | | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8b, RM, twos_comp }); |
| 3662 | | } else if (off <= 2147483648) { |
| 3663 | | // Example: 48 8b 8d 80 00 00 00 mov rcx,QWORD PTR [rbp+0x80] |
| 3664 | | const RM = @as(u8, 0b10_000_101) | (reg_id << 3); |
| 3665 | | const negative_offset = @intCast(i32, -@intCast(i33, off)); |
| 3666 | | const twos_comp = @bitCast(u32, negative_offset); |
| 3667 | | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x8b, RM }); |
| 3668 | | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), twos_comp); |
| 3669 | | } else { |
| 3828 | if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) { |
| 3670 | 3829 | return self.fail(src, "stack offset too large", .{}); |
| 3671 | 3830 | } |
| 3831 | const ioff = -@intCast(i32, off); |
| 3832 | try self.encodeX8664Instruction(src, Instruction{ |
| 3833 | .operand_size_64 = reg.size() == 64, |
| 3834 | .primary_opcode_1b = 0x8B, |
| 3835 | .reg = reg, |
| 3836 | // TODO: Explicit optional wrap due to stage 1 miscompilation :( |
| 3837 | // https://github.com/ziglang/zig/issues/6515 |
| 3838 | .modrm = @as( |
| 3839 | ?Instruction.ModrmEffectiveAddress, |
| 3840 | Instruction.ModrmEffectiveAddress{ .mem_disp = .{ |
| 3841 | .reg = Register.ebp, |
| 3842 | .disp = ioff, |
| 3843 | } }, |
| 3844 | ), |
| 3845 | }); |
| 3672 | 3846 | }, |
| 3673 | 3847 | }, |
| 3674 | 3848 | else => return self.fail(src, "TODO implement getSetReg for {}", .{self.target.cpu.arch}), |