| ... | @@ -133,6 +133,11 @@ pub fn lowerMir(emit: *Emit) InnerError!void { | ... | @@ -133,6 +133,11 @@ pub fn lowerMir(emit: *Emit) InnerError!void { |
| 133 | .lea => try emit.mirLea(inst), | 133 | .lea => try emit.mirLea(inst), |
| 134 | .lea_pie => try emit.mirLeaPie(inst), | 134 | .lea_pie => try emit.mirLeaPie(inst), |
| 135 | | 135 | |
| | 136 | .shl => try emit.mirShift(.shl, inst), |
| | 137 | .sal => try emit.mirShift(.sal, inst), |
| | 138 | .shr => try emit.mirShift(.shr, inst), |
| | 139 | .sar => try emit.mirShift(.sar, inst), |
| | 140 | |
| 136 | .imul_complex => try emit.mirIMulComplex(inst), | 141 | .imul_complex => try emit.mirIMulComplex(inst), |
| 137 | | 142 | |
| 138 | .push => try emit.mirPushPop(.push, inst), | 143 | .push => try emit.mirPushPop(.push, inst), |
| ... | @@ -653,6 +658,31 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { | ... | @@ -653,6 +658,31 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 653 | return lowerToFdEnc(.mov, ops.reg1, imm, emit.code); | 658 | return lowerToFdEnc(.mov, ops.reg1, imm, emit.code); |
| 654 | } | 659 | } |
| 655 | | 660 | |
| | 661 | fn mirShift(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { |
| | 662 | const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); |
| | 663 | switch (ops.flags) { |
| | 664 | 0b00 => { |
| | 665 | // sal reg1, 1 |
| | 666 | // M1 |
| | 667 | return lowerToM1Enc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); |
| | 668 | }, |
| | 669 | 0b01 => { |
| | 670 | // sal reg1, .cl |
| | 671 | // MC |
| | 672 | return lowerToMcEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); |
| | 673 | }, |
| | 674 | 0b10 => { |
| | 675 | // sal reg1, imm8 |
| | 676 | // MI |
| | 677 | const imm = @truncate(u8, emit.mir.instructions.items(.data)[inst].imm); |
| | 678 | return lowerToMiImm8Enc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code); |
| | 679 | }, |
| | 680 | 0b11 => { |
| | 681 | return emit.fail("TODO unused variant: SHIFT reg1, 0b11", .{}); |
| | 682 | }, |
| | 683 | } |
| | 684 | } |
| | 685 | |
| 656 | fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { | 686 | fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 657 | const tag = emit.mir.instructions.items(.tag)[inst]; | 687 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 658 | assert(tag == .imul_complex); | 688 | assert(tag == .imul_complex); |
| ... | @@ -743,13 +773,13 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { | ... | @@ -743,13 +773,13 @@ fn mirLeaPie(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 743 | emit.code, | 773 | emit.code, |
| 744 | ); | 774 | ); |
| 745 | const end_offset = emit.code.items.len; | 775 | const end_offset = emit.code.items.len; |
| 746 | const reloc_type = switch (ops.flags) { | | |
| 747 | 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), | | |
| 748 | 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), | | |
| 749 | else => return emit.fail("TODO unused LEA PIE variants 0b10 and 0b11", .{}), | | |
| 750 | }; | | |
| 751 | const sym_index = emit.mir.instructions.items(.data)[inst].linker_sym_index; | 776 | const sym_index = emit.mir.instructions.items(.data)[inst].linker_sym_index; |
| 752 | if (emit.bin_file.cast(link.File.MachO)) |macho_file| { | 777 | if (emit.bin_file.cast(link.File.MachO)) |macho_file| { |
| | 778 | const reloc_type = switch (ops.flags) { |
| | 779 | 0b00 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), |
| | 780 | 0b01 => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED), |
| | 781 | else => return emit.fail("TODO unused LEA PIE variants 0b10 and 0b11", .{}), |
| | 782 | }; |
| 753 | const decl = macho_file.active_decl.?; | 783 | const decl = macho_file.active_decl.?; |
| 754 | try decl.link.macho.relocs.append(emit.bin_file.allocator, .{ | 784 | try decl.link.macho.relocs.append(emit.bin_file.allocator, .{ |
| 755 | .offset = @intCast(u32, end_offset - 4), | 785 | .offset = @intCast(u32, end_offset - 4), |
| ... | @@ -1064,6 +1094,10 @@ const Tag = enum { | ... | @@ -1064,6 +1094,10 @@ const Tag = enum { |
| 1064 | setng, | 1094 | setng, |
| 1065 | setnle, | 1095 | setnle, |
| 1066 | setg, | 1096 | setg, |
| | 1097 | shl, |
| | 1098 | sal, |
| | 1099 | shr, |
| | 1100 | sar, |
| 1067 | | 1101 | |
| 1068 | fn isSetCC(tag: Tag) bool { | 1102 | fn isSetCC(tag: Tag) bool { |
| 1069 | return switch (tag) { | 1103 | return switch (tag) { |
| ... | @@ -1119,9 +1153,18 @@ const Encoding = enum { | ... | @@ -1119,9 +1153,18 @@ const Encoding = enum { |
| 1119 | /// OP imm32 | 1153 | /// OP imm32 |
| 1120 | i, | 1154 | i, |
| 1121 | | 1155 | |
| | 1156 | /// OP r/m64, 1 |
| | 1157 | m1, |
| | 1158 | |
| | 1159 | /// OP r/m64, .cl |
| | 1160 | mc, |
| | 1161 | |
| 1122 | /// OP r/m64, imm32 | 1162 | /// OP r/m64, imm32 |
| 1123 | mi, | 1163 | mi, |
| 1124 | | 1164 | |
| | 1165 | /// OP r/m64, imm8 |
| | 1166 | mi8, |
| | 1167 | |
| 1125 | /// OP r/m64, r64 | 1168 | /// OP r/m64, r64 |
| 1126 | mr, | 1169 | mr, |
| 1127 | | 1170 | |
| ... | @@ -1230,12 +1273,25 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { | ... | @@ -1230,12 +1273,25 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { |
| 1230 | .ret_far => OpCode.oneByte(0xca), | 1273 | .ret_far => OpCode.oneByte(0xca), |
| 1231 | else => null, | 1274 | else => null, |
| 1232 | }, | 1275 | }, |
| | 1276 | .m1 => return switch (tag) { |
| | 1277 | .shl, .sal, .shr, .sar => OpCode.oneByte(if (is_one_byte) 0xd0 else 0xd1), |
| | 1278 | else => null, |
| | 1279 | }, |
| | 1280 | .mc => return switch (tag) { |
| | 1281 | .shl, .sal, .shr, .sar => OpCode.oneByte(if (is_one_byte) 0xd2 else 0xd3), |
| | 1282 | else => null, |
| | 1283 | }, |
| 1233 | .mi => return switch (tag) { | 1284 | .mi => return switch (tag) { |
| 1234 | .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp => OpCode.oneByte(if (is_one_byte) 0x80 else 0x81), | 1285 | .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp => OpCode.oneByte(if (is_one_byte) 0x80 else 0x81), |
| 1235 | .mov => OpCode.oneByte(if (is_one_byte) 0xc6 else 0xc7), | 1286 | .mov => OpCode.oneByte(if (is_one_byte) 0xc6 else 0xc7), |
| 1236 | .@"test" => OpCode.oneByte(if (is_one_byte) 0xf6 else 0xf7), | 1287 | .@"test" => OpCode.oneByte(if (is_one_byte) 0xf6 else 0xf7), |
| 1237 | else => null, | 1288 | else => null, |
| 1238 | }, | 1289 | }, |
| | 1290 | .mi8 => return switch (tag) { |
| | 1291 | .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp => OpCode.oneByte(0x83), |
| | 1292 | .shl, .sal, .shr, .sar => OpCode.oneByte(if (is_one_byte) 0xc0 else 0xc1), |
| | 1293 | else => null, |
| | 1294 | }, |
| 1239 | .mr => return switch (tag) { | 1295 | .mr => return switch (tag) { |
| 1240 | .adc => OpCode.oneByte(if (is_one_byte) 0x10 else 0x11), | 1296 | .adc => OpCode.oneByte(if (is_one_byte) 0x10 else 0x11), |
| 1241 | .add => OpCode.oneByte(if (is_one_byte) 0x00 else 0x01), | 1297 | .add => OpCode.oneByte(if (is_one_byte) 0x00 else 0x01), |
| ... | @@ -1331,6 +1387,11 @@ inline fn getModRmExt(tag: Tag) ?u3 { | ... | @@ -1331,6 +1387,11 @@ inline fn getModRmExt(tag: Tag) ?u3 { |
| 1331 | .setnle, | 1387 | .setnle, |
| 1332 | .setg, | 1388 | .setg, |
| 1333 | => 0x0, | 1389 | => 0x0, |
| | 1390 | .shl, |
| | 1391 | .sal, |
| | 1392 | => 0x4, |
| | 1393 | .shr => 0x5, |
| | 1394 | .sar => 0x7, |
| 1334 | else => null, | 1395 | else => null, |
| 1335 | }; | 1396 | }; |
| 1336 | } | 1397 | } |
| ... | @@ -1528,8 +1589,8 @@ fn lowerToDEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) InnerError!void { | ... | @@ -1528,8 +1589,8 @@ fn lowerToDEnc(tag: Tag, imm: u32, code: *std.ArrayList(u8)) InnerError!void { |
| 1528 | encoder.imm32(@bitCast(i32, imm)); | 1589 | encoder.imm32(@bitCast(i32, imm)); |
| 1529 | } | 1590 | } |
| 1530 | | 1591 | |
| 1531 | fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { | 1592 | fn lowerToMxEnc(tag: Tag, reg_or_mem: RegisterOrMemory, enc: Encoding, code: *std.ArrayList(u8)) InnerError!void { |
| 1532 | const opc = getOpCode(tag, .m, false).?; | 1593 | const opc = getOpCode(tag, enc, reg_or_mem.size() == 8).?; |
| 1533 | const modrm_ext = getModRmExt(tag).?; | 1594 | const modrm_ext = getModRmExt(tag).?; |
| 1534 | switch (reg_or_mem) { | 1595 | switch (reg_or_mem) { |
| 1535 | .register => |reg| { | 1596 | .register => |reg| { |
| ... | @@ -1537,11 +1598,9 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) | ... | @@ -1537,11 +1598,9 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) |
| 1537 | if (reg.size() == 16) { | 1598 | if (reg.size() == 16) { |
| 1538 | encoder.prefix16BitMode(); | 1599 | encoder.prefix16BitMode(); |
| 1539 | } | 1600 | } |
| | 1601 | const wide = if (tag == .jmp_near) false else setRexWRegister(reg); |
| 1540 | encoder.rex(.{ | 1602 | encoder.rex(.{ |
| 1541 | .w = switch (reg) { | 1603 | .w = wide, |
| 1542 | .ah, .bh, .ch, .dh => true, | | |
| 1543 | else => false, | | |
| 1544 | }, | | |
| 1545 | .b = reg.isExtended(), | 1604 | .b = reg.isExtended(), |
| 1546 | }); | 1605 | }); |
| 1547 | opc.encode(encoder); | 1606 | opc.encode(encoder); |
| ... | @@ -1553,8 +1612,9 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) | ... | @@ -1553,8 +1612,9 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) |
| 1553 | encoder.prefix16BitMode(); | 1612 | encoder.prefix16BitMode(); |
| 1554 | } | 1613 | } |
| 1555 | if (mem_op.base) |base| { | 1614 | if (mem_op.base) |base| { |
| | 1615 | const wide = if (tag == .jmp_near) false else mem_op.ptr_size == .qword_ptr; |
| 1556 | encoder.rex(.{ | 1616 | encoder.rex(.{ |
| 1557 | .w = false, | 1617 | .w = wide, |
| 1558 | .b = base.isExtended(), | 1618 | .b = base.isExtended(), |
| 1559 | }); | 1619 | }); |
| 1560 | } | 1620 | } |
| ... | @@ -1564,6 +1624,18 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) | ... | @@ -1564,6 +1624,18 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) |
| 1564 | } | 1624 | } |
| 1565 | } | 1625 | } |
| 1566 | | 1626 | |
| | 1627 | fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { |
| | 1628 | return lowerToMxEnc(tag, reg_or_mem, .m, code); |
| | 1629 | } |
| | 1630 | |
| | 1631 | fn lowerToM1Enc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { |
| | 1632 | return lowerToMxEnc(tag, reg_or_mem, .m1, code); |
| | 1633 | } |
| | 1634 | |
| | 1635 | fn lowerToMcEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { |
| | 1636 | return lowerToMxEnc(tag, reg_or_mem, .mc, code); |
| | 1637 | } |
| | 1638 | |
| 1567 | fn lowerToTdEnc(tag: Tag, moffs: u64, reg: Register, code: *std.ArrayList(u8)) InnerError!void { | 1639 | fn lowerToTdEnc(tag: Tag, moffs: u64, reg: Register, code: *std.ArrayList(u8)) InnerError!void { |
| 1568 | return lowerToTdFdEnc(tag, reg, moffs, code, true); | 1640 | return lowerToTdFdEnc(tag, reg, moffs, code, true); |
| 1569 | } | 1641 | } |
| ... | @@ -1614,9 +1686,15 @@ fn lowerToOiEnc(tag: Tag, reg: Register, imm: u64, code: *std.ArrayList(u8)) Inn | ... | @@ -1614,9 +1686,15 @@ fn lowerToOiEnc(tag: Tag, reg: Register, imm: u64, code: *std.ArrayList(u8)) Inn |
| 1614 | } | 1686 | } |
| 1615 | } | 1687 | } |
| 1616 | | 1688 | |
| 1617 | fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.ArrayList(u8)) InnerError!void { | 1689 | fn lowerToMiXEnc( |
| | 1690 | tag: Tag, |
| | 1691 | reg_or_mem: RegisterOrMemory, |
| | 1692 | imm: u32, |
| | 1693 | enc: Encoding, |
| | 1694 | code: *std.ArrayList(u8), |
| | 1695 | ) InnerError!void { |
| 1618 | const modrm_ext = getModRmExt(tag).?; | 1696 | const modrm_ext = getModRmExt(tag).?; |
| 1619 | const opc = getOpCode(tag, .mi, reg_or_mem.size() == 8).?; | 1697 | const opc = getOpCode(tag, enc, reg_or_mem.size() == 8).?; |
| 1620 | switch (reg_or_mem) { | 1698 | switch (reg_or_mem) { |
| 1621 | .register => |dst_reg| { | 1699 | .register => |dst_reg| { |
| 1622 | const encoder = try Encoder.init(code, 7); | 1700 | const encoder = try Encoder.init(code, 7); |
| ... | @@ -1632,7 +1710,7 @@ fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.Arr | ... | @@ -1632,7 +1710,7 @@ fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.Arr |
| 1632 | }); | 1710 | }); |
| 1633 | opc.encode(encoder); | 1711 | opc.encode(encoder); |
| 1634 | encoder.modRm_direct(modrm_ext, dst_reg.lowId()); | 1712 | encoder.modRm_direct(modrm_ext, dst_reg.lowId()); |
| 1635 | encodeImm(encoder, imm, dst_reg.size()); | 1713 | encodeImm(encoder, imm, if (enc == .mi8) 8 else dst_reg.size()); |
| 1636 | }, | 1714 | }, |
| 1637 | .memory => |dst_mem| { | 1715 | .memory => |dst_mem| { |
| 1638 | const encoder = try Encoder.init(code, 12); | 1716 | const encoder = try Encoder.init(code, 12); |
| ... | @@ -1651,11 +1729,19 @@ fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.Arr | ... | @@ -1651,11 +1729,19 @@ fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.Arr |
| 1651 | } | 1729 | } |
| 1652 | opc.encode(encoder); | 1730 | opc.encode(encoder); |
| 1653 | dst_mem.encode(encoder, modrm_ext); | 1731 | dst_mem.encode(encoder, modrm_ext); |
| 1654 | encodeImm(encoder, imm, dst_mem.ptr_size.size()); | 1732 | encodeImm(encoder, imm, if (enc == .mi8) 8 else dst_mem.ptr_size.size()); |
| 1655 | }, | 1733 | }, |
| 1656 | } | 1734 | } |
| 1657 | } | 1735 | } |
| 1658 | | 1736 | |
| | 1737 | fn lowerToMiImm8Enc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u8, code: *std.ArrayList(u8)) InnerError!void { |
| | 1738 | return lowerToMiXEnc(tag, reg_or_mem, imm, .mi8, code); |
| | 1739 | } |
| | 1740 | |
| | 1741 | fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: u32, code: *std.ArrayList(u8)) InnerError!void { |
| | 1742 | return lowerToMiXEnc(tag, reg_or_mem, imm, .mi, code); |
| | 1743 | } |
| | 1744 | |
| 1659 | fn lowerToRmEnc( | 1745 | fn lowerToRmEnc( |
| 1660 | tag: Tag, | 1746 | tag: Tag, |
| 1661 | reg: Register, | 1747 | reg: Register, |
| ... | @@ -1902,6 +1988,9 @@ test "lower MI encoding" { | ... | @@ -1902,6 +1988,9 @@ test "lower MI encoding" { |
| 1902 | emit.lowered(), | 1988 | emit.lowered(), |
| 1903 | "mov qword ptr [rcx*2 + 0x10000000], 0x10", | 1989 | "mov qword ptr [rcx*2 + 0x10000000], 0x10", |
| 1904 | ); | 1990 | ); |
| | 1991 | |
| | 1992 | try lowerToMiImm8Enc(.add, RegisterOrMemory.reg(.rax), 0x10, emit.code()); |
| | 1993 | try expectEqualHexStrings("\x48\x83\xC0\x10", emit.lowered(), "add rax, 0x10"); |
| 1905 | } | 1994 | } |
| 1906 | | 1995 | |
| 1907 | test "lower RM encoding" { | 1996 | test "lower RM encoding" { |
| ... | @@ -2100,6 +2189,41 @@ test "lower M encoding" { | ... | @@ -2100,6 +2189,41 @@ test "lower M encoding" { |
| 2100 | try expectEqualHexStrings("\x41\x0F\x97\xC3", emit.lowered(), "seta r11b"); | 2189 | try expectEqualHexStrings("\x41\x0F\x97\xC3", emit.lowered(), "seta r11b"); |
| 2101 | } | 2190 | } |
| 2102 | | 2191 | |
| | 2192 | test "lower M1 and MC encodings" { |
| | 2193 | var emit = TestEmit.init(); |
| | 2194 | defer emit.deinit(); |
| | 2195 | try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12), emit.code()); |
| | 2196 | try expectEqualHexStrings("\x49\xD1\xE4", emit.lowered(), "sal r12, 1"); |
| | 2197 | try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12d), emit.code()); |
| | 2198 | try expectEqualHexStrings("\x41\xD1\xE4", emit.lowered(), "sal r12d, 1"); |
| | 2199 | try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12w), emit.code()); |
| | 2200 | try expectEqualHexStrings("\x66\x41\xD1\xE4", emit.lowered(), "sal r12w, 1"); |
| | 2201 | try lowerToM1Enc(.sal, RegisterOrMemory.reg(.r12b), emit.code()); |
| | 2202 | try expectEqualHexStrings("\x41\xD0\xE4", emit.lowered(), "sal r12b, 1"); |
| | 2203 | try lowerToM1Enc(.sal, RegisterOrMemory.reg(.rax), emit.code()); |
| | 2204 | try expectEqualHexStrings("\x48\xD1\xE0", emit.lowered(), "sal rax, 1"); |
| | 2205 | try lowerToM1Enc(.sal, RegisterOrMemory.reg(.eax), emit.code()); |
| | 2206 | try expectEqualHexStrings("\xD1\xE0", emit.lowered(), "sal eax, 1"); |
| | 2207 | try lowerToM1Enc(.sal, RegisterOrMemory.mem(.qword_ptr, .{ |
| | 2208 | .disp = @bitCast(u32, @as(i32, -0x10)), |
| | 2209 | .base = .rbp, |
| | 2210 | }), emit.code()); |
| | 2211 | try expectEqualHexStrings("\x48\xD1\x65\xF0", emit.lowered(), "sal qword ptr [rbp - 0x10], 1"); |
| | 2212 | try lowerToM1Enc(.sal, RegisterOrMemory.mem(.dword_ptr, .{ |
| | 2213 | .disp = @bitCast(u32, @as(i32, -0x10)), |
| | 2214 | .base = .rbp, |
| | 2215 | }), emit.code()); |
| | 2216 | try expectEqualHexStrings("\xD1\x65\xF0", emit.lowered(), "sal dword ptr [rbp - 0x10], 1"); |
| | 2217 | |
| | 2218 | try lowerToMcEnc(.shr, RegisterOrMemory.reg(.r12), emit.code()); |
| | 2219 | try expectEqualHexStrings("\x49\xD3\xEC", emit.lowered(), "shr r12, cl"); |
| | 2220 | try lowerToMcEnc(.shr, RegisterOrMemory.reg(.rax), emit.code()); |
| | 2221 | try expectEqualHexStrings("\x48\xD3\xE8", emit.lowered(), "shr rax, cl"); |
| | 2222 | |
| | 2223 | try lowerToMcEnc(.sar, RegisterOrMemory.reg(.rsi), emit.code()); |
| | 2224 | try expectEqualHexStrings("\x48\xD3\xFE", emit.lowered(), "sar rsi, cl"); |
| | 2225 | } |
| | 2226 | |
| 2103 | test "lower O encoding" { | 2227 | test "lower O encoding" { |
| 2104 | var emit = TestEmit.init(); | 2228 | var emit = TestEmit.init(); |
| 2105 | defer emit.deinit(); | 2229 | defer emit.deinit(); |