| ... | ... | @@ -48,33 +48,6 @@ const InnerError = error{ |
| 48 | 48 | EmitFail, |
| 49 | 49 | }; |
| 50 | 50 | |
| 51 | | const 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 | | |
| 78 | 51 | const Reloc = struct { |
| 79 | 52 | /// Offset of the instruction. |
| 80 | 53 | source: u64, |
| ... | ... | @@ -184,15 +157,9 @@ pub fn deinit(emit: *Emit) void { |
| 184 | 157 | } |
| 185 | 158 | |
| 186 | 159 | fn 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 | | |
| 192 | | fn failWithErrorMsg(emit: *Emit, err_msg: *ErrorMsg) InnerError { |
| 193 | 160 | @setCold(true); |
| 194 | 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 | 163 | return error.EmitFail; |
| 197 | 164 | } |
| 198 | 165 | |
| ... | ... | @@ -565,7 +532,7 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 565 | 532 | } |
| 566 | 533 | } |
| 567 | 534 | |
| 568 | | const EncType = enum { |
| 535 | const Encoding = enum { |
| 569 | 536 | /// OP r/m64, imm32 |
| 570 | 537 | mi, |
| 571 | 538 | |
| ... | ... | @@ -578,11 +545,11 @@ const EncType = enum { |
| 578 | 545 | |
| 579 | 546 | const OpCode = struct { |
| 580 | 547 | opc: u8, |
| 581 | | /// Only used if `EncType == .mi`. |
| 548 | /// Only used if `Encoding == .mi`. |
| 582 | 549 | modrm_ext: u3, |
| 583 | 550 | }; |
| 584 | 551 | |
| 585 | | inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { |
| 552 | inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) OpCode { |
| 586 | 553 | switch (enc) { |
| 587 | 554 | .mi => return switch (tag) { |
| 588 | 555 | .adc => .{ .opc = 0x81, .modrm_ext = 0x2 }, |
| ... | ... | @@ -629,247 +596,298 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { |
| 629 | 596 | } |
| 630 | 597 | } |
| 631 | 598 | |
| 632 | | fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { |
| 633 | | const res = try mirArithImpl( |
| 634 | | emit.bin_file.allocator, |
| 635 | | tag, |
| 636 | | emit.mir.instructions, |
| 637 | | emit.mir.extra, |
| 638 | | inst, |
| 639 | | emit.src_loc, |
| 640 | | emit.code, |
| 641 | | ); |
| 642 | | switch (res) { |
| 643 | | .ok => {}, |
| 644 | | .err => |err_msg| return emit.failWithErrorMsg(err_msg), |
| 599 | const ScaleIndexBase = struct { |
| 600 | scale: u2, |
| 601 | index_reg: ?Register, |
| 602 | base_reg: ?Register, |
| 603 | }; |
| 604 | |
| 605 | const Memory = struct { |
| 606 | reg: ?Register, |
| 607 | disp: i32, |
| 608 | sib: ?ScaleIndexBase = null, |
| 609 | }; |
| 610 | |
| 611 | const RegisterOrMemory = union(enum) { |
| 612 | register: Register, |
| 613 | memory: Memory, |
| 614 | |
| 615 | fn reg(register: Register) RegisterOrMemory { |
| 616 | return .{ .register = register }; |
| 645 | 617 | } |
| 646 | | } |
| 647 | 618 | |
| 648 | | fn mirArithImpl( |
| 649 | | allocator: Allocator, |
| 619 | fn mem(register: ?Register, disp: i32) RegisterOrMemory { |
| 620 | return .{ |
| 621 | .memory = .{ |
| 622 | .reg = register, |
| 623 | .disp = disp, |
| 624 | }, |
| 625 | }; |
| 626 | } |
| 627 | }; |
| 628 | |
| 629 | fn lowerToMiEnc( |
| 650 | 630 | tag: Mir.Inst.Tag, |
| 651 | | mir_instructions: std.MultiArrayList(Mir.Inst).Slice, |
| 652 | | mir_extra: []const u32, |
| 653 | | inst: Mir.Inst.Index, |
| 654 | | src_loc: Module.SrcLoc, |
| 631 | reg_or_mem: RegisterOrMemory, |
| 632 | imm: i32, |
| 655 | 633 | code: *std.ArrayList(u8), |
| 656 | | ) error{OutOfMemory}!EmitResult { |
| 657 | | const ops = Mir.Ops.decode(mir_instructions.items(.ops)[inst]); |
| 658 | | switch (ops.flags) { |
| 659 | | 0b00 => blk: { |
| 660 | | if (ops.reg2 == .none) { |
| 661 | | // mov reg1, imm32 |
| 662 | | // MI |
| 663 | | const imm = mir_instructions.items(.data)[inst].imm; |
| 664 | | const opcode = getArithOpCode(tag, .mi); |
| 665 | | const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 666 | | const encoder = try Encoder.init(code, 7); |
| 667 | | if (ops.reg1.size() == 16) { |
| 668 | | // 0x66 prefix switches to the non-default size; here we assume a switch from |
| 669 | | // the default 32bits to 16bits operand-size. |
| 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,773 |
| 671 | | encoder.opcode_1byte(0x66); |
| 672 | | } |
| 634 | ) InnerError!void { |
| 635 | const opcode = getOpCode(tag, .mi); |
| 636 | switch (reg_or_mem) { |
| 637 | .register => |dst_reg| { |
| 638 | const opc: u8 = if (dst_reg.size() == 8) opcode.opc - 1 else opcode.opc; |
| 639 | const encoder = try Encoder.init(code, 7); |
| 640 | if (dst_reg.size() == 16) { |
| 641 | // 0x66 prefix switches to the non-default size; here we assume a switch from |
| 642 | // the default 32bits to 16bits operand-size. |
| 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 |
| 644 | encoder.opcode_1byte(0x66); |
| 645 | } |
| 646 | encoder.rex(.{ |
| 647 | .w = dst_reg.size() == 64, |
| 648 | .b = dst_reg.isExtended(), |
| 649 | }); |
| 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 | 675 | encoder.rex(.{ |
| 674 | | .w = ops.reg1.size() == 64, |
| 675 | | .b = ops.reg1.isExtended(), |
| 676 | .w = false, |
| 677 | .b = dst_reg.isExtended(), |
| 676 | 678 | }); |
| 677 | | encoder.opcode_1byte(opc); |
| 678 | | encoder.modRm_direct(opcode.modrm_ext, ops.reg1.lowId()); |
| 679 | | switch (ops.reg1.size()) { |
| 680 | | 8 => { |
| 681 | | const imm8 = math.cast(i8, imm) catch { |
| 682 | | return EmitResult.err( |
| 683 | | allocator, |
| 684 | | src_loc, |
| 685 | | "size mismatch: sizeof {} != sizeof 0x{x}", |
| 686 | | .{ |
| 687 | | ops.reg1, |
| 688 | | imm, |
| 689 | | }, |
| 690 | | ); |
| 691 | | }; |
| 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, |
| 679 | encoder.opcode_1byte(opcode.opc); |
| 680 | if (dst_mem.disp == 0) { |
| 681 | encoder.modRm_indirectDisp0(opcode.modrm_ext, dst_reg.lowId()); |
| 682 | } else if (immOpSize(dst_mem.disp) == 8) { |
| 683 | encoder.modRm_indirectDisp8(opcode.modrm_ext, dst_reg.lowId()); |
| 684 | encoder.disp8(@intCast(i8, dst_mem.disp)); |
| 685 | } else { |
| 686 | if (dst_reg.lowId() == 4) { |
| 687 | encoder.modRm_SIBDisp32(opcode.modrm_ext); |
| 688 | encoder.sib_baseDisp32(dst_reg.lowId()); |
| 689 | encoder.disp32(dst_mem.disp); |
| 690 | } else { |
| 691 | encoder.modRm_indirectDisp32(opcode.modrm_ext, dst_reg.lowId()); |
| 692 | encoder.disp32(dst_mem.disp); |
| 693 | } |
| 712 | 694 | } |
| 713 | | break :blk; |
| 714 | | } |
| 715 | | // mov reg1, reg2 |
| 716 | | // MR |
| 717 | | if (ops.reg1.size() != ops.reg2.size()) { |
| 718 | | return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != sizeof {}", .{ |
| 719 | | ops.reg1, |
| 720 | | ops.reg2, |
| 721 | | }); |
| 695 | } else { |
| 696 | encoder.opcode_1byte(opcode.opc); |
| 697 | encoder.modRm_SIBDisp0(opcode.modrm_ext); |
| 698 | encoder.sib_disp32(); |
| 699 | encoder.disp32(dst_mem.disp); |
| 722 | 700 | } |
| 723 | | const opcode = getArithOpCode(tag, .mr); |
| 724 | | const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 701 | encoder.imm32(imm); |
| 702 | }, |
| 703 | } |
| 704 | } |
| 705 | |
| 706 | fn 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 | 717 | const encoder = try Encoder.init(code, 3); |
| 726 | 718 | encoder.rex(.{ |
| 727 | | .w = ops.reg1.size() == 64 and ops.reg2.size() == 64, |
| 728 | | .r = ops.reg2.isExtended(), |
| 729 | | .b = ops.reg1.isExtended(), |
| 719 | .w = reg.size() == 64, |
| 720 | .r = reg.isExtended(), |
| 721 | .b = src_reg.isExtended(), |
| 730 | 722 | }); |
| 731 | 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: { |
| 735 | | const imm = mir_instructions.items(.data)[inst].imm; |
| 736 | | const opcode = getArithOpCode(tag, .rm); |
| 737 | | const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 738 | | if (ops.reg2 == .none) { |
| 739 | | // mov reg1, [imm32] |
| 740 | | // RM |
| 741 | | const encoder = try Encoder.init(code, 9); |
| 742 | | if (ops.reg1.size() == 16) { |
| 743 | | encoder.opcode_1byte(0x66); |
| 726 | .memory => |src_mem| { |
| 727 | const encoder = try Encoder.init(code, 9); |
| 728 | if (reg.size() == 16) { |
| 729 | encoder.opcode_1byte(0x66); |
| 730 | } |
| 731 | if (src_mem.reg) |src_reg| { |
| 732 | // TODO handle 32-bit base register - requires prefix 0x67 |
| 733 | // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 |
| 734 | if (src_reg.size() != 64) return error.EmitFail; |
| 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 | 757 | encoder.rex(.{ |
| 746 | | .w = ops.reg1.size() == 64, |
| 747 | | .r = ops.reg1.isExtended(), |
| 758 | .w = reg.size() == 64, |
| 759 | .r = reg.isExtended(), |
| 748 | 760 | }); |
| 749 | 761 | encoder.opcode_1byte(opc); |
| 750 | | encoder.modRm_SIBDisp0(ops.reg1.lowId()); |
| 762 | encoder.modRm_SIBDisp0(reg.lowId()); |
| 751 | 763 | encoder.sib_disp32(); |
| 752 | | encoder.disp32(imm); |
| 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); |
| 764 | encoder.disp32(src_mem.disp); |
| 765 | 765 | } |
| 766 | }, |
| 767 | } |
| 768 | } |
| 769 | |
| 770 | fn 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 | 788 | encoder.rex(.{ |
| 767 | | .w = ops.reg1.size() == 64, |
| 768 | | .r = ops.reg1.isExtended(), |
| 769 | | .b = ops.reg2.isExtended(), |
| 789 | .w = dst_reg.size() == 64, |
| 790 | .r = reg.isExtended(), |
| 791 | .b = dst_reg.isExtended(), |
| 770 | 792 | }); |
| 771 | 793 | encoder.opcode_1byte(opc); |
| 772 | | if (immOpSize(imm) == 8) { |
| 773 | | encoder.modRm_indirectDisp8(ops.reg1.lowId(), ops.reg2.lowId()); |
| 774 | | encoder.disp8(@intCast(i8, imm)); |
| 794 | encoder.modRm_direct(reg.lowId(), dst_reg.lowId()); |
| 795 | }, |
| 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 | 824 | } else { |
| 776 | | encoder.modRm_indirectDisp32(ops.reg1.lowId(), ops.reg2.lowId()); |
| 777 | | encoder.disp32(imm); |
| 825 | encoder.rex(.{ |
| 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: { |
| 781 | | // TODO handle 32-bit base register - requires prefix 0x67 |
| 782 | | // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 |
| 783 | | if (ops.reg1.size() != 64) { |
| 784 | | return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1}); |
| 835 | } |
| 836 | } |
| 837 | |
| 838 | fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { |
| 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 | 864 | if (ops.reg2 == .none) { |
| 787 | | // mov [reg1 + 0], imm32 |
| 865 | // mov dword ptr [reg1 + 0], imm32 |
| 788 | 866 | // MI |
| 789 | | // Base register reg1 can either be 64bit or 32bit in size. |
| 790 | | // TODO for memory operand, immediate operand pair, we currently |
| 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; |
| 867 | const imm = emit.mir.instructions.items(.data)[inst].imm; |
| 868 | return lowerToMiEnc(tag, RegisterOrMemory.mem(ops.reg1, 0), imm, emit.code); |
| 806 | 869 | } |
| 807 | 870 | // mov [reg1 + imm32], reg2 |
| 808 | 871 | // MR |
| 809 | | // We use size of source register reg2 to work out which |
| 810 | | // variant of memory ptr to pick: |
| 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 | | } |
| 872 | const imm = emit.mir.instructions.items(.data)[inst].imm; |
| 873 | return lowerToMrEnc(tag, RegisterOrMemory.mem(ops.reg1, imm), ops.reg2, emit.code); |
| 835 | 874 | }, |
| 836 | | 0b11 => blk: { |
| 875 | 0b11 => { |
| 837 | 876 | if (ops.reg2 == .none) { |
| 838 | | // mov [reg1 + imm32], imm32 |
| 877 | // mov dword ptr [reg1 + imm32], imm32 |
| 839 | 878 | // MI |
| 840 | | // Base register reg1 can either be 64bit or 32bit in size. |
| 841 | | // TODO for memory operand, immediate operand pair, we currently |
| 842 | | // have no way of flagging whether the immediate can be 8-, 16- or |
| 843 | | // 32-bit and whether the corresponding memory operand is respectively |
| 844 | | // a byte, word or dword ptr. |
| 845 | | // TODO we currently don't have a way to flag imm32 64bit sign extended |
| 846 | | if (ops.reg1.size() != 64) { |
| 847 | | return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1}); |
| 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; |
| 879 | const payload = emit.mir.instructions.items(.data)[inst].payload; |
| 880 | const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; |
| 881 | return lowerToMiEnc( |
| 882 | tag, |
| 883 | RegisterOrMemory.mem(ops.reg1, imm_pair.dest_off), |
| 884 | imm_pair.operand, |
| 885 | emit.code, |
| 886 | ); |
| 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 | } |
| 874 | 892 | |
| 875 | 893 | fn immOpSize(imm: i32) u8 { |
| ... | ... | @@ -888,7 +906,7 @@ fn mirArithScaleSrc(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE |
| 888 | 906 | const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); |
| 889 | 907 | const scale = ops.flags; |
| 890 | 908 | // OP reg1, [reg2 + scale*rcx + imm32] |
| 891 | | const opcode = getArithOpCode(tag, .rm); |
| 909 | const opcode = getOpCode(tag, .rm); |
| 892 | 910 | const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 893 | 911 | const imm = emit.mir.instructions.items(.data)[inst].imm; |
| 894 | 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 | 934 | |
| 917 | 935 | if (ops.reg2 == .none) { |
| 918 | 936 | // OP [reg1 + scale*rax + 0], imm32 |
| 919 | | const opcode = getArithOpCode(tag, .mi); |
| 937 | const opcode = getOpCode(tag, .mi); |
| 920 | 938 | const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 921 | 939 | const encoder = try Encoder.init(emit.code, 8); |
| 922 | 940 | encoder.rex(.{ |
| ... | ... | @@ -937,7 +955,7 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE |
| 937 | 955 | } |
| 938 | 956 | |
| 939 | 957 | // OP [reg1 + scale*rax + imm32], reg2 |
| 940 | | const opcode = getArithOpCode(tag, .mr); |
| 958 | const opcode = getOpCode(tag, .mr); |
| 941 | 959 | const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 942 | 960 | const encoder = try Encoder.init(emit.code, 8); |
| 943 | 961 | encoder.rex(.{ |
| ... | ... | @@ -961,8 +979,8 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE |
| 961 | 979 | const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); |
| 962 | 980 | const scale = ops.flags; |
| 963 | 981 | const payload = emit.mir.instructions.items(.data)[inst].payload; |
| 964 | | const imm_pair = Mir.extraData(emit.mir.extra, Mir.ImmPair, payload).data; |
| 965 | | const opcode = getArithOpCode(tag, .mi); |
| 982 | const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; |
| 983 | const opcode = getOpCode(tag, .mi); |
| 966 | 984 | const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 967 | 985 | const encoder = try Encoder.init(emit.code, 2); |
| 968 | 986 | encoder.rex(.{ |
| ... | ... | @@ -1023,7 +1041,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 1023 | 1041 | |
| 1024 | 1042 | if (is_64) { |
| 1025 | 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 | 1045 | encoder.imm64(imm64.decode()); |
| 1028 | 1046 | } else { |
| 1029 | 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 | 1147 | const end_offset = emit.code.items.len; |
| 1130 | 1148 | if (@truncate(u1, ops.flags) == 0b0) { |
| 1131 | 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 | 1151 | encoder.disp32(@intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset + 4))); |
| 1134 | 1152 | } else { |
| 1135 | 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 | 1202 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 1185 | 1203 | assert(tag == .dbg_line); |
| 1186 | 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 | 1206 | try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column); |
| 1189 | 1207 | } |
| 1190 | 1208 | |
| ... | ... | @@ -1269,7 +1287,7 @@ fn mirArgDbgInfo(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 1269 | 1287 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 1270 | 1288 | assert(tag == .arg_dbg_info); |
| 1271 | 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 | 1291 | const mcv = emit.mir.function.args[arg_dbg_info.arg_index]; |
| 1274 | 1292 | try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv); |
| 1275 | 1293 | } |
| ... | ... | @@ -1333,111 +1351,6 @@ fn addDbgInfoTypeReloc(emit: *Emit, ty: Type) !void { |
| 1333 | 1351 | } |
| 1334 | 1352 | } |
| 1335 | 1353 | |
| 1336 | | const 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 | | |
| 1441 | 1354 | fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void { |
| 1442 | 1355 | assert(expected.len > 0); |
| 1443 | 1356 | if (mem.eql(u8, expected, given)) return; |
| ... | ... | @@ -1458,334 +1371,118 @@ fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []co |
| 1458 | 1371 | return error.TestFailed; |
| 1459 | 1372 | } |
| 1460 | 1373 | |
| 1461 | | test "ARITH_OP/MOV dst_reg, src_reg" { |
| 1462 | | var mock = Mock.init(); |
| 1463 | | defer mock.deinit(); |
| 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 | | |
| 1502 | | test "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 | | }; |
| 1374 | const TestEmitCode = struct { |
| 1375 | buf: std.ArrayList(u8), |
| 1376 | next: usize = 0, |
| 1512 | 1377 | |
| 1513 | | inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { |
| 1514 | | const opcode = comptime getArithOpCode(tag, .mi); |
| 1515 | | const opc = [1]u8{opcode.opc}; |
| 1516 | | const opc_1 = [1]u8{opcode.opc - 1}; |
| 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"); |
| 1378 | fn init() TestEmitCode { |
| 1379 | return .{ |
| 1380 | .buf = std.ArrayList(u8).init(testing.allocator), |
| 1381 | }; |
| 1555 | 1382 | } |
| 1556 | | } |
| 1557 | 1383 | |
| 1558 | | test "ARITH_OP/MOV dst_reg, [imm32]" { |
| 1559 | | var mock = Mock.init(); |
| 1560 | | defer mock.deinit(); |
| 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]"); |
| 1384 | fn deinit(emit: *TestEmitCode) void { |
| 1385 | emit.buf.deinit(); |
| 1386 | emit.next = undefined; |
| 1590 | 1387 | } |
| 1591 | | } |
| 1592 | 1388 | |
| 1593 | | test "ARITH_OP/MOV dst_reg, [src_reg + imm]" { |
| 1594 | | var mock = Mock.init(); |
| 1595 | | defer mock.deinit(); |
| 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]"); |
| 1389 | fn buffer(emit: *TestEmitCode) *std.ArrayList(u8) { |
| 1390 | emit.next = emit.buf.items.len; |
| 1391 | return &emit.buf; |
| 1620 | 1392 | } |
| 1621 | | } |
| 1622 | | |
| 1623 | | test "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 | | }; |
| 1633 | 1393 | |
| 1634 | | inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { |
| 1635 | | const opcode = comptime getArithOpCode(tag, .mi); |
| 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"); |
| 1394 | fn emitted(emit: TestEmitCode) []const u8 { |
| 1395 | return emit.buf.items[emit.next..]; |
| 1662 | 1396 | } |
| 1663 | | } |
| 1397 | }; |
| 1664 | 1398 | |
| 1665 | | test "ARITH_OP/MOV [dst_reg + imm32], src_reg" { |
| 1666 | | var mock = Mock.init(); |
| 1667 | | defer mock.deinit(); |
| 1668 | | inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { |
| 1669 | | const opcode = comptime getArithOpCode(tag, .mr); |
| 1670 | | const opc = [1]u8{opcode.opc}; |
| 1671 | | const opc_1 = [1]u8{opcode.opc - 1}; |
| 1672 | | try mock.testEmitSingleSuccess(.{ |
| 1673 | | .tag = tag, |
| 1674 | | .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11, .flags = 0b10 }).encode(), |
| 1675 | | .data = .{ .imm = 0x10 }, |
| 1676 | | }, "\x4c" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " qword ptr [rbp + 0x10], r11"); |
| 1677 | | try mock.testEmitSingleSuccess(.{ |
| 1678 | | .tag = tag, |
| 1679 | | .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11d, .flags = 0b10 }).encode(), |
| 1680 | | .data = .{ .imm = 0x10 }, |
| 1681 | | }, "\x44" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " dword ptr [rbp + 0x10], r11d"); |
| 1682 | | try mock.testEmitSingleSuccess(.{ |
| 1683 | | .tag = tag, |
| 1684 | | .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11w, .flags = 0b10 }).encode(), |
| 1685 | | .data = .{ .imm = 0x10 }, |
| 1686 | | }, "\x66\x44" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " word ptr [rbp + 0x10], r11w"); |
| 1687 | | try mock.testEmitSingleSuccess(.{ |
| 1688 | | .tag = tag, |
| 1689 | | .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11b, .flags = 0b10 }).encode(), |
| 1690 | | .data = .{ .imm = 0x10 }, |
| 1691 | | }, "\x44" ++ opc_1 ++ "\x5d\x10", @tagName(tag) ++ " byte ptr [rbp + 0x10], r11b"); |
| 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 | | } |
| 1399 | test "lower MI encoding" { |
| 1400 | var code = TestEmitCode.init(); |
| 1401 | defer code.deinit(); |
| 1402 | try lowerToMiEnc(.mov, RegisterOrMemory.reg(.rax), 0x10, code.buffer()); |
| 1403 | try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", code.emitted(), "mov rax, 0x10"); |
| 1404 | try lowerToMiEnc(.mov, RegisterOrMemory.mem(.r11, 0), 0x10, code.buffer()); |
| 1405 | try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", code.emitted(), "mov dword ptr [r11 + 0], 0x10"); |
| 1406 | try lowerToMiEnc(.add, RegisterOrMemory.mem(.rdx, -8), 0x10, code.buffer()); |
| 1407 | try expectEqualHexStrings("\x81\x42\xF8\x10\x00\x00\x00", code.emitted(), "add dword ptr [rdx - 8], 0x10"); |
| 1408 | try lowerToMiEnc(.sub, RegisterOrMemory.mem(.r11, 0x10000000), 0x10, code.buffer()); |
| 1409 | try expectEqualHexStrings( |
| 1410 | "\x41\x81\xab\x00\x00\x00\x10\x10\x00\x00\x00", |
| 1411 | code.emitted(), |
| 1412 | "sub dword ptr [r11 + 0x10000000], 0x10", |
| 1413 | ); |
| 1414 | try lowerToMiEnc(.@"and", RegisterOrMemory.mem(null, 0x10000000), 0x10, code.buffer()); |
| 1415 | try expectEqualHexStrings( |
| 1416 | "\x81\x24\x25\x00\x00\x00\x10\x10\x00\x00\x00", |
| 1417 | code.emitted(), |
| 1418 | "and dword ptr [ds:0x10000000], 0x10", |
| 1419 | ); |
| 1420 | try lowerToMiEnc(.@"and", RegisterOrMemory.mem(.r12, 0x10000000), 0x10, code.buffer()); |
| 1421 | try expectEqualHexStrings( |
| 1422 | "\x41\x81\xA4\x24\x00\x00\x00\x10\x10\x00\x00\x00", |
| 1423 | code.emitted(), |
| 1424 | "and dword ptr [r12 + 0x10000000], 0x10", |
| 1425 | ); |
| 1708 | 1426 | } |
| 1709 | 1427 | |
| 1710 | | test "ARITH_OP/MOV [dst_reg + imm32], imm32" { |
| 1711 | | var mock = Mock.init(); |
| 1712 | | defer mock.deinit(); |
| 1713 | | |
| 1714 | | const ModRmByte = struct { |
| 1715 | | inline fn get(tag: Mir.Inst.Tag, disp: u2, reg: u8) [1]u8 { |
| 1716 | | const modrm: u8 = getArithOpCode(tag, .mi).modrm_ext; |
| 1717 | | return .{(@as(u8, disp) << 6) + (modrm << 3) + reg}; |
| 1718 | | } |
| 1719 | | }; |
| 1428 | test "lower RM encoding" { |
| 1429 | var code = TestEmitCode.init(); |
| 1430 | defer code.deinit(); |
| 1431 | try lowerToRmEnc(.mov, .rax, RegisterOrMemory.reg(.rbx), code.buffer()); |
| 1432 | try expectEqualHexStrings("\x48\x8b\xc3", code.emitted(), "mov rax, rbx"); |
| 1433 | try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.r11, 0), code.buffer()); |
| 1434 | try expectEqualHexStrings("\x49\x8b\x03", code.emitted(), "mov rax, qword ptr [r11 + 0]"); |
| 1435 | try lowerToRmEnc(.add, .r11, RegisterOrMemory.mem(null, 0x10000000), code.buffer()); |
| 1436 | try expectEqualHexStrings( |
| 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 | } |
| 1720 | 1462 | |
| 1721 | | inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { |
| 1722 | | const opcode = comptime getArithOpCode(tag, .mi); |
| 1723 | | const opc = [1]u8{opcode.opc}; |
| 1724 | | { |
| 1725 | | const payload = try mock.addExtra(Mir.ImmPair{ |
| 1726 | | .dest_off = 0x10, |
| 1727 | | .operand = 0x20000000, |
| 1728 | | }); |
| 1729 | | try mock.testEmitSingleSuccess(.{ |
| 1730 | | .tag = tag, |
| 1731 | | .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), |
| 1732 | | .data = .{ .payload = payload }, |
| 1733 | | }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x00\x00\x00\x20", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x20000000"); |
| 1734 | | } |
| 1735 | | { |
| 1736 | | const payload = try mock.addExtra(Mir.ImmPair{ |
| 1737 | | .dest_off = 0x10, |
| 1738 | | .operand = 0x2000, |
| 1739 | | }); |
| 1740 | | try mock.testEmitSingleSuccess(.{ |
| 1741 | | .tag = tag, |
| 1742 | | .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), |
| 1743 | | .data = .{ .payload = payload }, |
| 1744 | | }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x00\x20\x00\x00", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x2000"); |
| 1745 | | } |
| 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 | | } |
| 1463 | test "lower MR encoding" { |
| 1464 | var code = TestEmitCode.init(); |
| 1465 | defer code.deinit(); |
| 1466 | try lowerToMrEnc(.mov, RegisterOrMemory.reg(.rax), .rbx, code.buffer()); |
| 1467 | try expectEqualHexStrings("\x48\x89\xd8", code.emitted(), "mov rax, rbx"); |
| 1468 | try lowerToMrEnc(.mov, RegisterOrMemory.mem(.rbp, -4), .r11, code.buffer()); |
| 1469 | try expectEqualHexStrings("\x4c\x89\x5d\xfc", code.emitted(), "mov qword ptr [rbp - 4], r11"); |
| 1470 | try lowerToMrEnc(.add, RegisterOrMemory.mem(null, 0x10000000), .r12b, code.buffer()); |
| 1471 | try expectEqualHexStrings( |
| 1472 | "\x44\x00\x24\x25\x00\x00\x00\x10", |
| 1473 | code.emitted(), |
| 1474 | "add byte ptr [ds:0x10000000], r12b", |
| 1475 | ); |
| 1476 | try lowerToMrEnc(.add, RegisterOrMemory.mem(null, 0x10000000), .r12d, code.buffer()); |
| 1477 | try expectEqualHexStrings( |
| 1478 | "\x44\x01\x24\x25\x00\x00\x00\x10", |
| 1479 | code.emitted(), |
| 1480 | "add dword ptr [ds:0x10000000], r12d", |
| 1481 | ); |
| 1482 | try lowerToMrEnc(.sub, RegisterOrMemory.mem(.r11, 0x10000000), .r12, code.buffer()); |
| 1483 | try expectEqualHexStrings( |
| 1484 | "\x4D\x29\xA3\x00\x00\x00\x10", |
| 1485 | code.emitted(), |
| 1486 | "sub qword ptr [r11 + 0x10000000], r12", |
| 1487 | ); |
| 1791 | 1488 | } |