authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-12-15 23:07:09+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2021-12-20 17:59:37+01:00
log70bff2f4d5f52f17a6e7090e11c300aff81bd87b
tree64ba9378ee478e26d79171c92a336d882d2e7f11
parentef0566df7858df3a770a2b3112ca991358974be3

stage2: fix MOV MIR -> Isel lowering logic

* ensure that every callsite of basic MOV MIR instruction follows the Intel syntax (dst <- src) * add extensive unit tests for MOV MIR -> Isel lowering * leave TODOs for cases that are currently not handled and/or missing * fix any ABI size mismatch between operands

4 files changed, 660 insertions(+), 28 deletions(-)

src/arch/x86_64/CodeGen.zig+10-9
......@@ -275,7 +275,9 @@ pub fn generate(
275275 .stack_align = undefined,
276276 .end_di_line = module_fn.rbrace_line,
277277 .end_di_column = module_fn.rbrace_column,
278 .mir_to_air_map = if (builtin.mode == .Debug) std.AutoHashMap(Mir.Inst.Index, Air.Inst.Index).init(bin_file.allocator) else {},
278 .mir_to_air_map = if (builtin.mode == .Debug)
279 std.AutoHashMap(Mir.Inst.Index, Air.Inst.Index).init(bin_file.allocator)
280 else {},
279281 };
280282 defer function.stack.deinit(bin_file.allocator);
281283 defer function.blocks.deinit(bin_file.allocator);
......@@ -386,8 +388,8 @@ fn gen(self: *Self) InnerError!void {
386388 _ = try self.addInst(.{
387389 .tag = .mov,
388390 .ops = (Mir.Ops{
389 .reg1 = .rsp,
390 .reg2 = .rbp,
391 .reg1 = .rbp,
392 .reg2 = .rsp,
391393 }).encode(),
392394 .data = undefined,
393395 });
......@@ -2838,8 +2840,8 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro
28382840 _ = try self.addInst(.{
28392841 .tag = .mov,
28402842 .ops = (Mir.Ops{
2841 .reg1 = registerAlias(reg, @intCast(u32, abi_size)),
2842 .reg2 = registerAlias(.rbp, @intCast(u32, abi_size)),
2843 .reg1 = .rbp,
2844 .reg2 = registerAlias(reg, @intCast(u32, abi_size)),
28432845 .flags = 0b10,
28442846 }).encode(),
28452847 .data = .{ .imm = -@intCast(i32, adj_off) },
......@@ -2925,7 +2927,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
29252927 _ = try self.addInst(.{
29262928 .tag = .mov,
29272929 .ops = (Mir.Ops{
2928 .reg1 = reg,
2930 .reg1 = registerAlias(reg, 4),
29292931 }).encode(),
29302932 .data = .{ .imm = @intCast(i32, x) },
29312933 });
......@@ -3055,15 +3057,14 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
30553057 if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) {
30563058 return self.fail("stack offset too large", .{});
30573059 }
3058 const ioff = -@intCast(i32, off);
30593060 _ = try self.addInst(.{
30603061 .tag = .mov,
30613062 .ops = (Mir.Ops{
30623063 .reg1 = registerAlias(reg, @intCast(u32, abi_size)),
3063 .reg2 = registerAlias(.rbp, @intCast(u32, abi_size)),
3064 .reg2 = .rbp,
30643065 .flags = 0b01,
30653066 }).encode(),
3066 .data = .{ .imm = ioff },
3067 .data = .{ .imm = -@intCast(i32, off) },
30673068 });
30683069 },
30693070 }
src/arch/x86_64/Emit.zig+629-13
......@@ -11,8 +11,10 @@ const link = @import("../../link.zig");
1111const log = std.log.scoped(.codegen);
1212const math = std.math;
1313const mem = std.mem;
14const testing = std.testing;
1415
1516const Air = @import("../../Air.zig");
17const Allocator = mem.Allocator;
1618const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
1719const DW = std.dwarf;
1820const Encoder = bits.Encoder;
......@@ -46,6 +48,11 @@ const InnerError = error{
4648 EmitFail,
4749};
4850
51const EmitError = error{
52 OutOfMemory,
53 OperandSizeMismatch,
54};
55
4956const Reloc = struct {
5057 /// Offset of the instruction.
5158 source: u64,
......@@ -100,10 +107,7 @@ pub fn emitMir(emit: *Emit) InnerError!void {
100107 .sbb_scale_imm => try emit.mirArithScaleImm(.sbb, inst),
101108 .cmp_scale_imm => try emit.mirArithScaleImm(.cmp, inst),
102109
103 // Even though MOV is technically not an arithmetic op,
104 // its structure can be represented using the same set of
105 // opcode primitives.
106 .mov => try emit.mirArith(.mov, inst),
110 .mov => try emit.mirMov(inst),
107111 .mov_scale_src => try emit.mirArithScaleSrc(.mov, inst),
108112 .mov_scale_dst => try emit.mirArithScaleDst(.mov, inst),
109113 .mov_scale_imm => try emit.mirArithScaleImm(.mov, inst),
......@@ -566,7 +570,6 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode {
566570 .@"or" => .{ .opc = 0x81, .modrm_ext = 0x1 },
567571 .sbb => .{ .opc = 0x81, .modrm_ext = 0x3 },
568572 .cmp => .{ .opc = 0x81, .modrm_ext = 0x7 },
569 .mov => .{ .opc = 0xc7, .modrm_ext = 0x0 },
570573 else => unreachable,
571574 },
572575 .mr => {
......@@ -579,7 +582,6 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode {
579582 .@"or" => 0x09,
580583 .sbb => 0x19,
581584 .cmp => 0x39,
582 .mov => 0x89,
583585 else => unreachable,
584586 };
585587 return .{ .opc = opc, .modrm_ext = undefined };
......@@ -594,7 +596,6 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode {
594596 .@"or" => 0x0b,
595597 .sbb => 0x1b,
596598 .cmp => 0x3b,
597 .mov => 0x8b,
598599 else => unreachable,
599600 };
600601 return .{ .opc = opc, .modrm_ext = undefined };
......@@ -723,7 +724,7 @@ fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!voi
723724 // OP [reg1 + imm32], imm32
724725 // OP r/m64, imm32
725726 const payload = emit.mir.instructions.items(.data)[inst].payload;
726 const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data;
727 const imm_pair = Mir.extraData(emit.mir.extra, Mir.ImmPair, payload).data;
727728 const opcode = getArithOpCode(tag, .mi);
728729 const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;
729730 const encoder = try Encoder.init(emit.code, 11);
......@@ -835,7 +836,7 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE
835836 const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]);
836837 const scale = ops.flags;
837838 const payload = emit.mir.instructions.items(.data)[inst].payload;
838 const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data;
839 const imm_pair = Mir.extraData(emit.mir.extra, Mir.ImmPair, payload).data;
839840 const opcode = getArithOpCode(tag, .mi);
840841 const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc;
841842 const encoder = try Encoder.init(emit.code, 2);
......@@ -856,6 +857,240 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE
856857 encoder.imm32(imm_pair.operand);
857858}
858859
860fn mirMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
861 return mirMovImpl(
862 emit.mir.instructions,
863 emit.mir.extra,
864 inst,
865 emit.code,
866 ) catch |err| switch (err) {
867 // TODO better formating of operands in case of an error
868 error.OperandSizeMismatch => emit.fail("operand size mismatch", .{}),
869 else => emit.fail("emit failed with error: {}", .{err}),
870 };
871}
872
873fn mirMovImpl(
874 mir_instructions: std.MultiArrayList(Mir.Inst).Slice,
875 mir_extra: []const u32,
876 inst: Mir.Inst.Index,
877 code: *std.ArrayList(u8),
878) EmitError!void {
879 const ops = Mir.Ops.decode(mir_instructions.items(.ops)[inst]);
880 switch (ops.flags) {
881 0b00 => blk: {
882 if (ops.reg2 == .none) {
883 // mov reg1, imm32
884 // MI
885 const imm = mir_instructions.items(.data)[inst].imm;
886 const opc: u8 = if (ops.reg1.size() == 8) 0xc6 else 0xc7;
887 const modrm_ext: u3 = 0x0;
888 const encoder = try Encoder.init(code, 7);
889 if (ops.reg1.size() == 16) {
890 // 0x66 prefix switches to the non-default size; here we assume a switch from
891 // the default 32bits to 16bits operand-size.
892 // 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
893 encoder.opcode_1byte(0x66);
894 }
895 encoder.rex(.{
896 .w = ops.reg1.size() == 64,
897 .b = ops.reg1.isExtended(),
898 });
899 encoder.opcode_1byte(opc);
900 encoder.modRm_direct(modrm_ext, ops.reg1.lowId());
901 switch (ops.reg1.size()) {
902 8 => {
903 const imm8 = math.cast(i8, imm) catch return error.OperandSizeMismatch;
904 encoder.imm8(imm8);
905 },
906 16 => {
907 const imm16 = math.cast(i16, imm) catch return error.OperandSizeMismatch;
908 encoder.imm16(imm16);
909 },
910 32, 64 => {
911 encoder.imm32(imm);
912 },
913 else => unreachable,
914 }
915 break :blk;
916 }
917 // mov reg1, reg2
918 // MR
919 if (ops.reg1.size() != ops.reg2.size()) {
920 return error.OperandSizeMismatch;
921 }
922 const opc: u8 = if (ops.reg1.size() == 8) 0x88 else 0x89;
923 const encoder = try Encoder.init(code, 3);
924 encoder.rex(.{
925 .w = ops.reg1.size() == 64 and ops.reg2.size() == 64,
926 .r = ops.reg2.isExtended(),
927 .b = ops.reg1.isExtended(),
928 });
929 encoder.opcode_1byte(opc);
930 encoder.modRm_direct(ops.reg2.lowId(), ops.reg1.lowId());
931 },
932 0b01 => blk: {
933 const imm = mir_instructions.items(.data)[inst].imm;
934 const opc: u8 = if (ops.reg1.size() == 8) 0x8a else 0x8b;
935 if (ops.reg2 == .none) {
936 // mov reg1, [imm32]
937 // RM
938 const encoder = try Encoder.init(code, 9);
939 if (ops.reg1.size() == 16) {
940 encoder.opcode_1byte(0x66);
941 }
942 encoder.rex(.{
943 .w = ops.reg1.size() == 64,
944 .r = ops.reg1.isExtended(),
945 });
946 encoder.opcode_1byte(opc);
947 encoder.modRm_SIBDisp0(ops.reg1.lowId());
948 encoder.sib_disp32();
949 encoder.disp32(imm);
950 break :blk;
951 }
952 // mov reg1, [reg2 + imm32]
953 // RM
954 // TODO handle 32-bit base register - requires prefix 0x67
955 // Intel Manual, Vol 1, chapter 3.6 and 3.6.1
956 if (ops.reg2.size() != 64) {
957 return error.OperandSizeMismatch;
958 }
959 const encoder = try Encoder.init(code, 8);
960 if (ops.reg1.size() == 16) {
961 encoder.opcode_1byte(0x66);
962 }
963 encoder.rex(.{
964 .w = ops.reg1.size() == 64,
965 .r = ops.reg1.isExtended(),
966 .b = ops.reg2.isExtended(),
967 });
968 encoder.opcode_1byte(opc);
969 if (immOpSize(imm) == 8) {
970 encoder.modRm_indirectDisp8(ops.reg1.lowId(), ops.reg2.lowId());
971 encoder.disp8(@intCast(i8, imm));
972 } else {
973 encoder.modRm_indirectDisp32(ops.reg1.lowId(), ops.reg2.lowId());
974 encoder.disp32(imm);
975 }
976 },
977 0b10 => blk: {
978 // TODO handle 32-bit base register - requires prefix 0x67
979 // Intel Manual, Vol 1, chapter 3.6 and 3.6.1
980 if (ops.reg1.size() != 64) {
981 return error.OperandSizeMismatch;
982 }
983 if (ops.reg2 == .none) {
984 // mov [reg1 + 0], imm32
985 // MI
986 // Base register reg1 can either be 64bit or 32bit in size.
987 // TODO for memory operand, immediate operand pair, we currently
988 // have no way of flagging whether the immediate can be 8-, 16- or
989 // 32-bit and whether the corresponding memory operand is respectively
990 // a byte, word or dword ptr.
991 // TODO we currently don't have a way to flag imm32 64bit sign extended
992 const imm = mir_instructions.items(.data)[inst].imm;
993 const opc: u8 = 0xc7;
994 const modrm_ext: u3 = 0x0;
995 const encoder = try Encoder.init(code, 7);
996 encoder.rex(.{
997 .w = false,
998 .b = ops.reg1.isExtended(),
999 });
1000 encoder.opcode_1byte(opc);
1001 encoder.modRm_indirectDisp0(modrm_ext, ops.reg1.lowId());
1002 encoder.imm32(imm);
1003 break :blk;
1004 }
1005 // mov [reg1 + imm32], reg2
1006 // MR
1007 // We use size of source register reg2 to work out which
1008 // variant of memory ptr to pick:
1009 // * reg2 is 64bit - qword ptr
1010 // * reg2 is 32bit - dword ptr
1011 // * reg2 is 16bit - word ptr
1012 // * reg2 is 8bit - byte ptr
1013 const imm = mir_instructions.items(.data)[inst].imm;
1014 const opc: u8 = if (ops.reg2.size() == 8) 0x88 else 0x89;
1015 const encoder = try Encoder.init(code, 5);
1016 if (ops.reg2.size() == 16) {
1017 encoder.opcode_1byte(0x66);
1018 }
1019 encoder.rex(.{
1020 .w = ops.reg2.size() == 64,
1021 .r = ops.reg2.isExtended(),
1022 .b = ops.reg1.isExtended(),
1023 });
1024 encoder.opcode_1byte(opc);
1025 if (immOpSize(imm) == 8) {
1026 encoder.modRm_indirectDisp8(ops.reg2.lowId(), ops.reg1.lowId());
1027 encoder.disp8(@intCast(i8, imm));
1028 } else {
1029 encoder.modRm_indirectDisp32(ops.reg2.lowId(), ops.reg1.lowId());
1030 encoder.disp32(imm);
1031 }
1032 },
1033 0b11 => blk: {
1034 if (ops.reg2 == .none) {
1035 // mov [reg1 + imm32], imm32
1036 // MI
1037 // Base register reg1 can either be 64bit or 32bit in size.
1038 // TODO for memory operand, immediate operand pair, we currently
1039 // have no way of flagging whether the immediate can be 8-, 16- or
1040 // 32-bit and whether the corresponding memory operand is respectively
1041 // a byte, word or dword ptr.
1042 // TODO we currently don't have a way to flag imm32 64bit sign extended
1043 if (ops.reg1.size() != 64) {
1044 return error.OperandSizeMismatch;
1045 }
1046 const payload = mir_instructions.items(.data)[inst].payload;
1047 const imm_pair = Mir.extraData(mir_extra, Mir.ImmPair, payload).data;
1048 const imm_op = imm_pair.operand;
1049 const opc: u8 = 0xc7;
1050 const modrm_ext: u3 = 0x0;
1051 const encoder = try Encoder.init(code, 10);
1052 encoder.rex(.{
1053 .w = false,
1054 .b = ops.reg1.isExtended(),
1055 });
1056 encoder.opcode_1byte(opc);
1057 if (immOpSize(imm_pair.dest_off) == 8) {
1058 encoder.modRm_indirectDisp8(modrm_ext, ops.reg1.lowId());
1059 encoder.disp8(@intCast(i8, imm_pair.dest_off));
1060 } else {
1061 encoder.modRm_indirectDisp32(modrm_ext, ops.reg1.lowId());
1062 encoder.disp32(imm_pair.dest_off);
1063 }
1064 encoder.imm32(imm_op);
1065 break :blk;
1066 }
1067 // mov reg1, reg2
1068 // RM
1069 const opc: u8 = if (ops.reg1.size() == 8) 0x8a else 0x8b;
1070 const encoder = try Encoder.init(code, 3);
1071 encoder.rex(.{
1072 .w = ops.reg1.size() == 64 and ops.reg2.size() == 64,
1073 .r = ops.reg1.isExtended(),
1074 .b = ops.reg2.isExtended(),
1075 });
1076 encoder.opcode_1byte(opc);
1077 encoder.modRm_direct(ops.reg1.lowId(), ops.reg2.lowId());
1078 },
1079 }
1080}
1081
1082fn immOpSize(imm: i32) u8 {
1083 blk: {
1084 _ = math.cast(i8, imm) catch break :blk;
1085 return 8;
1086 }
1087 blk: {
1088 _ = math.cast(i16, imm) catch break :blk;
1089 return 16;
1090 }
1091 return 32;
1092}
1093
8591094fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
8601095 const tag = emit.mir.instructions.items(.tag)[inst];
8611096 assert(tag == .movabs);
......@@ -897,7 +1132,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
8971132
8981133 if (is_64) {
8991134 const payload = emit.mir.instructions.items(.data)[inst].payload;
900 const imm64 = emit.mir.extraData(Mir.Imm64, payload).data;
1135 const imm64 = Mir.extraData(emit.mir.extra, Mir.Imm64, payload).data;
9011136 encoder.imm64(imm64.decode());
9021137 } else {
9031138 const imm = emit.mir.instructions.items(.data)[inst].imm;
......@@ -1003,7 +1238,7 @@ fn mirLeaRip(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
10031238 const end_offset = emit.code.items.len;
10041239 if (@truncate(u1, ops.flags) == 0b0) {
10051240 const payload = emit.mir.instructions.items(.data)[inst].payload;
1006 const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode();
1241 const imm = Mir.extraData(emit.mir.extra, Mir.Imm64, payload).data.decode();
10071242 encoder.disp32(@intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset + 4)));
10081243 } else {
10091244 const got_entry = emit.mir.instructions.items(.data)[inst].got_entry;
......@@ -1058,7 +1293,7 @@ fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
10581293 const tag = emit.mir.instructions.items(.tag)[inst];
10591294 assert(tag == .dbg_line);
10601295 const payload = emit.mir.instructions.items(.data)[inst].payload;
1061 const dbg_line_column = emit.mir.extraData(Mir.DbgLineColumn, payload).data;
1296 const dbg_line_column = Mir.extraData(emit.mir.extra, Mir.DbgLineColumn, payload).data;
10621297 try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column);
10631298}
10641299
......@@ -1143,7 +1378,7 @@ fn mirArgDbgInfo(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
11431378 const tag = emit.mir.instructions.items(.tag)[inst];
11441379 assert(tag == .arg_dbg_info);
11451380 const payload = emit.mir.instructions.items(.data)[inst].payload;
1146 const arg_dbg_info = emit.mir.extraData(Mir.ArgDbgInfo, payload).data;
1381 const arg_dbg_info = Mir.extraData(emit.mir.extra, Mir.ArgDbgInfo, payload).data;
11471382 const mcv = emit.mir.function.args[arg_dbg_info.arg_index];
11481383 try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv);
11491384}
......@@ -1206,3 +1441,384 @@ fn addDbgInfoTypeReloc(emit: *Emit, ty: Type) !void {
12061441 .none => {},
12071442 }
12081443}
1444
1445const Mock = struct {
1446 const gpa = testing.allocator;
1447
1448 mir_instructions: std.MultiArrayList(Mir.Inst) = .{},
1449 mir_extra: std.ArrayList(u32),
1450 code: std.ArrayList(u8),
1451
1452 fn init() Mock {
1453 return .{
1454 .mir_extra = std.ArrayList(u32).init(gpa),
1455 .code = std.ArrayList(u8).init(gpa),
1456 };
1457 }
1458
1459 fn deinit(self: *Mock) void {
1460 self.mir_instructions.deinit(gpa);
1461 self.mir_extra.deinit();
1462 self.code.deinit();
1463 }
1464
1465 fn addInst(self: *Mock, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index {
1466 try self.mir_instructions.ensureUnusedCapacity(gpa, 1);
1467 const result_index = @intCast(Air.Inst.Index, self.mir_instructions.len);
1468 self.mir_instructions.appendAssumeCapacity(inst);
1469 return result_index;
1470 }
1471
1472 fn addExtra(self: *Mock, extra: anytype) Allocator.Error!u32 {
1473 const fields = std.meta.fields(@TypeOf(extra));
1474 try self.mir_extra.ensureUnusedCapacity(fields.len);
1475 return self.addExtraAssumeCapacity(extra);
1476 }
1477
1478 fn addExtraAssumeCapacity(self: *Mock, extra: anytype) u32 {
1479 const fields = std.meta.fields(@TypeOf(extra));
1480 const result = @intCast(u32, self.mir_extra.items.len);
1481 inline for (fields) |field| {
1482 self.mir_extra.appendAssumeCapacity(switch (field.field_type) {
1483 u32 => @field(extra, field.name),
1484 i32 => @bitCast(u32, @field(extra, field.name)),
1485 else => @compileError("bad field type"),
1486 });
1487 }
1488 return result;
1489 }
1490
1491 fn testEmitSingleSuccess(
1492 self: *Mock,
1493 mir_inst: Mir.Inst,
1494 expected_enc: []const u8,
1495 assembly: []const u8,
1496 ) !void {
1497 const code_index = self.code.items.len;
1498 const mir_index = try self.addInst(mir_inst);
1499 switch (mir_inst.tag) {
1500 .mov => try mirMovImpl(
1501 self.mir_instructions.slice(),
1502 self.mir_extra.items,
1503 mir_index,
1504 &self.code,
1505 ),
1506 else => unreachable,
1507 }
1508 const code_len = if (self.code.items[code_index..].len >= expected_enc.len)
1509 expected_enc.len
1510 else
1511 self.code.items.len - code_index;
1512 try expectEqualHexStrings(expected_enc, self.code.items[code_index..][0..code_len], assembly);
1513 }
1514
1515 fn testEmitSingleError(self: *Mock, mir_inst: Mir.Inst, err: EmitError) !void {
1516 const index = try self.addInst(mir_inst);
1517 const res = switch (mir_inst.tag) {
1518 .mov => mirMovImpl(
1519 self.mir_instructions.slice(),
1520 self.mir_extra.items,
1521 index,
1522 &self.code,
1523 ),
1524 else => unreachable,
1525 };
1526 try testing.expectError(err, res);
1527 }
1528};
1529
1530fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void {
1531 assert(expected.len > 0);
1532 if (mem.eql(u8, expected, given)) return;
1533 const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)});
1534 defer testing.allocator.free(expected_fmt);
1535 const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)});
1536 defer testing.allocator.free(given_fmt);
1537 const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?;
1538 var padding = try testing.allocator.alloc(u8, idx + 5);
1539 defer testing.allocator.free(padding);
1540 mem.set(u8, padding, ' ');
1541 std.debug.print("\nASM: {s}\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{
1542 assembly,
1543 expected_fmt,
1544 given_fmt,
1545 padding,
1546 });
1547 return error.TestFailed;
1548}
1549
1550test "mov dst_reg, src_reg" {
1551 var mock = Mock.init();
1552 defer mock.deinit();
1553 try mock.testEmitSingleSuccess(.{
1554 .tag = .mov,
1555 .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .rsp }).encode(),
1556 .data = undefined,
1557 }, "\x48\x89\xe5", "mov rbp, rsp");
1558 try mock.testEmitSingleSuccess(.{
1559 .tag = .mov,
1560 .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .rax }).encode(),
1561 .data = undefined,
1562 }, "\x49\x89\xc4", "mov r12, rax");
1563 try mock.testEmitSingleError(.{
1564 .tag = .mov,
1565 .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .eax }).encode(),
1566 .data = undefined,
1567 }, error.OperandSizeMismatch);
1568 try mock.testEmitSingleError(.{
1569 .tag = .mov,
1570 .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .rax }).encode(),
1571 .data = undefined,
1572 }, error.OperandSizeMismatch);
1573 try mock.testEmitSingleSuccess(.{
1574 .tag = .mov,
1575 .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .eax }).encode(),
1576 .data = undefined,
1577 }, "\x41\x89\xc4", "mov r12d, eax");
1578
1579 // TODO mov r12b, ah requires a codepath without REX prefix
1580 try mock.testEmitSingleSuccess(.{
1581 .tag = .mov,
1582 .ops = (Mir.Ops{ .reg1 = .r12b, .reg2 = .al }).encode(),
1583 .data = undefined,
1584 }, "\x41\x88\xc4", "mov r12b, al");
1585}
1586
1587test "mov dst_reg, imm" {
1588 var mock = Mock.init();
1589 defer mock.deinit();
1590 try mock.testEmitSingleSuccess(.{
1591 .tag = .mov,
1592 .ops = (Mir.Ops{ .reg1 = .rcx }).encode(),
1593 .data = .{ .imm = 0x10 },
1594 }, "\x48\xc7\xc1\x10\x00\x00\x00", "mov rcx, 0x10");
1595
1596 // TODO we are wasting one byte here: this could be encoded as OI with the encoding opc + rd, imm8/16/32
1597 // b9 10 00 00 00
1598 try mock.testEmitSingleSuccess(.{
1599 .tag = .mov,
1600 .ops = (Mir.Ops{ .reg1 = .ecx }).encode(),
1601 .data = .{ .imm = 0x10 },
1602 }, "\xc7\xc1\x10\x00\x00\x00", "mov ecx, 0x10");
1603 try mock.testEmitSingleSuccess(.{
1604 .tag = .mov,
1605 .ops = (Mir.Ops{ .reg1 = .cx }).encode(),
1606 .data = .{ .imm = 0x10 },
1607 }, "\x66\xc7\xc1\x10\x00", "mov cx, 0x10");
1608 try mock.testEmitSingleSuccess(.{
1609 .tag = .mov,
1610 .ops = (Mir.Ops{ .reg1 = .r11w }).encode(),
1611 .data = .{ .imm = 0x10 },
1612 }, "\x66\x41\xC7\xC3\x10\x00", "mov r11w, 0x10");
1613 try mock.testEmitSingleSuccess(.{
1614 .tag = .mov,
1615 .ops = (Mir.Ops{ .reg1 = .cl }).encode(),
1616 .data = .{ .imm = 0x10 },
1617 }, "\xc6\xc1\x10", "mov cl, 0x10");
1618 try mock.testEmitSingleError(.{
1619 .tag = .mov,
1620 .ops = (Mir.Ops{ .reg1 = .cx }).encode(),
1621 .data = .{ .imm = 0x10000000 },
1622 }, error.OperandSizeMismatch);
1623 try mock.testEmitSingleError(.{
1624 .tag = .mov,
1625 .ops = (Mir.Ops{ .reg1 = .cl }).encode(),
1626 .data = .{ .imm = 0x1000 },
1627 }, error.OperandSizeMismatch);
1628}
1629
1630test "mov dst_reg, [imm32]" {
1631 var mock = Mock.init();
1632 defer mock.deinit();
1633 try mock.testEmitSingleSuccess(.{
1634 .tag = .mov,
1635 .ops = (Mir.Ops{ .reg1 = .rcx, .flags = 0b01 }).encode(),
1636 .data = .{ .imm = 0x10 },
1637 }, "\x48\x8B\x0C\x25\x10\x00\x00\x00", "mov rcx, [0x10]");
1638 try mock.testEmitSingleSuccess(.{
1639 .tag = .mov,
1640 .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b01 }).encode(),
1641 .data = .{ .imm = 0x10 },
1642 }, "\x4C\x8B\x1C\x25\x10\x00\x00\x00", "mov r11, [0x10]");
1643 try mock.testEmitSingleSuccess(.{
1644 .tag = .mov,
1645 .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b01 }).encode(),
1646 .data = .{ .imm = 0x10 },
1647 }, "\x44\x8B\x1C\x25\x10\x00\x00\x00", "mov r11d, [0x10]");
1648 try mock.testEmitSingleSuccess(.{
1649 .tag = .mov,
1650 .ops = (Mir.Ops{ .reg1 = .r11w, .flags = 0b01 }).encode(),
1651 .data = .{ .imm = 0x10 },
1652 }, "\x66\x44\x8B\x1C\x25\x10\x00\x00\x00", "mov r11w, [0x10]");
1653 try mock.testEmitSingleSuccess(.{
1654 .tag = .mov,
1655 .ops = (Mir.Ops{ .reg1 = .r11b, .flags = 0b01 }).encode(),
1656 .data = .{ .imm = 0x10 },
1657 }, "\x44\x8A\x1C\x25\x10\x00\x00\x00", "mov r11b, [0x10]");
1658}
1659
1660test "mov dst_reg, [src_reg + imm]" {
1661 var mock = Mock.init();
1662 defer mock.deinit();
1663 try mock.testEmitSingleSuccess(.{
1664 .tag = .mov,
1665 .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(),
1666 .data = .{ .imm = 0x10 },
1667 }, "\x48\x8B\x4D\x10", "mov rcx, [rbp + 0x10]");
1668 try mock.testEmitSingleSuccess(.{
1669 .tag = .mov,
1670 .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(),
1671 .data = .{ .imm = 0x10000000 },
1672 }, "\x48\x8B\x8D\x00\x00\x00\x10", "mov rcx, [rbp + 0x10000000]");
1673 try mock.testEmitSingleSuccess(.{
1674 .tag = .mov,
1675 .ops = (Mir.Ops{ .reg1 = .r11b, .reg2 = .rbp, .flags = 0b01 }).encode(),
1676 .data = .{ .imm = 0x10 },
1677 }, "\x44\x8A\x5D\x10", "mov r11b, [rbp + 0x10]");
1678 try mock.testEmitSingleSuccess(.{
1679 .tag = .mov,
1680 .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .rbp, .flags = 0b01 }).encode(),
1681 .data = .{ .imm = 0x10000000 },
1682 }, "\x66\x44\x8B\x9D\x00\x00\x00\x10", "mov r11w, [rbp + 0x10000000]");
1683}
1684
1685test "mov [dst_reg + 0], imm" {
1686 var mock = Mock.init();
1687 defer mock.deinit();
1688 try mock.testEmitSingleSuccess(.{
1689 .tag = .mov,
1690 .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b10 }).encode(),
1691 .data = .{ .imm = 0x10 },
1692 }, "\x41\xC7\x03\x10\x00\x00\x00", "mov dword ptr [r11 + 0], 0x10");
1693 try mock.testEmitSingleSuccess(.{
1694 .tag = .mov,
1695 .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(),
1696 .data = .{ .imm = 0x10000000 },
1697 }, "\xC7\x00\x00\x00\x00\x10", "mov dword ptr [rax + 0], 0x10000000");
1698 try mock.testEmitSingleSuccess(.{
1699 .tag = .mov,
1700 .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(),
1701 .data = .{ .imm = 0x1000 },
1702 }, "\xC7\x00\x00\x10\x00\x00", "mov dword ptr [rax + 0], 0x1000");
1703 try mock.testEmitSingleSuccess(.{
1704 .tag = .mov,
1705 .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(),
1706 .data = .{ .imm = 0x10 },
1707 }, "\xC7\x00\x10\x00\x00\x00", "mov dword ptr [rax + 0], 0x10");
1708 try mock.testEmitSingleError(.{
1709 .tag = .mov,
1710 .ops = (Mir.Ops{ .reg1 = .eax, .flags = 0b10 }).encode(),
1711 .data = .{ .imm = 0x10 },
1712 }, error.OperandSizeMismatch);
1713}
1714
1715test "mov [dst_reg + imm32], src_reg" {
1716 var mock = Mock.init();
1717 defer mock.deinit();
1718 try mock.testEmitSingleSuccess(.{
1719 .tag = .mov,
1720 .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11, .flags = 0b10 }).encode(),
1721 .data = .{ .imm = 0x10 },
1722 }, "\x4c\x89\x5d\x10", "mov qword ptr [rbp + 0x10], r11");
1723 try mock.testEmitSingleSuccess(.{
1724 .tag = .mov,
1725 .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11d, .flags = 0b10 }).encode(),
1726 .data = .{ .imm = 0x10 },
1727 }, "\x44\x89\x5d\x10", "mov dword ptr [rbp + 0x10], r11d");
1728 try mock.testEmitSingleSuccess(.{
1729 .tag = .mov,
1730 .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11w, .flags = 0b10 }).encode(),
1731 .data = .{ .imm = 0x10 },
1732 }, "\x66\x44\x89\x5d\x10", "mov word ptr [rbp + 0x10], r11w");
1733 try mock.testEmitSingleSuccess(.{
1734 .tag = .mov,
1735 .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11b, .flags = 0b10 }).encode(),
1736 .data = .{ .imm = 0x10 },
1737 }, "\x44\x88\x5d\x10", "mov byte ptr [rbp + 0x10], r11b");
1738 try mock.testEmitSingleSuccess(.{
1739 .tag = .mov,
1740 .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .rax, .flags = 0b10 }).encode(),
1741 .data = .{ .imm = 0x10 },
1742 }, "\x49\x89\x43\x10", "mov qword ptr [r11 + 0x10], rax");
1743 try mock.testEmitSingleSuccess(.{
1744 .tag = .mov,
1745 .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .eax, .flags = 0b10 }).encode(),
1746 .data = .{ .imm = 0x10 },
1747 }, "\x41\x89\x43\x10", "mov dword ptr [r11 + 0x10], eax");
1748 try mock.testEmitSingleError(.{
1749 .tag = .mov,
1750 .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .ax, .flags = 0b10 }).encode(),
1751 .data = .{ .imm = 0x10 },
1752 }, error.OperandSizeMismatch);
1753}
1754
1755test "mov [dst_reg + imm32], imm32" {
1756 var mock = Mock.init();
1757 defer mock.deinit();
1758 {
1759 const payload = try mock.addExtra(Mir.ImmPair{
1760 .dest_off = 0x10,
1761 .operand = 0x20000000,
1762 });
1763 try mock.testEmitSingleSuccess(.{
1764 .tag = .mov,
1765 .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(),
1766 .data = .{ .payload = payload },
1767 }, "\xC7\x45\x10\x00\x00\x00\x20", "mov dword ptr [rbp + 0x10], 0x20000000");
1768 }
1769 {
1770 const payload = try mock.addExtra(Mir.ImmPair{
1771 .dest_off = 0x10,
1772 .operand = 0x2000,
1773 });
1774 try mock.testEmitSingleSuccess(.{
1775 .tag = .mov,
1776 .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(),
1777 .data = .{ .payload = payload },
1778 }, "\xC7\x45\x10\x00\x20\x00\x00", "mov dword ptr [rbp + 0x10], 0x2000");
1779 }
1780 {
1781 const payload = try mock.addExtra(Mir.ImmPair{
1782 .dest_off = 0x10,
1783 .operand = 0x20,
1784 });
1785 try mock.testEmitSingleSuccess(.{
1786 .tag = .mov,
1787 .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(),
1788 .data = .{ .payload = payload },
1789 }, "\xc7\x45\x10\x20\x00\x00\x00", "mov dword ptr [rbp + 0x10], 0x20");
1790 }
1791 {
1792 const payload = try mock.addExtra(Mir.ImmPair{
1793 .dest_off = 0x10,
1794 .operand = 0x20000000,
1795 });
1796 try mock.testEmitSingleSuccess(.{
1797 .tag = .mov,
1798 .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(),
1799 .data = .{ .payload = payload },
1800 }, "\x41\xC7\x43\x10\x00\x00\x00\x20", "mov dword ptr [r11 + 0x10], 0x20000000");
1801 }
1802 {
1803 const payload = try mock.addExtra(Mir.ImmPair{
1804 .dest_off = 0x10000000,
1805 .operand = 0x20000000,
1806 });
1807 try mock.testEmitSingleSuccess(.{
1808 .tag = .mov,
1809 .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(),
1810 .data = .{ .payload = payload },
1811 }, "\x41\xC7\x83\x00\x00\x00\x10\x00\x00\x00\x20", "mov dword ptr [r11 + 0x10], 0x20000000");
1812 }
1813 {
1814 const payload = try mock.addExtra(Mir.ImmPair{
1815 .dest_off = 0x10,
1816 .operand = 0x20,
1817 });
1818 try mock.testEmitSingleError(.{
1819 .tag = .mov,
1820 .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b11 }).encode(),
1821 .data = .{ .payload = payload },
1822 }, error.OperandSizeMismatch);
1823 }
1824}
src/arch/x86_64/Mir.zig+18-3
......@@ -136,10 +136,24 @@ pub const Inst = struct {
136136 cmp_scale_src,
137137 cmp_scale_dst,
138138 cmp_scale_imm,
139
140 /// ops flags: form:
141 /// 0b00 reg1, reg2 (MR)
142 /// 0b00 reg1, imm32
143 /// 0b01 reg1, [reg2 + imm32]
144 /// 0b01 reg1, [ds:imm32]
145 /// 0b10 [reg1 + imm32], reg2
146 /// 0b10 [reg1 + 0], imm32
147 /// 0b11 [reg1 + imm32], imm32
148 /// 0b11 reg1, reg2 (RM)
149 /// Notes:
150 /// * If reg2 is `none` then it means Data field `imm` is used as the immediate.
151 /// * When two imm32 values are required, Data field `payload` points at `ImmPair`.
139152 mov,
140153 mov_scale_src,
141154 mov_scale_dst,
142155 mov_scale_imm,
156
143157 lea,
144158 lea_scale_src,
145159 lea_scale_dst,
......@@ -292,6 +306,7 @@ pub const Inst = struct {
292306 /// Another instruction.
293307 inst: Index,
294308 /// A 32-bit immediate value.
309 /// TODO we should add support for 16- and 8-bit immediate values.
295310 imm: i32,
296311 /// An extern function.
297312 /// Index into the linker's string table.
......@@ -378,14 +393,14 @@ pub const Ops = struct {
378393 }
379394};
380395
381pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } {
396pub fn extraData(mir_extra: []const u32, comptime T: type, index: usize) struct { data: T, end: usize } {
382397 const fields = std.meta.fields(T);
383398 var i: usize = index;
384399 var result: T = undefined;
385400 inline for (fields) |field| {
386401 @field(result, field.name) = switch (field.field_type) {
387 u32 => mir.extra[i],
388 i32 => @bitCast(i32, mir.extra[i]),
402 u32 => mir_extra[i],
403 i32 => @bitCast(i32, mir_extra[i]),
389404 else => @compileError("bad field type"),
390405 };
391406 i += 1;
src/arch/x86_64/PrintMir.zig+3-3
......@@ -481,7 +481,7 @@ fn mirArith(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index, w: any
481481 0b11 => {
482482 if (ops.reg2 == .none) {
483483 const payload = print.mir.instructions.items(.data)[inst].payload;
484 const imm_pair = print.mir.extraData(Mir.ImmPair, payload).data;
484 const imm_pair = Mir.extraData(print.mir.extra, Mir.ImmPair, payload).data;
485485 try w.print("[{s} + {d}], {d}", .{ @tagName(ops.reg1), imm_pair.dest_off, imm_pair.operand });
486486 }
487487 try w.writeAll("TODO");
......@@ -516,7 +516,7 @@ fn mirArithScaleImm(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index
516516 const ops = Mir.Ops.decode(print.mir.instructions.items(.ops)[inst]);
517517 const scale = ops.flags;
518518 const payload = print.mir.instructions.items(.data)[inst].payload;
519 const imm_pair = print.mir.extraData(Mir.ImmPair, payload).data;
519 const imm_pair = Mir.extraData(print.mir.extra, Mir.ImmPair, payload).data;
520520 try w.print("{s} [{s} + {d}*rcx + {d}], {d}\n", .{ @tagName(tag), @tagName(ops.reg1), scale, imm_pair.dest_off, imm_pair.operand });
521521}
522522
......@@ -528,7 +528,7 @@ fn mirMovabs(print: *const Print, inst: Mir.Inst.Index, w: anytype) !void {
528528 const is_64 = ops.reg1.size() == 64;
529529 const imm: i128 = if (is_64) blk: {
530530 const payload = print.mir.instructions.items(.data)[inst].payload;
531 const imm64 = print.mir.extraData(Mir.Imm64, payload).data;
531 const imm64 = Mir.extraData(print.mir.extra, Mir.Imm64, payload).data;
532532 break :blk imm64.decode();
533533 } else print.mir.instructions.items(.data)[inst].imm;
534534 if (ops.flags == 0b00) {