authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-12-21 16:52:50+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-12-22 18:10:52+01:00
log2b5de9403d51561642b241f5940d5e7630993849
treec935392c99b528b2c27cb68ef82d5fb3aff9f2da
parent5156ccd552fa6d0959c8349cbbe176099ac4c356

stage2: create generic lowering fns for MI, RM, and MR encodings

This way I am hopeful they can be reused for every MIR lowering function which follows a given encoding. Currently, support MI, RM and MR encodings without SIB scaling.

3 files changed, 376 insertions(+), 679 deletions(-)

src/arch/x86_64/Emit.zig+370-673
...@@ -48,33 +48,6 @@ const InnerError = error{...@@ -48,33 +48,6 @@ const InnerError = error{
48 EmitFail,48 EmitFail,
49};49};
5050
51const EmitResult = union(enum) {
52 ok: void,
53 err: *ErrorMsg,
54
55 fn ok() EmitResult {
56 return EmitResult{ .ok = .{} };
57 }
58
59 fn err(
60 allocator: Allocator,
61 src_loc: Module.SrcLoc,
62 comptime format: []const u8,
63 args: anytype,
64 ) error{OutOfMemory}!EmitResult {
65 return EmitResult{
66 .err = try ErrorMsg.create(allocator, src_loc, format, args),
67 };
68 }
69
70 fn deinit(res: EmitResult, allocator: Allocator) void {
71 switch (res) {
72 .ok => {},
73 .err => |err_msg| err_msg.destroy(allocator),
74 }
75 }
76};
77
78const Reloc = struct {51const Reloc = struct {
79 /// Offset of the instruction.52 /// Offset of the instruction.
80 source: u64,53 source: u64,
...@@ -184,15 +157,9 @@ pub fn deinit(emit: *Emit) void {...@@ -184,15 +157,9 @@ pub fn deinit(emit: *Emit) void {
184}157}
185158
186fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {159fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError {
187 @setCold(true);
188 const err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args);
189 return emit.failWithErrorMsg(err_msg);
190}
191
192fn failWithErrorMsg(emit: *Emit, err_msg: *ErrorMsg) InnerError {
193 @setCold(true);160 @setCold(true);
194 assert(emit.err_msg == null);161 assert(emit.err_msg == null);
195 emit.err_msg = err_msg;162 emit.err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args);
196 return error.EmitFail;163 return error.EmitFail;
197}164}
198165
...@@ -565,7 +532,7 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {...@@ -565,7 +532,7 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
565 }532 }
566}533}
567534
568const EncType = enum {535const Encoding = enum {
569 /// OP r/m64, imm32536 /// OP r/m64, imm32
570 mi,537 mi,
571538
...@@ -578,11 +545,11 @@ const EncType = enum {...@@ -578,11 +545,11 @@ const EncType = enum {
578545
579const OpCode = struct {546const OpCode = struct {
580 opc: u8,547 opc: u8,
581 /// Only used if `EncType == .mi`.548 /// Only used if `Encoding == .mi`.
582 modrm_ext: u3,549 modrm_ext: u3,
583};550};
584551
585inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode {552inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) OpCode {
586 switch (enc) {553 switch (enc) {
587 .mi => return switch (tag) {554 .mi => return switch (tag) {
588 .adc => .{ .opc = 0x81, .modrm_ext = 0x2 },555 .adc => .{ .opc = 0x81, .modrm_ext = 0x2 },
...@@ -629,247 +596,298 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode {...@@ -629,247 +596,298 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode {
629 }596 }
630}597}
631598
632fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void {599const ScaleIndexBase = struct {
633 const res = try mirArithImpl(600 scale: u2,
634 emit.bin_file.allocator,601 index_reg: ?Register,
635 tag,602 base_reg: ?Register,
636 emit.mir.instructions,603};
637 emit.mir.extra,604
638 inst,605const Memory = struct {
639 emit.src_loc,606 reg: ?Register,
640 emit.code,607 disp: i32,
641 );608 sib: ?ScaleIndexBase = null,
642 switch (res) {609};
643 .ok => {},610
644 .err => |err_msg| return emit.failWithErrorMsg(err_msg),611const RegisterOrMemory = union(enum) {
612 register: Register,
613 memory: Memory,
614
615 fn reg(register: Register) RegisterOrMemory {
616 return .{ .register = register };
645 }617 }
646}
647618
648fn mirArithImpl(619 fn mem(register: ?Register, disp: i32) RegisterOrMemory {
649 allocator: Allocator,620 return .{
621 .memory = .{
622 .reg = register,
623 .disp = disp,
624 },
625 };
626 }
627};
628
629fn lowerToMiEnc(
650 tag: Mir.Inst.Tag,630 tag: Mir.Inst.Tag,
651 mir_instructions: std.MultiArrayList(Mir.Inst).Slice,631 reg_or_mem: RegisterOrMemory,
652 mir_extra: []const u32,632 imm: i32,
653 inst: Mir.Inst.Index,
654 src_loc: Module.SrcLoc,
655 code: *std.ArrayList(u8),633 code: *std.ArrayList(u8),
656) error{OutOfMemory}!EmitResult {634) InnerError!void {
657 const ops = Mir.Ops.decode(mir_instructions.items(.ops)[inst]);635 const opcode = getOpCode(tag, .mi);
658 switch (ops.flags) {636 switch (reg_or_mem) {
659 0b00 => blk: {637 .register => |dst_reg| {
660 if (ops.reg2 == .none) {638 const opc: u8 = if (dst_reg.size() == 8) opcode.opc - 1 else opcode.opc;
661 // mov reg1, imm32639 const encoder = try Encoder.init(code, 7);
662 // MI640 if (dst_reg.size() == 16) {
663 const imm = mir_instructions.items(.data)[inst].imm;641 // 0x66 prefix switches to the non-default size; here we assume a switch from
664 const opcode = getArithOpCode(tag, .mi);642 // the default 32bits to 16bits operand-size.
665 const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;643 // More info: https://www.cs.uni-potsdam.de/desn/lehre/ss15/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf#page=32&zoom=auto,-159,773
666 const encoder = try Encoder.init(code, 7);644 encoder.opcode_1byte(0x66);
667 if (ops.reg1.size() == 16) {645 }
668 // 0x66 prefix switches to the non-default size; here we assume a switch from646 encoder.rex(.{
669 // the default 32bits to 16bits operand-size.647 .w = dst_reg.size() == 64,
670 // More info: https://www.cs.uni-potsdam.de/desn/lehre/ss15/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf#page=32&zoom=auto,-159,773648 .b = dst_reg.isExtended(),
671 encoder.opcode_1byte(0x66);649 });
672 }650 encoder.opcode_1byte(opc);
651 encoder.modRm_direct(opcode.modrm_ext, dst_reg.lowId());
652 switch (dst_reg.size()) {
653 8 => {
654 const imm8 = try math.cast(i8, imm);
655 encoder.imm8(imm8);
656 },
657 16 => {
658 const imm16 = try math.cast(i16, imm);
659 encoder.imm16(imm16);
660 },
661 32, 64 => encoder.imm32(imm),
662 else => unreachable,
663 }
664 },
665 .memory => |dst_mem| {
666 const encoder = try Encoder.init(code, 12);
667 if (dst_mem.reg) |dst_reg| {
668 // Register dst_reg can either be 64bit or 32bit in size.
669 // TODO for memory operand, immediate operand pair, we currently
670 // have no way of flagging whether the immediate can be 8-, 16- or
671 // 32-bit and whether the corresponding memory operand is respectively
672 // a byte, word or dword ptr.
673 // TODO we currently don't have a way to flag imm32 64bit sign extended
674 if (dst_reg.size() != 64) return error.EmitFail;
673 encoder.rex(.{675 encoder.rex(.{
674 .w = ops.reg1.size() == 64,676 .w = false,
675 .b = ops.reg1.isExtended(),677 .b = dst_reg.isExtended(),
676 });678 });
677 encoder.opcode_1byte(opc);679 encoder.opcode_1byte(opcode.opc);
678 encoder.modRm_direct(opcode.modrm_ext, ops.reg1.lowId());680 if (dst_mem.disp == 0) {
679 switch (ops.reg1.size()) {681 encoder.modRm_indirectDisp0(opcode.modrm_ext, dst_reg.lowId());
680 8 => {682 } else if (immOpSize(dst_mem.disp) == 8) {
681 const imm8 = math.cast(i8, imm) catch {683 encoder.modRm_indirectDisp8(opcode.modrm_ext, dst_reg.lowId());
682 return EmitResult.err(684 encoder.disp8(@intCast(i8, dst_mem.disp));
683 allocator,685 } else {
684 src_loc,686 if (dst_reg.lowId() == 4) {
685 "size mismatch: sizeof {} != sizeof 0x{x}",687 encoder.modRm_SIBDisp32(opcode.modrm_ext);
686 .{688 encoder.sib_baseDisp32(dst_reg.lowId());
687 ops.reg1,689 encoder.disp32(dst_mem.disp);
688 imm,690 } else {
689 },691 encoder.modRm_indirectDisp32(opcode.modrm_ext, dst_reg.lowId());
690 );692 encoder.disp32(dst_mem.disp);
691 };693 }
692 encoder.imm8(imm8);
693 },
694 16 => {
695 const imm16 = math.cast(i16, imm) catch {
696 return EmitResult.err(
697 allocator,
698 src_loc,
699 "size mismatch: sizeof {} != sizeof 0x{x}",
700 .{
701 ops.reg1,
702 imm,
703 },
704 );
705 };
706 encoder.imm16(imm16);
707 },
708 32, 64 => {
709 encoder.imm32(imm);
710 },
711 else => unreachable,
712 }694 }
713 break :blk;695 } else {
714 }696 encoder.opcode_1byte(opcode.opc);
715 // mov reg1, reg2697 encoder.modRm_SIBDisp0(opcode.modrm_ext);
716 // MR698 encoder.sib_disp32();
717 if (ops.reg1.size() != ops.reg2.size()) {699 encoder.disp32(dst_mem.disp);
718 return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != sizeof {}", .{
719 ops.reg1,
720 ops.reg2,
721 });
722 }700 }
723 const opcode = getArithOpCode(tag, .mr);701 encoder.imm32(imm);
724 const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;702 },
703 }
704}
705
706fn lowerToRmEnc(
707 tag: Mir.Inst.Tag,
708 reg: Register,
709 reg_or_mem: RegisterOrMemory,
710 code: *std.ArrayList(u8),
711) InnerError!void {
712 const opcode = getOpCode(tag, .rm);
713 const opc: u8 = if (reg.size() == 8) opcode.opc - 1 else opcode.opc;
714 switch (reg_or_mem) {
715 .register => |src_reg| {
716 if (reg.size() != src_reg.size()) return error.EmitFail;
725 const encoder = try Encoder.init(code, 3);717 const encoder = try Encoder.init(code, 3);
726 encoder.rex(.{718 encoder.rex(.{
727 .w = ops.reg1.size() == 64 and ops.reg2.size() == 64,719 .w = reg.size() == 64,
728 .r = ops.reg2.isExtended(),720 .r = reg.isExtended(),
729 .b = ops.reg1.isExtended(),721 .b = src_reg.isExtended(),
730 });722 });
731 encoder.opcode_1byte(opc);723 encoder.opcode_1byte(opc);
732 encoder.modRm_direct(ops.reg2.lowId(), ops.reg1.lowId());724 encoder.modRm_direct(reg.lowId(), src_reg.lowId());
733 },725 },
734 0b01 => blk: {726 .memory => |src_mem| {
735 const imm = mir_instructions.items(.data)[inst].imm;727 const encoder = try Encoder.init(code, 9);
736 const opcode = getArithOpCode(tag, .rm);728 if (reg.size() == 16) {
737 const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;729 encoder.opcode_1byte(0x66);
738 if (ops.reg2 == .none) {730 }
739 // mov reg1, [imm32]731 if (src_mem.reg) |src_reg| {
740 // RM732 // TODO handle 32-bit base register - requires prefix 0x67
741 const encoder = try Encoder.init(code, 9);733 // Intel Manual, Vol 1, chapter 3.6 and 3.6.1
742 if (ops.reg1.size() == 16) {734 if (src_reg.size() != 64) return error.EmitFail;
743 encoder.opcode_1byte(0x66);735 encoder.rex(.{
736 .w = reg.size() == 64,
737 .r = reg.isExtended(),
738 .b = src_reg.isExtended(),
739 });
740 encoder.opcode_1byte(opc);
741 if (src_mem.disp == 0) {
742 encoder.modRm_indirectDisp0(reg.lowId(), src_reg.lowId());
743 } else if (immOpSize(src_mem.disp) == 8) {
744 encoder.modRm_indirectDisp8(reg.lowId(), src_reg.lowId());
745 encoder.disp8(@intCast(i8, src_mem.disp));
746 } else {
747 if (src_reg.lowId() == 4) {
748 encoder.modRm_SIBDisp32(reg.lowId());
749 encoder.sib_baseDisp32(src_reg.lowId());
750 encoder.disp32(src_mem.disp);
751 } else {
752 encoder.modRm_indirectDisp32(reg.lowId(), src_reg.lowId());
753 encoder.disp32(src_mem.disp);
754 }
744 }755 }
756 } else {
745 encoder.rex(.{757 encoder.rex(.{
746 .w = ops.reg1.size() == 64,758 .w = reg.size() == 64,
747 .r = ops.reg1.isExtended(),759 .r = reg.isExtended(),
748 });760 });
749 encoder.opcode_1byte(opc);761 encoder.opcode_1byte(opc);
750 encoder.modRm_SIBDisp0(ops.reg1.lowId());762 encoder.modRm_SIBDisp0(reg.lowId());
751 encoder.sib_disp32();763 encoder.sib_disp32();
752 encoder.disp32(imm);764 encoder.disp32(src_mem.disp);
753 break :blk;
754 }
755 // mov reg1, [reg2 + imm32]
756 // RM
757 // TODO handle 32-bit base register - requires prefix 0x67
758 // Intel Manual, Vol 1, chapter 3.6 and 3.6.1
759 if (ops.reg2.size() != 64) {
760 return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg2});
761 }
762 const encoder = try Encoder.init(code, 8);
763 if (ops.reg1.size() == 16) {
764 encoder.opcode_1byte(0x66);
765 }765 }
766 },
767 }
768}
769
770fn lowerToMrEnc(
771 tag: Mir.Inst.Tag,
772 reg_or_mem: RegisterOrMemory,
773 reg: Register,
774 code: *std.ArrayList(u8),
775) InnerError!void {
776 // We use size of source register reg to work out which
777 // variant of memory ptr to pick:
778 // * reg is 64bit - qword ptr
779 // * reg is 32bit - dword ptr
780 // * reg is 16bit - word ptr
781 // * reg is 8bit - byte ptr
782 const opcode = getOpCode(tag, .mr);
783 const opc: u8 = if (reg.size() == 8) opcode.opc - 1 else opcode.opc;
784 switch (reg_or_mem) {
785 .register => |dst_reg| {
786 if (dst_reg.size() != reg.size()) return error.EmitFail;
787 const encoder = try Encoder.init(code, 3);
766 encoder.rex(.{788 encoder.rex(.{
767 .w = ops.reg1.size() == 64,789 .w = dst_reg.size() == 64,
768 .r = ops.reg1.isExtended(),790 .r = reg.isExtended(),
769 .b = ops.reg2.isExtended(),791 .b = dst_reg.isExtended(),
770 });792 });
771 encoder.opcode_1byte(opc);793 encoder.opcode_1byte(opc);
772 if (immOpSize(imm) == 8) {794 encoder.modRm_direct(reg.lowId(), dst_reg.lowId());
773 encoder.modRm_indirectDisp8(ops.reg1.lowId(), ops.reg2.lowId());795 },
774 encoder.disp8(@intCast(i8, imm));796 .memory => |dst_mem| {
797 const encoder = try Encoder.init(code, 9);
798 if (reg.size() == 16) {
799 encoder.opcode_1byte(0x66);
800 }
801 if (dst_mem.reg) |dst_reg| {
802 if (dst_reg.size() != 64) return error.EmitFail;
803 encoder.rex(.{
804 .w = reg.size() == 64,
805 .r = reg.isExtended(),
806 .b = dst_reg.isExtended(),
807 });
808 encoder.opcode_1byte(opc);
809 if (dst_mem.disp == 0) {
810 encoder.modRm_indirectDisp0(reg.lowId(), dst_reg.lowId());
811 } else if (immOpSize(dst_mem.disp) == 8) {
812 encoder.modRm_indirectDisp8(reg.lowId(), dst_reg.lowId());
813 encoder.disp8(@intCast(i8, dst_mem.disp));
814 } else {
815 if (dst_reg.lowId() == 4) {
816 encoder.modRm_SIBDisp32(reg.lowId());
817 encoder.sib_baseDisp32(dst_reg.lowId());
818 encoder.disp32(dst_mem.disp);
819 } else {
820 encoder.modRm_indirectDisp32(reg.lowId(), dst_reg.lowId());
821 encoder.disp32(dst_mem.disp);
822 }
823 }
775 } else {824 } else {
776 encoder.modRm_indirectDisp32(ops.reg1.lowId(), ops.reg2.lowId());825 encoder.rex(.{
777 encoder.disp32(imm);826 .w = reg.size() == 64,
827 .r = reg.isExtended(),
828 });
829 encoder.opcode_1byte(opc);
830 encoder.modRm_SIBDisp0(reg.lowId());
831 encoder.sib_disp32();
832 encoder.disp32(dst_mem.disp);
778 }833 }
779 },834 },
780 0b10 => blk: {835 }
781 // TODO handle 32-bit base register - requires prefix 0x67836}
782 // Intel Manual, Vol 1, chapter 3.6 and 3.6.1837
783 if (ops.reg1.size() != 64) {838fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void {
784 return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1});839 const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]);
840 switch (ops.flags) {
841 0b00 => {
842 if (ops.reg2 == .none) {
843 // mov reg1, imm32
844 // MI
845 const imm = emit.mir.instructions.items(.data)[inst].imm;
846 return lowerToMiEnc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code);
847 }
848 // mov reg1, reg2
849 // RM
850 return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code);
851 },
852 0b01 => {
853 const imm = emit.mir.instructions.items(.data)[inst].imm;
854 if (ops.reg2 == .none) {
855 // mov reg1, [imm32]
856 // RM
857 return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(null, imm), emit.code);
785 }858 }
859 // mov reg1, [reg2 + imm32]
860 // RM
861 return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(ops.reg2, imm), emit.code);
862 },
863 0b10 => {
786 if (ops.reg2 == .none) {864 if (ops.reg2 == .none) {
787 // mov [reg1 + 0], imm32865 // mov dword ptr [reg1 + 0], imm32
788 // MI866 // MI
789 // Base register reg1 can either be 64bit or 32bit in size.867 const imm = emit.mir.instructions.items(.data)[inst].imm;
790 // TODO for memory operand, immediate operand pair, we currently868 return lowerToMiEnc(tag, RegisterOrMemory.mem(ops.reg1, 0), imm, emit.code);
791 // have no way of flagging whether the immediate can be 8-, 16- or
792 // 32-bit and whether the corresponding memory operand is respectively
793 // a byte, word or dword ptr.
794 // TODO we currently don't have a way to flag imm32 64bit sign extended
795 const imm = mir_instructions.items(.data)[inst].imm;
796 const opcode = getArithOpCode(tag, .mi);
797 const encoder = try Encoder.init(code, 7);
798 encoder.rex(.{
799 .w = false,
800 .b = ops.reg1.isExtended(),
801 });
802 encoder.opcode_1byte(opcode.opc);
803 encoder.modRm_indirectDisp0(opcode.modrm_ext, ops.reg1.lowId());
804 encoder.imm32(imm);
805 break :blk;
806 }869 }
807 // mov [reg1 + imm32], reg2870 // mov [reg1 + imm32], reg2
808 // MR871 // MR
809 // We use size of source register reg2 to work out which872 const imm = emit.mir.instructions.items(.data)[inst].imm;
810 // variant of memory ptr to pick:873 return lowerToMrEnc(tag, RegisterOrMemory.mem(ops.reg1, imm), ops.reg2, emit.code);
811 // * reg2 is 64bit - qword ptr
812 // * reg2 is 32bit - dword ptr
813 // * reg2 is 16bit - word ptr
814 // * reg2 is 8bit - byte ptr
815 const imm = mir_instructions.items(.data)[inst].imm;
816 const opcode = getArithOpCode(tag, .mr);
817 const opc: u8 = if (ops.reg2.size() == 8) opcode.opc - 1 else opcode.opc;
818 const encoder = try Encoder.init(code, 5);
819 if (ops.reg2.size() == 16) {
820 encoder.opcode_1byte(0x66);
821 }
822 encoder.rex(.{
823 .w = ops.reg2.size() == 64,
824 .r = ops.reg2.isExtended(),
825 .b = ops.reg1.isExtended(),
826 });
827 encoder.opcode_1byte(opc);
828 if (immOpSize(imm) == 8) {
829 encoder.modRm_indirectDisp8(ops.reg2.lowId(), ops.reg1.lowId());
830 encoder.disp8(@intCast(i8, imm));
831 } else {
832 encoder.modRm_indirectDisp32(ops.reg2.lowId(), ops.reg1.lowId());
833 encoder.disp32(imm);
834 }
835 },874 },
836 0b11 => blk: {875 0b11 => {
837 if (ops.reg2 == .none) {876 if (ops.reg2 == .none) {
838 // mov [reg1 + imm32], imm32877 // mov dword ptr [reg1 + imm32], imm32
839 // MI878 // MI
840 // Base register reg1 can either be 64bit or 32bit in size.879 const payload = emit.mir.instructions.items(.data)[inst].payload;
841 // TODO for memory operand, immediate operand pair, we currently880 const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data;
842 // have no way of flagging whether the immediate can be 8-, 16- or881 return lowerToMiEnc(
843 // 32-bit and whether the corresponding memory operand is respectively882 tag,
844 // a byte, word or dword ptr.883 RegisterOrMemory.mem(ops.reg1, imm_pair.dest_off),
845 // TODO we currently don't have a way to flag imm32 64bit sign extended884 imm_pair.operand,
846 if (ops.reg1.size() != 64) {885 emit.code,
847 return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1});886 );
848 }
849 const payload = mir_instructions.items(.data)[inst].payload;
850 const imm_pair = Mir.extraData(mir_extra, Mir.ImmPair, payload).data;
851 const imm_op = imm_pair.operand;
852 const opcode = getArithOpCode(tag, .mi);
853 const encoder = try Encoder.init(code, 10);
854 encoder.rex(.{
855 .w = false,
856 .b = ops.reg1.isExtended(),
857 });
858 encoder.opcode_1byte(opcode.opc);
859 if (immOpSize(imm_pair.dest_off) == 8) {
860 encoder.modRm_indirectDisp8(opcode.modrm_ext, ops.reg1.lowId());
861 encoder.disp8(@intCast(i8, imm_pair.dest_off));
862 } else {
863 encoder.modRm_indirectDisp32(opcode.modrm_ext, ops.reg1.lowId());
864 encoder.disp32(imm_pair.dest_off);
865 }
866 encoder.imm32(imm_op);
867 break :blk;
868 }887 }
869 return EmitResult.err(allocator, src_loc, "TODO unused variant: mov reg1, reg2, 0b11", .{});888 return emit.fail("TODO unused variant: mov reg1, reg2, 0b11", .{});
870 },889 },
871 }890 }
872 return EmitResult.ok();
873}891}
874892
875fn immOpSize(imm: i32) u8 {893fn immOpSize(imm: i32) u8 {
...@@ -888,7 +906,7 @@ fn mirArithScaleSrc(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE...@@ -888,7 +906,7 @@ fn mirArithScaleSrc(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE
888 const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]);906 const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]);
889 const scale = ops.flags;907 const scale = ops.flags;
890 // OP reg1, [reg2 + scale*rcx + imm32]908 // OP reg1, [reg2 + scale*rcx + imm32]
891 const opcode = getArithOpCode(tag, .rm);909 const opcode = getOpCode(tag, .rm);
892 const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;910 const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;
893 const imm = emit.mir.instructions.items(.data)[inst].imm;911 const imm = emit.mir.instructions.items(.data)[inst].imm;
894 const encoder = try Encoder.init(emit.code, 8);912 const encoder = try Encoder.init(emit.code, 8);
...@@ -916,7 +934,7 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE...@@ -916,7 +934,7 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE
916934
917 if (ops.reg2 == .none) {935 if (ops.reg2 == .none) {
918 // OP [reg1 + scale*rax + 0], imm32936 // OP [reg1 + scale*rax + 0], imm32
919 const opcode = getArithOpCode(tag, .mi);937 const opcode = getOpCode(tag, .mi);
920 const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;938 const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;
921 const encoder = try Encoder.init(emit.code, 8);939 const encoder = try Encoder.init(emit.code, 8);
922 encoder.rex(.{940 encoder.rex(.{
...@@ -937,7 +955,7 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE...@@ -937,7 +955,7 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE
937 }955 }
938956
939 // OP [reg1 + scale*rax + imm32], reg2957 // OP [reg1 + scale*rax + imm32], reg2
940 const opcode = getArithOpCode(tag, .mr);958 const opcode = getOpCode(tag, .mr);
941 const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;959 const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;
942 const encoder = try Encoder.init(emit.code, 8);960 const encoder = try Encoder.init(emit.code, 8);
943 encoder.rex(.{961 encoder.rex(.{
...@@ -961,8 +979,8 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE...@@ -961,8 +979,8 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE
961 const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]);979 const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]);
962 const scale = ops.flags;980 const scale = ops.flags;
963 const payload = emit.mir.instructions.items(.data)[inst].payload;981 const payload = emit.mir.instructions.items(.data)[inst].payload;
964 const imm_pair = Mir.extraData(emit.mir.extra, Mir.ImmPair, payload).data;982 const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data;
965 const opcode = getArithOpCode(tag, .mi);983 const opcode = getOpCode(tag, .mi);
966 const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;984 const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;
967 const encoder = try Encoder.init(emit.code, 2);985 const encoder = try Encoder.init(emit.code, 2);
968 encoder.rex(.{986 encoder.rex(.{
...@@ -1023,7 +1041,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {...@@ -1023,7 +1041,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
10231041
1024 if (is_64) {1042 if (is_64) {
1025 const payload = emit.mir.instructions.items(.data)[inst].payload;1043 const payload = emit.mir.instructions.items(.data)[inst].payload;
1026 const imm64 = Mir.extraData(emit.mir.extra, Mir.Imm64, payload).data;1044 const imm64 = emit.mir.extraData(Mir.Imm64, payload).data;
1027 encoder.imm64(imm64.decode());1045 encoder.imm64(imm64.decode());
1028 } else {1046 } else {
1029 const imm = emit.mir.instructions.items(.data)[inst].imm;1047 const imm = emit.mir.instructions.items(.data)[inst].imm;
...@@ -1129,7 +1147,7 @@ fn mirLeaRip(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {...@@ -1129,7 +1147,7 @@ fn mirLeaRip(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
1129 const end_offset = emit.code.items.len;1147 const end_offset = emit.code.items.len;
1130 if (@truncate(u1, ops.flags) == 0b0) {1148 if (@truncate(u1, ops.flags) == 0b0) {
1131 const payload = emit.mir.instructions.items(.data)[inst].payload;1149 const payload = emit.mir.instructions.items(.data)[inst].payload;
1132 const imm = Mir.extraData(emit.mir.extra, Mir.Imm64, payload).data.decode();1150 const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode();
1133 encoder.disp32(@intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset + 4)));1151 encoder.disp32(@intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset + 4)));
1134 } else {1152 } else {
1135 const got_entry = emit.mir.instructions.items(.data)[inst].got_entry;1153 const got_entry = emit.mir.instructions.items(.data)[inst].got_entry;
...@@ -1184,7 +1202,7 @@ fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {...@@ -1184,7 +1202,7 @@ fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
1184 const tag = emit.mir.instructions.items(.tag)[inst];1202 const tag = emit.mir.instructions.items(.tag)[inst];
1185 assert(tag == .dbg_line);1203 assert(tag == .dbg_line);
1186 const payload = emit.mir.instructions.items(.data)[inst].payload;1204 const payload = emit.mir.instructions.items(.data)[inst].payload;
1187 const dbg_line_column = Mir.extraData(emit.mir.extra, Mir.DbgLineColumn, payload).data;1205 const dbg_line_column = emit.mir.extraData(Mir.DbgLineColumn, payload).data;
1188 try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column);1206 try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column);
1189}1207}
11901208
...@@ -1269,7 +1287,7 @@ fn mirArgDbgInfo(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {...@@ -1269,7 +1287,7 @@ fn mirArgDbgInfo(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
1269 const tag = emit.mir.instructions.items(.tag)[inst];1287 const tag = emit.mir.instructions.items(.tag)[inst];
1270 assert(tag == .arg_dbg_info);1288 assert(tag == .arg_dbg_info);
1271 const payload = emit.mir.instructions.items(.data)[inst].payload;1289 const payload = emit.mir.instructions.items(.data)[inst].payload;
1272 const arg_dbg_info = Mir.extraData(emit.mir.extra, Mir.ArgDbgInfo, payload).data;1290 const arg_dbg_info = emit.mir.extraData(Mir.ArgDbgInfo, payload).data;
1273 const mcv = emit.mir.function.args[arg_dbg_info.arg_index];1291 const mcv = emit.mir.function.args[arg_dbg_info.arg_index];
1274 try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv);1292 try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv);
1275}1293}
...@@ -1333,111 +1351,6 @@ fn addDbgInfoTypeReloc(emit: *Emit, ty: Type) !void {...@@ -1333,111 +1351,6 @@ fn addDbgInfoTypeReloc(emit: *Emit, ty: Type) !void {
1333 }1351 }
1334}1352}
13351353
1336const Mock = struct {
1337 const gpa = testing.allocator;
1338
1339 mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
1340 mir_extra: std.ArrayList(u32),
1341 code: std.ArrayList(u8),
1342
1343 fn init() Mock {
1344 return .{
1345 .mir_extra = std.ArrayList(u32).init(gpa),
1346 .code = std.ArrayList(u8).init(gpa),
1347 };
1348 }
1349
1350 fn deinit(self: *Mock) void {
1351 self.mir_instructions.deinit(gpa);
1352 self.mir_extra.deinit();
1353 self.code.deinit();
1354 }
1355
1356 fn addInst(self: *Mock, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index {
1357 try self.mir_instructions.ensureUnusedCapacity(gpa, 1);
1358 const result_index = @intCast(Air.Inst.Index, self.mir_instructions.len);
1359 self.mir_instructions.appendAssumeCapacity(inst);
1360 return result_index;
1361 }
1362
1363 fn addExtra(self: *Mock, extra: anytype) Allocator.Error!u32 {
1364 const fields = std.meta.fields(@TypeOf(extra));
1365 try self.mir_extra.ensureUnusedCapacity(fields.len);
1366 return self.addExtraAssumeCapacity(extra);
1367 }
1368
1369 fn addExtraAssumeCapacity(self: *Mock, extra: anytype) u32 {
1370 const fields = std.meta.fields(@TypeOf(extra));
1371 const result = @intCast(u32, self.mir_extra.items.len);
1372 inline for (fields) |field| {
1373 self.mir_extra.appendAssumeCapacity(switch (field.field_type) {
1374 u32 => @field(extra, field.name),
1375 i32 => @bitCast(u32, @field(extra, field.name)),
1376 else => @compileError("bad field type"),
1377 });
1378 }
1379 return result;
1380 }
1381
1382 fn dummySrcLoc() Module.SrcLoc {
1383 return .{
1384 .file_scope = undefined,
1385 .parent_decl_node = 0,
1386 .lazy = .unneeded,
1387 };
1388 }
1389
1390 fn testEmitSingleSuccess(
1391 self: *Mock,
1392 mir_inst: Mir.Inst,
1393 expected_enc: []const u8,
1394 assembly: []const u8,
1395 ) !void {
1396 const dummy_src_loc = Mock.dummySrcLoc();
1397 const code_index = self.code.items.len;
1398 const mir_index = try self.addInst(mir_inst);
1399 const res = switch (mir_inst.tag) {
1400 .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov => try mirArithImpl(
1401 testing.allocator,
1402 mir_inst.tag,
1403 self.mir_instructions.slice(),
1404 self.mir_extra.items,
1405 mir_index,
1406 dummy_src_loc,
1407 &self.code,
1408 ),
1409 else => unreachable,
1410 };
1411 defer res.deinit(testing.allocator);
1412 try testing.expect(res == .ok);
1413 const code_len = if (self.code.items[code_index..].len >= expected_enc.len)
1414 expected_enc.len
1415 else
1416 self.code.items.len - code_index;
1417 try expectEqualHexStrings(expected_enc, self.code.items[code_index..][0..code_len], assembly);
1418 }
1419
1420 fn testEmitSingleFail(self: *Mock, mir_inst: Mir.Inst, msg: []const u8) !void {
1421 const dummy_src_loc = Mock.dummySrcLoc();
1422 const index = try self.addInst(mir_inst);
1423 const res = switch (mir_inst.tag) {
1424 .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov => try mirArithImpl(
1425 testing.allocator,
1426 mir_inst.tag,
1427 self.mir_instructions.slice(),
1428 self.mir_extra.items,
1429 index,
1430 dummy_src_loc,
1431 &self.code,
1432 ),
1433 else => unreachable,
1434 };
1435 defer res.deinit(testing.allocator);
1436 try testing.expect(res == .err);
1437 try testing.expectEqualStrings(msg, res.err.msg);
1438 }
1439};
1440
1441fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void {1354fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void {
1442 assert(expected.len > 0);1355 assert(expected.len > 0);
1443 if (mem.eql(u8, expected, given)) return;1356 if (mem.eql(u8, expected, given)) return;
...@@ -1458,334 +1371,118 @@ fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []co...@@ -1458,334 +1371,118 @@ fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []co
1458 return error.TestFailed;1371 return error.TestFailed;
1459}1372}
14601373
1461test "ARITH_OP/MOV dst_reg, src_reg" {1374const TestEmitCode = struct {
1462 var mock = Mock.init();1375 buf: std.ArrayList(u8),
1463 defer mock.deinit();1376 next: usize = 0,
1464 inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| {
1465 const opcode = comptime getArithOpCode(tag, .mr);
1466 const opc = [1]u8{opcode.opc};
1467 const opc_1 = [1]u8{opcode.opc - 1};
1468 try mock.testEmitSingleSuccess(.{
1469 .tag = tag,
1470 .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .rsp }).encode(),
1471 .data = undefined,
1472 }, "\x48" ++ opc ++ "\xe5", @tagName(tag) ++ " rbp, rsp");
1473 try mock.testEmitSingleSuccess(.{
1474 .tag = tag,
1475 .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .rax }).encode(),
1476 .data = undefined,
1477 }, "\x49" ++ opc ++ "\xc4", @tagName(tag) ++ " r12, rax");
1478 try mock.testEmitSingleFail(.{
1479 .tag = tag,
1480 .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .eax }).encode(),
1481 .data = undefined,
1482 }, "size mismatch: sizeof Register.r12 != sizeof Register.eax");
1483 try mock.testEmitSingleFail(.{
1484 .tag = tag,
1485 .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .rax }).encode(),
1486 .data = undefined,
1487 }, "size mismatch: sizeof Register.r12d != sizeof Register.rax");
1488 try mock.testEmitSingleSuccess(.{
1489 .tag = tag,
1490 .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .eax }).encode(),
1491 .data = undefined,
1492 }, "\x41" ++ opc ++ "\xc4", @tagName(tag) ++ " r12d, eax");
1493 // TODO mov r12b, ah requires a codepath without REX prefix
1494 try mock.testEmitSingleSuccess(.{
1495 .tag = tag,
1496 .ops = (Mir.Ops{ .reg1 = .r12b, .reg2 = .al }).encode(),
1497 .data = undefined,
1498 }, "\x41" ++ opc_1 ++ "\xc4", @tagName(tag) ++ " r12b, al");
1499 }
1500}
1501
1502test "ARITH_OP/MOV dst_reg, imm" {
1503 var mock = Mock.init();
1504 defer mock.deinit();
1505
1506 const ModRmByte = struct {
1507 inline fn get(tag: Mir.Inst.Tag, reg: u8) [1]u8 {
1508 const modrm: u8 = getArithOpCode(tag, .mi).modrm_ext;
1509 return .{0xc0 + (modrm << 3) + reg};
1510 }
1511 };
15121377
1513 inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| {1378 fn init() TestEmitCode {
1514 const opcode = comptime getArithOpCode(tag, .mi);1379 return .{
1515 const opc = [1]u8{opcode.opc};1380 .buf = std.ArrayList(u8).init(testing.allocator),
1516 const opc_1 = [1]u8{opcode.opc - 1};1381 };
1517 try mock.testEmitSingleSuccess(.{
1518 .tag = tag,
1519 .ops = (Mir.Ops{ .reg1 = .rcx }).encode(),
1520 .data = .{ .imm = 0x10 },
1521 }, "\x48" ++ opc ++ ModRmByte.get(tag, 1) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " rcx, 0x10");
1522 // TODO we are wasting one byte here: this could be encoded as OI with the encoding
1523 // opc + rd, imm8/16/32
1524 // b9 10 00 00 00
1525 try mock.testEmitSingleSuccess(.{
1526 .tag = tag,
1527 .ops = (Mir.Ops{ .reg1 = .ecx }).encode(),
1528 .data = .{ .imm = 0x10 },
1529 }, opc ++ ModRmByte.get(tag, 1) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " ecx, 0x10");
1530 try mock.testEmitSingleSuccess(.{
1531 .tag = tag,
1532 .ops = (Mir.Ops{ .reg1 = .cx }).encode(),
1533 .data = .{ .imm = 0x10 },
1534 }, "\x66" ++ opc ++ ModRmByte.get(tag, 1) ++ "\x10\x00", @tagName(tag) ++ " cx, 0x10");
1535 try mock.testEmitSingleSuccess(.{
1536 .tag = tag,
1537 .ops = (Mir.Ops{ .reg1 = .r11w }).encode(),
1538 .data = .{ .imm = 0x10 },
1539 }, "\x66\x41" ++ opc ++ ModRmByte.get(tag, 3) ++ "\x10\x00", @tagName(tag) ++ " r11w, 0x10");
1540 try mock.testEmitSingleSuccess(.{
1541 .tag = tag,
1542 .ops = (Mir.Ops{ .reg1 = .cl }).encode(),
1543 .data = .{ .imm = 0x10 },
1544 }, opc_1 ++ ModRmByte.get(tag, 1) ++ "\x10", @tagName(tag) ++ " cl, 0x10");
1545 try mock.testEmitSingleFail(.{
1546 .tag = .mov,
1547 .ops = (Mir.Ops{ .reg1 = .cx }).encode(),
1548 .data = .{ .imm = 0x10000000 },
1549 }, "size mismatch: sizeof Register.cx != sizeof 0x10000000");
1550 try mock.testEmitSingleFail(.{
1551 .tag = .mov,
1552 .ops = (Mir.Ops{ .reg1 = .cl }).encode(),
1553 .data = .{ .imm = 0x1000 },
1554 }, "size mismatch: sizeof Register.cl != sizeof 0x1000");
1555 }1382 }
1556}
15571383
1558test "ARITH_OP/MOV dst_reg, [imm32]" {1384 fn deinit(emit: *TestEmitCode) void {
1559 var mock = Mock.init();1385 emit.buf.deinit();
1560 defer mock.deinit();1386 emit.next = undefined;
1561 inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| {
1562 const opcode = comptime getArithOpCode(tag, .rm);
1563 const opc = [1]u8{opcode.opc};
1564 const opc_1 = [1]u8{opcode.opc - 1};
1565 try mock.testEmitSingleSuccess(.{
1566 .tag = tag,
1567 .ops = (Mir.Ops{ .reg1 = .rcx, .flags = 0b01 }).encode(),
1568 .data = .{ .imm = 0x10 },
1569 }, "\x48" ++ opc ++ "\x0C\x25\x10\x00\x00\x00", @tagName(tag) ++ " rcx, [0x10]");
1570 try mock.testEmitSingleSuccess(.{
1571 .tag = tag,
1572 .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b01 }).encode(),
1573 .data = .{ .imm = 0x10 },
1574 }, "\x4C" ++ opc ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11, [0x10]");
1575 try mock.testEmitSingleSuccess(.{
1576 .tag = tag,
1577 .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b01 }).encode(),
1578 .data = .{ .imm = 0x10 },
1579 }, "\x44" ++ opc ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11d, [0x10]");
1580 try mock.testEmitSingleSuccess(.{
1581 .tag = tag,
1582 .ops = (Mir.Ops{ .reg1 = .r11w, .flags = 0b01 }).encode(),
1583 .data = .{ .imm = 0x10 },
1584 }, "\x66\x44" ++ opc ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11w, [0x10]");
1585 try mock.testEmitSingleSuccess(.{
1586 .tag = tag,
1587 .ops = (Mir.Ops{ .reg1 = .r11b, .flags = 0b01 }).encode(),
1588 .data = .{ .imm = 0x10 },
1589 }, "\x44" ++ opc_1 ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11b, [0x10]");
1590 }1387 }
1591}
15921388
1593test "ARITH_OP/MOV dst_reg, [src_reg + imm]" {1389 fn buffer(emit: *TestEmitCode) *std.ArrayList(u8) {
1594 var mock = Mock.init();1390 emit.next = emit.buf.items.len;
1595 defer mock.deinit();1391 return &emit.buf;
1596 inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| {
1597 const opcode = comptime getArithOpCode(tag, .rm);
1598 const opc = [1]u8{opcode.opc};
1599 const opc_1 = [1]u8{opcode.opc - 1};
1600 try mock.testEmitSingleSuccess(.{
1601 .tag = tag,
1602 .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(),
1603 .data = .{ .imm = 0x10 },
1604 }, "\x48" ++ opc ++ "\x4D\x10", @tagName(tag) ++ " rcx, [rbp + 0x10]");
1605 try mock.testEmitSingleSuccess(.{
1606 .tag = tag,
1607 .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(),
1608 .data = .{ .imm = 0x10000000 },
1609 }, "\x48" ++ opc ++ "\x8D\x00\x00\x00\x10", @tagName(tag) ++ " rcx, [rbp + 0x10000000]");
1610 try mock.testEmitSingleSuccess(.{
1611 .tag = tag,
1612 .ops = (Mir.Ops{ .reg1 = .r11b, .reg2 = .rbp, .flags = 0b01 }).encode(),
1613 .data = .{ .imm = 0x10 },
1614 }, "\x44" ++ opc_1 ++ "\x5D\x10", @tagName(tag) ++ " r11b, [rbp + 0x10]");
1615 try mock.testEmitSingleSuccess(.{
1616 .tag = tag,
1617 .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .rbp, .flags = 0b01 }).encode(),
1618 .data = .{ .imm = 0x10000000 },
1619 }, "\x66\x44" ++ opc ++ "\x9D\x00\x00\x00\x10", @tagName(tag) ++ " r11w, [rbp + 0x10000000]");
1620 }1392 }
1621}
1622
1623test "ARITH_OP/MOV [dst_reg + 0], imm" {
1624 var mock = Mock.init();
1625 defer mock.deinit();
1626
1627 const ModRmByte = struct {
1628 inline fn get(tag: Mir.Inst.Tag, reg: u8) [1]u8 {
1629 const modrm: u8 = getArithOpCode(tag, .mi).modrm_ext;
1630 return .{(modrm << 3) + reg};
1631 }
1632 };
16331393
1634 inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| {1394 fn emitted(emit: TestEmitCode) []const u8 {
1635 const opcode = comptime getArithOpCode(tag, .mi);1395 return emit.buf.items[emit.next..];
1636 const opc = [1]u8{opcode.opc};
1637 try mock.testEmitSingleSuccess(.{
1638 .tag = tag,
1639 .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b10 }).encode(),
1640 .data = .{ .imm = 0x10 },
1641 }, "\x41" ++ opc ++ ModRmByte.get(tag, 3) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " dword ptr [r11 + 0], 0x10");
1642 try mock.testEmitSingleSuccess(.{
1643 .tag = tag,
1644 .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(),
1645 .data = .{ .imm = 0x10000000 },
1646 }, opc ++ ModRmByte.get(tag, 0) ++ "\x00\x00\x00\x10", @tagName(tag) ++ " dword ptr [rax + 0], 0x10000000");
1647 try mock.testEmitSingleSuccess(.{
1648 .tag = tag,
1649 .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(),
1650 .data = .{ .imm = 0x1000 },
1651 }, opc ++ ModRmByte.get(tag, 0) ++ "\x00\x10\x00\x00", @tagName(tag) ++ " dword ptr [rax + 0], 0x1000");
1652 try mock.testEmitSingleSuccess(.{
1653 .tag = tag,
1654 .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(),
1655 .data = .{ .imm = 0x10 },
1656 }, opc ++ ModRmByte.get(tag, 0) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " dword ptr [rax + 0], 0x10");
1657 try mock.testEmitSingleFail(.{
1658 .tag = tag,
1659 .ops = (Mir.Ops{ .reg1 = .eax, .flags = 0b10 }).encode(),
1660 .data = .{ .imm = 0x10 },
1661 }, "size mismatch: sizeof Register.eax != 8");
1662 }1396 }
1663}1397};
16641398
1665test "ARITH_OP/MOV [dst_reg + imm32], src_reg" {1399test "lower MI encoding" {
1666 var mock = Mock.init();1400 var code = TestEmitCode.init();
1667 defer mock.deinit();1401 defer code.deinit();
1668 inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| {1402 try lowerToMiEnc(.mov, RegisterOrMemory.reg(.rax), 0x10, code.buffer());
1669 const opcode = comptime getArithOpCode(tag, .mr);1403 try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", code.emitted(), "mov rax, 0x10");
1670 const opc = [1]u8{opcode.opc};1404 try lowerToMiEnc(.mov, RegisterOrMemory.mem(.r11, 0), 0x10, code.buffer());
1671 const opc_1 = [1]u8{opcode.opc - 1};1405 try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", code.emitted(), "mov dword ptr [r11 + 0], 0x10");
1672 try mock.testEmitSingleSuccess(.{1406 try lowerToMiEnc(.add, RegisterOrMemory.mem(.rdx, -8), 0x10, code.buffer());
1673 .tag = tag,1407 try expectEqualHexStrings("\x81\x42\xF8\x10\x00\x00\x00", code.emitted(), "add dword ptr [rdx - 8], 0x10");
1674 .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11, .flags = 0b10 }).encode(),1408 try lowerToMiEnc(.sub, RegisterOrMemory.mem(.r11, 0x10000000), 0x10, code.buffer());
1675 .data = .{ .imm = 0x10 },1409 try expectEqualHexStrings(
1676 }, "\x4c" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " qword ptr [rbp + 0x10], r11");1410 "\x41\x81\xab\x00\x00\x00\x10\x10\x00\x00\x00",
1677 try mock.testEmitSingleSuccess(.{1411 code.emitted(),
1678 .tag = tag,1412 "sub dword ptr [r11 + 0x10000000], 0x10",
1679 .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11d, .flags = 0b10 }).encode(),1413 );
1680 .data = .{ .imm = 0x10 },1414 try lowerToMiEnc(.@"and", RegisterOrMemory.mem(null, 0x10000000), 0x10, code.buffer());
1681 }, "\x44" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " dword ptr [rbp + 0x10], r11d");1415 try expectEqualHexStrings(
1682 try mock.testEmitSingleSuccess(.{1416 "\x81\x24\x25\x00\x00\x00\x10\x10\x00\x00\x00",
1683 .tag = tag,1417 code.emitted(),
1684 .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11w, .flags = 0b10 }).encode(),1418 "and dword ptr [ds:0x10000000], 0x10",
1685 .data = .{ .imm = 0x10 },1419 );
1686 }, "\x66\x44" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " word ptr [rbp + 0x10], r11w");1420 try lowerToMiEnc(.@"and", RegisterOrMemory.mem(.r12, 0x10000000), 0x10, code.buffer());
1687 try mock.testEmitSingleSuccess(.{1421 try expectEqualHexStrings(
1688 .tag = tag,1422 "\x41\x81\xA4\x24\x00\x00\x00\x10\x10\x00\x00\x00",
1689 .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11b, .flags = 0b10 }).encode(),1423 code.emitted(),
1690 .data = .{ .imm = 0x10 },1424 "and dword ptr [r12 + 0x10000000], 0x10",
1691 }, "\x44" ++ opc_1 ++ "\x5d\x10", @tagName(tag) ++ " byte ptr [rbp + 0x10], r11b");1425 );
1692 try mock.testEmitSingleSuccess(.{
1693 .tag = tag,
1694 .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .rax, .flags = 0b10 }).encode(),
1695 .data = .{ .imm = 0x10 },
1696 }, "\x49" ++ opc ++ "\x43\x10", @tagName(tag) ++ " qword ptr [r11 + 0x10], rax");
1697 try mock.testEmitSingleSuccess(.{
1698 .tag = tag,
1699 .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .eax, .flags = 0b10 }).encode(),
1700 .data = .{ .imm = 0x10 },
1701 }, "\x41" ++ opc ++ "\x43\x10", @tagName(tag) ++ " dword ptr [r11 + 0x10], eax");
1702 try mock.testEmitSingleFail(.{
1703 .tag = tag,
1704 .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .ax, .flags = 0b10 }).encode(),
1705 .data = .{ .imm = 0x10 },
1706 }, "size mismatch: sizeof Register.r11w != 8");
1707 }
1708}1426}
17091427
1710test "ARITH_OP/MOV [dst_reg + imm32], imm32" {1428test "lower RM encoding" {
1711 var mock = Mock.init();1429 var code = TestEmitCode.init();
1712 defer mock.deinit();1430 defer code.deinit();
17131431 try lowerToRmEnc(.mov, .rax, RegisterOrMemory.reg(.rbx), code.buffer());
1714 const ModRmByte = struct {1432 try expectEqualHexStrings("\x48\x8b\xc3", code.emitted(), "mov rax, rbx");
1715 inline fn get(tag: Mir.Inst.Tag, disp: u2, reg: u8) [1]u8 {1433 try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.r11, 0), code.buffer());
1716 const modrm: u8 = getArithOpCode(tag, .mi).modrm_ext;1434 try expectEqualHexStrings("\x49\x8b\x03", code.emitted(), "mov rax, qword ptr [r11 + 0]");
1717 return .{(@as(u8, disp) << 6) + (modrm << 3) + reg};1435 try lowerToRmEnc(.add, .r11, RegisterOrMemory.mem(null, 0x10000000), code.buffer());
1718 }1436 try expectEqualHexStrings(
1719 };1437 "\x4C\x03\x1C\x25\x00\x00\x00\x10",
1438 code.emitted(),
1439 "add r11, qword ptr [ds:0x10000000]",
1440 );
1441 try lowerToRmEnc(.add, .r12b, RegisterOrMemory.mem(null, 0x10000000), code.buffer());
1442 try expectEqualHexStrings(
1443 "\x44\x02\x24\x25\x00\x00\x00\x10",
1444 code.emitted(),
1445 "add r11b, byte ptr [ds:0x10000000]",
1446 );
1447 try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.r13, 0x10000000), code.buffer());
1448 try expectEqualHexStrings(
1449 "\x4D\x2B\x9D\x00\x00\x00\x10",
1450 code.emitted(),
1451 "sub r11, qword ptr [r13 + 0x10000000]",
1452 );
1453 try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.r12, 0x10000000), code.buffer());
1454 try expectEqualHexStrings(
1455 "\x4D\x2B\x9C\x24\x00\x00\x00\x10",
1456 code.emitted(),
1457 "sub r11, qword ptr [r12 + 0x10000000]",
1458 );
1459 try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.rbp, -4), code.buffer());
1460 try expectEqualHexStrings("\x48\x8B\x45\xFC", code.emitted(), "mov rax, qword ptr [rbp - 4]");
1461}
17201462
1721 inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| {1463test "lower MR encoding" {
1722 const opcode = comptime getArithOpCode(tag, .mi);1464 var code = TestEmitCode.init();
1723 const opc = [1]u8{opcode.opc};1465 defer code.deinit();
1724 {1466 try lowerToMrEnc(.mov, RegisterOrMemory.reg(.rax), .rbx, code.buffer());
1725 const payload = try mock.addExtra(Mir.ImmPair{1467 try expectEqualHexStrings("\x48\x89\xd8", code.emitted(), "mov rax, rbx");
1726 .dest_off = 0x10,1468 try lowerToMrEnc(.mov, RegisterOrMemory.mem(.rbp, -4), .r11, code.buffer());
1727 .operand = 0x20000000,1469 try expectEqualHexStrings("\x4c\x89\x5d\xfc", code.emitted(), "mov qword ptr [rbp - 4], r11");
1728 });1470 try lowerToMrEnc(.add, RegisterOrMemory.mem(null, 0x10000000), .r12b, code.buffer());
1729 try mock.testEmitSingleSuccess(.{1471 try expectEqualHexStrings(
1730 .tag = tag,1472 "\x44\x00\x24\x25\x00\x00\x00\x10",
1731 .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(),1473 code.emitted(),
1732 .data = .{ .payload = payload },1474 "add byte ptr [ds:0x10000000], r12b",
1733 }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x00\x00\x00\x20", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x20000000");1475 );
1734 }1476 try lowerToMrEnc(.add, RegisterOrMemory.mem(null, 0x10000000), .r12d, code.buffer());
1735 {1477 try expectEqualHexStrings(
1736 const payload = try mock.addExtra(Mir.ImmPair{1478 "\x44\x01\x24\x25\x00\x00\x00\x10",
1737 .dest_off = 0x10,1479 code.emitted(),
1738 .operand = 0x2000,1480 "add dword ptr [ds:0x10000000], r12d",
1739 });1481 );
1740 try mock.testEmitSingleSuccess(.{1482 try lowerToMrEnc(.sub, RegisterOrMemory.mem(.r11, 0x10000000), .r12, code.buffer());
1741 .tag = tag,1483 try expectEqualHexStrings(
1742 .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(),1484 "\x4D\x29\xA3\x00\x00\x00\x10",
1743 .data = .{ .payload = payload },1485 code.emitted(),
1744 }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x00\x20\x00\x00", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x2000");1486 "sub qword ptr [r11 + 0x10000000], r12",
1745 }1487 );
1746 {
1747 const payload = try mock.addExtra(Mir.ImmPair{
1748 .dest_off = 0x10,
1749 .operand = 0x20,
1750 });
1751 try mock.testEmitSingleSuccess(.{
1752 .tag = tag,
1753 .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(),
1754 .data = .{ .payload = payload },
1755 }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x20\x00\x00\x00", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x20");
1756 }
1757 {
1758 const payload = try mock.addExtra(Mir.ImmPair{
1759 .dest_off = 0x10,
1760 .operand = 0x20000000,
1761 });
1762 try mock.testEmitSingleSuccess(.{
1763 .tag = tag,
1764 .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(),
1765 .data = .{ .payload = payload },
1766 }, "\x41" ++ opc ++ ModRmByte.get(tag, 1, 3) ++ "\x10\x00\x00\x00\x20", @tagName(tag) ++ " dword ptr [r11 + 0x10], 0x20000000");
1767 }
1768 {
1769 const payload = try mock.addExtra(Mir.ImmPair{
1770 .dest_off = 0x10000000,
1771 .operand = 0x20000000,
1772 });
1773 try mock.testEmitSingleSuccess(.{
1774 .tag = tag,
1775 .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(),
1776 .data = .{ .payload = payload },
1777 }, "\x41" ++ opc ++ ModRmByte.get(tag, 2, 3) ++ "\x00\x00\x00\x10\x00\x00\x00\x20", @tagName(tag) ++ " dword ptr [r11 + 0x10], 0x20000000");
1778 }
1779 {
1780 const payload = try mock.addExtra(Mir.ImmPair{
1781 .dest_off = 0x10,
1782 .operand = 0x20,
1783 });
1784 try mock.testEmitSingleFail(.{
1785 .tag = tag,
1786 .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b11 }).encode(),
1787 .data = .{ .payload = payload },
1788 }, "size mismatch: sizeof Register.r11d != 8");
1789 }
1790 }
1791}1488}
src/arch/x86_64/Mir.zig+3-3
...@@ -380,14 +380,14 @@ pub const Ops = struct {...@@ -380,14 +380,14 @@ pub const Ops = struct {
380 }380 }
381};381};
382382
383pub fn extraData(mir_extra: []const u32, comptime T: type, index: usize) struct { data: T, end: usize } {383pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } {
384 const fields = std.meta.fields(T);384 const fields = std.meta.fields(T);
385 var i: usize = index;385 var i: usize = index;
386 var result: T = undefined;386 var result: T = undefined;
387 inline for (fields) |field| {387 inline for (fields) |field| {
388 @field(result, field.name) = switch (field.field_type) {388 @field(result, field.name) = switch (field.field_type) {
389 u32 => mir_extra[i],389 u32 => mir.extra[i],
390 i32 => @bitCast(i32, mir_extra[i]),390 i32 => @bitCast(i32, mir.extra[i]),
391 else => @compileError("bad field type"),391 else => @compileError("bad field type"),
392 };392 };
393 i += 1;393 i += 1;
src/arch/x86_64/PrintMir.zig+3-3
...@@ -481,7 +481,7 @@ fn mirArith(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index, w: any...@@ -481,7 +481,7 @@ fn mirArith(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index, w: any
481 0b11 => {481 0b11 => {
482 if (ops.reg2 == .none) {482 if (ops.reg2 == .none) {
483 const payload = print.mir.instructions.items(.data)[inst].payload;483 const payload = print.mir.instructions.items(.data)[inst].payload;
484 const imm_pair = Mir.extraData(print.mir.extra, Mir.ImmPair, payload).data;484 const imm_pair = print.mir.extraData(Mir.ImmPair, payload).data;
485 try w.print("[{s} + {d}], {d}", .{ @tagName(ops.reg1), imm_pair.dest_off, imm_pair.operand });485 try w.print("[{s} + {d}], {d}", .{ @tagName(ops.reg1), imm_pair.dest_off, imm_pair.operand });
486 }486 }
487 try w.writeAll("TODO");487 try w.writeAll("TODO");
...@@ -516,7 +516,7 @@ fn mirArithScaleImm(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index...@@ -516,7 +516,7 @@ fn mirArithScaleImm(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index
516 const ops = Mir.Ops.decode(print.mir.instructions.items(.ops)[inst]);516 const ops = Mir.Ops.decode(print.mir.instructions.items(.ops)[inst]);
517 const scale = ops.flags;517 const scale = ops.flags;
518 const payload = print.mir.instructions.items(.data)[inst].payload;518 const payload = print.mir.instructions.items(.data)[inst].payload;
519 const imm_pair = Mir.extraData(print.mir.extra, Mir.ImmPair, payload).data;519 const imm_pair = print.mir.extraData(Mir.ImmPair, payload).data;
520 try w.print("{s} [{s} + {d}*rcx + {d}], {d}\n", .{ @tagName(tag), @tagName(ops.reg1), scale, imm_pair.dest_off, imm_pair.operand });520 try w.print("{s} [{s} + {d}*rcx + {d}], {d}\n", .{ @tagName(tag), @tagName(ops.reg1), scale, imm_pair.dest_off, imm_pair.operand });
521}521}
522522
...@@ -528,7 +528,7 @@ fn mirMovabs(print: *const Print, inst: Mir.Inst.Index, w: anytype) !void {...@@ -528,7 +528,7 @@ fn mirMovabs(print: *const Print, inst: Mir.Inst.Index, w: anytype) !void {
528 const is_64 = ops.reg1.size() == 64;528 const is_64 = ops.reg1.size() == 64;
529 const imm: i128 = if (is_64) blk: {529 const imm: i128 = if (is_64) blk: {
530 const payload = print.mir.instructions.items(.data)[inst].payload;530 const payload = print.mir.instructions.items(.data)[inst].payload;
531 const imm64 = Mir.extraData(print.mir.extra, Mir.Imm64, payload).data;531 const imm64 = print.mir.extraData(Mir.Imm64, payload).data;
532 break :blk imm64.decode();532 break :blk imm64.decode();
533 } else print.mir.instructions.items(.data)[inst].imm;533 } else print.mir.instructions.items(.data)[inst].imm;
534 if (ops.flags == 0b00) {534 if (ops.flags == 0b00) {