| ... | @@ -48,33 +48,6 @@ const InnerError = error{ | ... | @@ -48,33 +48,6 @@ const InnerError = error{ |
| 48 | EmitFail, | 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 | const Reloc = struct { | 51 | const 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 | } |
| 185 | | 158 | |
| 186 | fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError { | 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 | @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 | } |
| 198 | | 165 | |
| ... | @@ -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 | } |
| 567 | | 534 | |
| 568 | const EncType = enum { | 535 | const Encoding = enum { |
| 569 | /// OP r/m64, imm32 | 536 | /// OP r/m64, imm32 |
| 570 | mi, | 537 | mi, |
| 571 | | 538 | |
| ... | @@ -578,11 +545,11 @@ const EncType = enum { | ... | @@ -578,11 +545,11 @@ const EncType = enum { |
| 578 | | 545 | |
| 579 | const OpCode = struct { | 546 | const 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 | }; |
| 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 | 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 | } |
| 631 | | 598 | |
| 632 | fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { | 599 | const 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, | 605 | const 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), | 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( | 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 | |
| | 629 | fn 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, imm32 | 639 | const encoder = try Encoder.init(code, 7); |
| 662 | // MI | 640 | 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 from | 646 | 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,773 | 648 | .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, reg2 | 697 | encoder.modRm_SIBDisp0(opcode.modrm_ext); |
| 716 | // MR | 698 | 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 | |
| | 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 | 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 | // RM | 732 | // 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 | |
| | 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 | 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 0x67 | 836 | } |
| 782 | // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 | 837 | |
| 783 | if (ops.reg1.size() != 64) { | 838 | fn 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], imm32 | 865 | // mov dword ptr [reg1 + 0], imm32 |
| 788 | // MI | 866 | // 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 currently | 868 | 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], reg2 | 870 | // mov [reg1 + imm32], reg2 |
| 808 | // MR | 871 | // MR |
| 809 | // We use size of source register reg2 to work out which | 872 | 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], imm32 | 877 | // mov dword ptr [reg1 + imm32], imm32 |
| 839 | // MI | 878 | // 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 currently | 880 | const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; |
| 842 | // have no way of flagging whether the immediate can be 8-, 16- or | 881 | return lowerToMiEnc( |
| 843 | // 32-bit and whether the corresponding memory operand is respectively | 882 | 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 extended | 884 | 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 | } |
| 874 | | 892 | |
| 875 | fn immOpSize(imm: i32) u8 { | 893 | fn 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 |
| 916 | | 934 | |
| 917 | if (ops.reg2 == .none) { | 935 | if (ops.reg2 == .none) { |
| 918 | // OP [reg1 + scale*rax + 0], imm32 | 936 | // 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 | } |
| 938 | | 956 | |
| 939 | // OP [reg1 + scale*rax + imm32], reg2 | 957 | // 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 { |
| 1023 | | 1041 | |
| 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 | } |
| 1190 | | 1208 | |
| ... | @@ -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 | } |
| 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 | fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void { | 1354 | fn 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 | } |
| 1460 | | 1373 | |
| 1461 | test "ARITH_OP/MOV dst_reg, src_reg" { | 1374 | const 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 | | | |
| 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 | }; | | |
| 1512 | | 1377 | |
| 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 | } | | |
| 1557 | | 1383 | |
| 1558 | test "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 | } | | |
| 1592 | | 1388 | |
| 1593 | test "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 | | | |
| 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| { | 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 | }; |
| 1664 | | 1398 | |
| 1665 | test "ARITH_OP/MOV [dst_reg + imm32], src_reg" { | 1399 | test "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 | } |
| 1709 | | 1427 | |
| 1710 | test "ARITH_OP/MOV [dst_reg + imm32], imm32" { | 1428 | test "lower RM encoding" { |
| 1711 | var mock = Mock.init(); | 1429 | var code = TestEmitCode.init(); |
| 1712 | defer mock.deinit(); | 1430 | defer code.deinit(); |
| 1713 | | 1431 | 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 | } |
| 1720 | | 1462 | |
| 1721 | inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { | 1463 | test "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 | } |