| ... | ... | @@ -541,61 +541,64 @@ const Encoding = enum { |
| 541 | 541 | |
| 542 | 542 | /// OP r64, r/m64 |
| 543 | 543 | rm, |
| 544 | | }; |
| 545 | 544 | |
| 546 | | const OpCode = struct { |
| 547 | | opc: u8, |
| 548 | | /// Only used if `Encoding == .mi`. |
| 549 | | modrm_ext: u3, |
| 545 | /// OP r64, imm64 |
| 546 | oi, |
| 550 | 547 | }; |
| 551 | 548 | |
| 552 | | inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) OpCode { |
| 549 | inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) u8 { |
| 553 | 550 | switch (enc) { |
| 554 | 551 | .mi => return switch (tag) { |
| 555 | | .adc => .{ .opc = 0x81, .modrm_ext = 0x2 }, |
| 556 | | .add => .{ .opc = 0x81, .modrm_ext = 0x0 }, |
| 557 | | .sub => .{ .opc = 0x81, .modrm_ext = 0x5 }, |
| 558 | | .xor => .{ .opc = 0x81, .modrm_ext = 0x6 }, |
| 559 | | .@"and" => .{ .opc = 0x81, .modrm_ext = 0x4 }, |
| 560 | | .@"or" => .{ .opc = 0x81, .modrm_ext = 0x1 }, |
| 561 | | .sbb => .{ .opc = 0x81, .modrm_ext = 0x3 }, |
| 562 | | .cmp => .{ .opc = 0x81, .modrm_ext = 0x7 }, |
| 563 | | .mov => .{ .opc = 0xc7, .modrm_ext = 0x0 }, |
| 552 | .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp => 0x81, |
| 553 | .mov => 0xc7, |
| 564 | 554 | else => unreachable, |
| 565 | 555 | }, |
| 566 | | .mr => { |
| 567 | | const opc: u8 = switch (tag) { |
| 568 | | .adc => 0x11, |
| 569 | | .add => 0x01, |
| 570 | | .sub => 0x29, |
| 571 | | .xor => 0x31, |
| 572 | | .@"and" => 0x21, |
| 573 | | .@"or" => 0x09, |
| 574 | | .sbb => 0x19, |
| 575 | | .cmp => 0x39, |
| 576 | | .mov => 0x89, |
| 577 | | else => unreachable, |
| 578 | | }; |
| 579 | | return .{ .opc = opc, .modrm_ext = undefined }; |
| 556 | .mr => return switch (tag) { |
| 557 | .adc => 0x11, |
| 558 | .add => 0x01, |
| 559 | .sub => 0x29, |
| 560 | .xor => 0x31, |
| 561 | .@"and" => 0x21, |
| 562 | .@"or" => 0x09, |
| 563 | .sbb => 0x19, |
| 564 | .cmp => 0x39, |
| 565 | .mov => 0x89, |
| 566 | else => unreachable, |
| 580 | 567 | }, |
| 581 | | .rm => { |
| 582 | | const opc: u8 = switch (tag) { |
| 583 | | .adc => 0x13, |
| 584 | | .add => 0x03, |
| 585 | | .sub => 0x2b, |
| 586 | | .xor => 0x33, |
| 587 | | .@"and" => 0x23, |
| 588 | | .@"or" => 0x0b, |
| 589 | | .sbb => 0x1b, |
| 590 | | .cmp => 0x3b, |
| 591 | | .mov => 0x8b, |
| 592 | | else => unreachable, |
| 593 | | }; |
| 594 | | return .{ .opc = opc, .modrm_ext = undefined }; |
| 568 | .rm => return switch (tag) { |
| 569 | .adc => 0x13, |
| 570 | .add => 0x03, |
| 571 | .sub => 0x2b, |
| 572 | .xor => 0x33, |
| 573 | .@"and" => 0x23, |
| 574 | .@"or" => 0x0b, |
| 575 | .sbb => 0x1b, |
| 576 | .cmp => 0x3b, |
| 577 | .mov => 0x8b, |
| 578 | else => unreachable, |
| 579 | }, |
| 580 | .oi => return switch (tag) { |
| 581 | .mov => 0xb8, |
| 582 | else => unreachable, |
| 595 | 583 | }, |
| 596 | 584 | } |
| 597 | 585 | } |
| 598 | 586 | |
| 587 | inline fn getMiModRmExt(tag: Mir.Inst.Tag) u3 { |
| 588 | return switch (tag) { |
| 589 | .adc => 0x2, |
| 590 | .add => 0x0, |
| 591 | .sub => 0x5, |
| 592 | .xor => 0x6, |
| 593 | .@"and" => 0x4, |
| 594 | .@"or" => 0x1, |
| 595 | .sbb => 0x3, |
| 596 | .cmp => 0x7, |
| 597 | .mov => 0x0, |
| 598 | else => unreachable, |
| 599 | }; |
| 600 | } |
| 601 | |
| 599 | 602 | const ScaleIndexBase = struct { |
| 600 | 603 | scale: u2, |
| 601 | 604 | index_reg: ?Register, |
| ... | ... | @@ -626,16 +629,56 @@ const RegisterOrMemory = union(enum) { |
| 626 | 629 | } |
| 627 | 630 | }; |
| 628 | 631 | |
| 632 | fn lowerToOiEnc( |
| 633 | tag: Mir.Inst.Tag, |
| 634 | reg: Register, |
| 635 | imm: i64, |
| 636 | code: *std.ArrayList(u8), |
| 637 | ) InnerError!void { |
| 638 | var opc = getOpCode(tag, .oi); |
| 639 | if (reg.size() != immOpSize(imm)) return error.EmitFail; |
| 640 | if (reg.size() == 8) { |
| 641 | opc -= 8; |
| 642 | } |
| 643 | const encoder = try Encoder.init(code, 10); |
| 644 | encoder.rex(.{ |
| 645 | .w = reg.size() == 64, |
| 646 | .b = reg.isExtended(), |
| 647 | }); |
| 648 | encoder.opcode_withReg(opc, reg.lowId()); |
| 649 | switch (reg.size()) { |
| 650 | 8 => { |
| 651 | const imm8 = try math.cast(i8, imm); |
| 652 | encoder.imm8(imm8); |
| 653 | }, |
| 654 | 16 => { |
| 655 | const imm16 = try math.cast(i16, imm); |
| 656 | encoder.imm16(imm16); |
| 657 | }, |
| 658 | 32 => { |
| 659 | const imm32 = try math.cast(i32, imm); |
| 660 | encoder.imm32(imm32); |
| 661 | }, |
| 662 | 64 => { |
| 663 | encoder.imm64(@bitCast(u64, imm)); |
| 664 | }, |
| 665 | else => unreachable, |
| 666 | } |
| 667 | } |
| 668 | |
| 629 | 669 | fn lowerToMiEnc( |
| 630 | 670 | tag: Mir.Inst.Tag, |
| 631 | 671 | reg_or_mem: RegisterOrMemory, |
| 632 | 672 | imm: i32, |
| 633 | 673 | code: *std.ArrayList(u8), |
| 634 | 674 | ) InnerError!void { |
| 635 | | const opcode = getOpCode(tag, .mi); |
| 675 | var opc = getOpCode(tag, .mi); |
| 676 | const modrm_ext = getMiModRmExt(tag); |
| 636 | 677 | switch (reg_or_mem) { |
| 637 | 678 | .register => |dst_reg| { |
| 638 | | const opc: u8 = if (dst_reg.size() == 8) opcode.opc - 1 else opcode.opc; |
| 679 | if (dst_reg.size() == 8) { |
| 680 | opc -= 1; |
| 681 | } |
| 639 | 682 | const encoder = try Encoder.init(code, 7); |
| 640 | 683 | if (dst_reg.size() == 16) { |
| 641 | 684 | // 0x66 prefix switches to the non-default size; here we assume a switch from |
| ... | ... | @@ -648,7 +691,7 @@ fn lowerToMiEnc( |
| 648 | 691 | .b = dst_reg.isExtended(), |
| 649 | 692 | }); |
| 650 | 693 | encoder.opcode_1byte(opc); |
| 651 | | encoder.modRm_direct(opcode.modrm_ext, dst_reg.lowId()); |
| 694 | encoder.modRm_direct(modrm_ext, dst_reg.lowId()); |
| 652 | 695 | switch (dst_reg.size()) { |
| 653 | 696 | 8 => { |
| 654 | 697 | const imm8 = try math.cast(i8, imm); |
| ... | ... | @@ -676,25 +719,25 @@ fn lowerToMiEnc( |
| 676 | 719 | .w = false, |
| 677 | 720 | .b = dst_reg.isExtended(), |
| 678 | 721 | }); |
| 679 | | encoder.opcode_1byte(opcode.opc); |
| 722 | encoder.opcode_1byte(opc); |
| 680 | 723 | if (dst_mem.disp == 0) { |
| 681 | | encoder.modRm_indirectDisp0(opcode.modrm_ext, dst_reg.lowId()); |
| 724 | encoder.modRm_indirectDisp0(modrm_ext, dst_reg.lowId()); |
| 682 | 725 | } else if (immOpSize(dst_mem.disp) == 8) { |
| 683 | | encoder.modRm_indirectDisp8(opcode.modrm_ext, dst_reg.lowId()); |
| 726 | encoder.modRm_indirectDisp8(modrm_ext, dst_reg.lowId()); |
| 684 | 727 | encoder.disp8(@intCast(i8, dst_mem.disp)); |
| 685 | 728 | } else { |
| 686 | 729 | if (dst_reg.lowId() == 4) { |
| 687 | | encoder.modRm_SIBDisp32(opcode.modrm_ext); |
| 730 | encoder.modRm_SIBDisp32(modrm_ext); |
| 688 | 731 | encoder.sib_baseDisp32(dst_reg.lowId()); |
| 689 | 732 | encoder.disp32(dst_mem.disp); |
| 690 | 733 | } else { |
| 691 | | encoder.modRm_indirectDisp32(opcode.modrm_ext, dst_reg.lowId()); |
| 734 | encoder.modRm_indirectDisp32(modrm_ext, dst_reg.lowId()); |
| 692 | 735 | encoder.disp32(dst_mem.disp); |
| 693 | 736 | } |
| 694 | 737 | } |
| 695 | 738 | } else { |
| 696 | | encoder.opcode_1byte(opcode.opc); |
| 697 | | encoder.modRm_SIBDisp0(opcode.modrm_ext); |
| 739 | encoder.opcode_1byte(opc); |
| 740 | encoder.modRm_SIBDisp0(modrm_ext); |
| 698 | 741 | encoder.sib_disp32(); |
| 699 | 742 | encoder.disp32(dst_mem.disp); |
| 700 | 743 | } |
| ... | ... | @@ -709,8 +752,10 @@ fn lowerToRmEnc( |
| 709 | 752 | reg_or_mem: RegisterOrMemory, |
| 710 | 753 | code: *std.ArrayList(u8), |
| 711 | 754 | ) InnerError!void { |
| 712 | | const opcode = getOpCode(tag, .rm); |
| 713 | | const opc: u8 = if (reg.size() == 8) opcode.opc - 1 else opcode.opc; |
| 755 | var opc = getOpCode(tag, .rm); |
| 756 | if (reg.size() == 8) { |
| 757 | opc -= 1; |
| 758 | } |
| 714 | 759 | switch (reg_or_mem) { |
| 715 | 760 | .register => |src_reg| { |
| 716 | 761 | if (reg.size() != src_reg.size()) return error.EmitFail; |
| ... | ... | @@ -779,8 +824,10 @@ fn lowerToMrEnc( |
| 779 | 824 | // * reg is 32bit - dword ptr |
| 780 | 825 | // * reg is 16bit - word ptr |
| 781 | 826 | // * 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; |
| 827 | var opc = getOpCode(tag, .mr); |
| 828 | if (reg.size() == 8) { |
| 829 | opc -= 1; |
| 830 | } |
| 784 | 831 | switch (reg_or_mem) { |
| 785 | 832 | .register => |dst_reg| { |
| 786 | 833 | if (dst_reg.size() != reg.size()) return error.EmitFail; |
| ... | ... | @@ -890,7 +937,7 @@ fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!voi |
| 890 | 937 | } |
| 891 | 938 | } |
| 892 | 939 | |
| 893 | | fn immOpSize(imm: i32) u8 { |
| 940 | fn immOpSize(imm: i64) u8 { |
| 894 | 941 | blk: { |
| 895 | 942 | _ = math.cast(i8, imm) catch break :blk; |
| 896 | 943 | return 8; |
| ... | ... | @@ -899,15 +946,21 @@ fn immOpSize(imm: i32) u8 { |
| 899 | 946 | _ = math.cast(i16, imm) catch break :blk; |
| 900 | 947 | return 16; |
| 901 | 948 | } |
| 902 | | return 32; |
| 949 | blk: { |
| 950 | _ = math.cast(i32, imm) catch break :blk; |
| 951 | return 32; |
| 952 | } |
| 953 | return 64; |
| 903 | 954 | } |
| 904 | 955 | |
| 905 | 956 | fn mirArithScaleSrc(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { |
| 906 | 957 | const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); |
| 907 | 958 | const scale = ops.flags; |
| 908 | 959 | // OP reg1, [reg2 + scale*rcx + imm32] |
| 909 | | const opcode = getOpCode(tag, .rm); |
| 910 | | const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 960 | var opc = getOpCode(tag, .rm); |
| 961 | if (ops.reg1.size() == 8) { |
| 962 | opc -= 1; |
| 963 | } |
| 911 | 964 | const imm = emit.mir.instructions.items(.data)[inst].imm; |
| 912 | 965 | const encoder = try Encoder.init(emit.code, 8); |
| 913 | 966 | encoder.rex(.{ |
| ... | ... | @@ -934,15 +987,18 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE |
| 934 | 987 | |
| 935 | 988 | if (ops.reg2 == .none) { |
| 936 | 989 | // OP [reg1 + scale*rax + 0], imm32 |
| 937 | | const opcode = getOpCode(tag, .mi); |
| 938 | | const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 990 | var opc = getOpCode(tag, .mi); |
| 991 | const modrm_ext = getMiModRmExt(tag); |
| 992 | if (ops.reg1.size() == 8) { |
| 993 | opc -= 1; |
| 994 | } |
| 939 | 995 | const encoder = try Encoder.init(emit.code, 8); |
| 940 | 996 | encoder.rex(.{ |
| 941 | 997 | .w = ops.reg1.size() == 64, |
| 942 | 998 | .b = ops.reg1.isExtended(), |
| 943 | 999 | }); |
| 944 | 1000 | encoder.opcode_1byte(opc); |
| 945 | | encoder.modRm_SIBDisp0(opcode.modrm_ext); |
| 1001 | encoder.modRm_SIBDisp0(modrm_ext); |
| 946 | 1002 | encoder.sib_scaleIndexBase(scale, Register.rax.lowId(), ops.reg1.lowId()); |
| 947 | 1003 | if (imm <= math.maxInt(i8)) { |
| 948 | 1004 | encoder.imm8(@intCast(i8, imm)); |
| ... | ... | @@ -955,8 +1011,10 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE |
| 955 | 1011 | } |
| 956 | 1012 | |
| 957 | 1013 | // OP [reg1 + scale*rax + imm32], reg2 |
| 958 | | const opcode = getOpCode(tag, .mr); |
| 959 | | const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 1014 | var opc = getOpCode(tag, .mr); |
| 1015 | if (ops.reg1.size() == 8) { |
| 1016 | opc -= 1; |
| 1017 | } |
| 960 | 1018 | const encoder = try Encoder.init(emit.code, 8); |
| 961 | 1019 | encoder.rex(.{ |
| 962 | 1020 | .w = ops.reg1.size() == 64, |
| ... | ... | @@ -980,8 +1038,11 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE |
| 980 | 1038 | const scale = ops.flags; |
| 981 | 1039 | const payload = emit.mir.instructions.items(.data)[inst].payload; |
| 982 | 1040 | const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; |
| 983 | | const opcode = getOpCode(tag, .mi); |
| 984 | | const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; |
| 1041 | var opc = getOpCode(tag, .mi); |
| 1042 | if (ops.reg1.size() == 8) { |
| 1043 | opc -= 1; |
| 1044 | } |
| 1045 | const modrm_ext = getMiModRmExt(tag); |
| 985 | 1046 | const encoder = try Encoder.init(emit.code, 2); |
| 986 | 1047 | encoder.rex(.{ |
| 987 | 1048 | .w = ops.reg1.size() == 64, |
| ... | ... | @@ -989,11 +1050,11 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE |
| 989 | 1050 | }); |
| 990 | 1051 | encoder.opcode_1byte(opc); |
| 991 | 1052 | if (imm_pair.dest_off <= math.maxInt(i8)) { |
| 992 | | encoder.modRm_SIBDisp8(opcode.modrm_ext); |
| 1053 | encoder.modRm_SIBDisp8(modrm_ext); |
| 993 | 1054 | encoder.sib_scaleIndexBaseDisp8(scale, Register.rax.lowId(), ops.reg1.lowId()); |
| 994 | 1055 | encoder.disp8(@intCast(i8, imm_pair.dest_off)); |
| 995 | 1056 | } else { |
| 996 | | encoder.modRm_SIBDisp32(opcode.modrm_ext); |
| 1057 | encoder.modRm_SIBDisp32(modrm_ext); |
| 997 | 1058 | encoder.sib_scaleIndexBaseDisp32(scale, Register.rax.lowId(), ops.reg1.lowId()); |
| 998 | 1059 | encoder.disp32(imm_pair.dest_off); |
| 999 | 1060 | } |
| ... | ... | @@ -1005,21 +1066,19 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 1005 | 1066 | assert(tag == .movabs); |
| 1006 | 1067 | const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); |
| 1007 | 1068 | |
| 1069 | if (ops.flags == 0b00) { |
| 1070 | // movabs reg, imm64 |
| 1071 | // OI |
| 1072 | const imm: i64 = if (ops.reg1.size() == 64) blk: { |
| 1073 | const payload = emit.mir.instructions.items(.data)[inst].payload; |
| 1074 | const imm = emit.mir.extraData(Mir.Imm64, payload).data; |
| 1075 | break :blk @bitCast(i64, imm.decode()); |
| 1076 | } else emit.mir.instructions.items(.data)[inst].imm; |
| 1077 | return lowerToOiEnc(.mov, ops.reg1, imm, emit.code); |
| 1078 | } |
| 1079 | |
| 1008 | 1080 | const encoder = try Encoder.init(emit.code, 10); |
| 1009 | 1081 | const is_64 = blk: { |
| 1010 | | if (ops.flags == 0b00) { |
| 1011 | | // movabs reg, imm64 |
| 1012 | | const opc: u8 = if (ops.reg1.size() == 8) 0xb0 else 0xb8; |
| 1013 | | if (ops.reg1.size() == 64) { |
| 1014 | | encoder.rex(.{ |
| 1015 | | .w = true, |
| 1016 | | .b = ops.reg1.isExtended(), |
| 1017 | | }); |
| 1018 | | encoder.opcode_withReg(opc, ops.reg1.lowId()); |
| 1019 | | break :blk true; |
| 1020 | | } |
| 1021 | | break :blk false; |
| 1022 | | } |
| 1023 | 1082 | if (ops.reg1 == .none) { |
| 1024 | 1083 | // movabs moffs64, rax |
| 1025 | 1084 | const opc: u8 = if (ops.reg2.size() == 8) 0xa2 else 0xa3; |
| ... | ... | @@ -1486,3 +1545,24 @@ test "lower MR encoding" { |
| 1486 | 1545 | "sub qword ptr [r11 + 0x10000000], r12", |
| 1487 | 1546 | ); |
| 1488 | 1547 | } |
| 1548 | |
| 1549 | test "lower OI encoding" { |
| 1550 | var code = TestEmitCode.init(); |
| 1551 | defer code.deinit(); |
| 1552 | try lowerToOiEnc(.mov, .rax, 0x1000000000000000, code.buffer()); |
| 1553 | try expectEqualHexStrings( |
| 1554 | "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x10", |
| 1555 | code.emitted(), |
| 1556 | "movabs rax, 0x1000000000000000", |
| 1557 | ); |
| 1558 | try lowerToOiEnc(.mov, .r11, 0x1000000000000000, code.buffer()); |
| 1559 | try expectEqualHexStrings( |
| 1560 | "\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x10", |
| 1561 | code.emitted(), |
| 1562 | "movabs r11, 0x1000000000000000", |
| 1563 | ); |
| 1564 | try lowerToOiEnc(.mov, .r11d, 0x10000000, code.buffer()); |
| 1565 | try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", code.emitted(), "mov r11d, 0x10000000"); |
| 1566 | try lowerToOiEnc(.mov, .r11b, 0x10, code.buffer()); |
| 1567 | try expectEqualHexStrings("\x41\xB3\x10", code.emitted(), "mov r11b, 0x10"); |
| 1568 | } |