From 70bff2f4d5f52f17a6e7090e11c300aff81bd87b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 15 Dec 2021 23:07:09 +0100 Subject: [PATCH 01/19] 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 --- src/arch/x86_64/CodeGen.zig | 19 +- src/arch/x86_64/Emit.zig | 642 ++++++++++++++++++++++++++++++++++- src/arch/x86_64/Mir.zig | 21 +- src/arch/x86_64/PrintMir.zig | 6 +- 4 files changed, 660 insertions(+), 28 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 378184f70a..0a209b8807 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -275,7 +275,9 @@ pub fn generate( .stack_align = undefined, .end_di_line = module_fn.rbrace_line, .end_di_column = module_fn.rbrace_column, - .mir_to_air_map = if (builtin.mode == .Debug) std.AutoHashMap(Mir.Inst.Index, Air.Inst.Index).init(bin_file.allocator) else {}, + .mir_to_air_map = if (builtin.mode == .Debug) + std.AutoHashMap(Mir.Inst.Index, Air.Inst.Index).init(bin_file.allocator) + else {}, }; defer function.stack.deinit(bin_file.allocator); defer function.blocks.deinit(bin_file.allocator); @@ -386,8 +388,8 @@ fn gen(self: *Self) InnerError!void { _ = try self.addInst(.{ .tag = .mov, .ops = (Mir.Ops{ - .reg1 = .rsp, - .reg2 = .rbp, + .reg1 = .rbp, + .reg2 = .rsp, }).encode(), .data = undefined, }); @@ -2838,8 +2840,8 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: u32, mcv: MCValue) InnerErro _ = try self.addInst(.{ .tag = .mov, .ops = (Mir.Ops{ - .reg1 = registerAlias(reg, @intCast(u32, abi_size)), - .reg2 = registerAlias(.rbp, @intCast(u32, abi_size)), + .reg1 = .rbp, + .reg2 = registerAlias(reg, @intCast(u32, abi_size)), .flags = 0b10, }).encode(), .data = .{ .imm = -@intCast(i32, adj_off) }, @@ -2925,7 +2927,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void _ = try self.addInst(.{ .tag = .mov, .ops = (Mir.Ops{ - .reg1 = reg, + .reg1 = registerAlias(reg, 4), }).encode(), .data = .{ .imm = @intCast(i32, x) }, }); @@ -3055,15 +3057,14 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void if (off < std.math.minInt(i32) or off > std.math.maxInt(i32)) { return self.fail("stack offset too large", .{}); } - const ioff = -@intCast(i32, off); _ = try self.addInst(.{ .tag = .mov, .ops = (Mir.Ops{ .reg1 = registerAlias(reg, @intCast(u32, abi_size)), - .reg2 = registerAlias(.rbp, @intCast(u32, abi_size)), + .reg2 = .rbp, .flags = 0b01, }).encode(), - .data = .{ .imm = ioff }, + .data = .{ .imm = -@intCast(i32, off) }, }); }, } diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index b4d156252e..af87e0161a 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -11,8 +11,10 @@ const link = @import("../../link.zig"); const log = std.log.scoped(.codegen); const math = std.math; const mem = std.mem; +const testing = std.testing; const Air = @import("../../Air.zig"); +const Allocator = mem.Allocator; const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; const DW = std.dwarf; const Encoder = bits.Encoder; @@ -46,6 +48,11 @@ const InnerError = error{ EmitFail, }; +const EmitError = error{ + OutOfMemory, + OperandSizeMismatch, +}; + const Reloc = struct { /// Offset of the instruction. source: u64, @@ -100,10 +107,7 @@ pub fn emitMir(emit: *Emit) InnerError!void { .sbb_scale_imm => try emit.mirArithScaleImm(.sbb, inst), .cmp_scale_imm => try emit.mirArithScaleImm(.cmp, inst), - // Even though MOV is technically not an arithmetic op, - // its structure can be represented using the same set of - // opcode primitives. - .mov => try emit.mirArith(.mov, inst), + .mov => try emit.mirMov(inst), .mov_scale_src => try emit.mirArithScaleSrc(.mov, inst), .mov_scale_dst => try emit.mirArithScaleDst(.mov, inst), .mov_scale_imm => try emit.mirArithScaleImm(.mov, inst), @@ -566,7 +570,6 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { .@"or" => .{ .opc = 0x81, .modrm_ext = 0x1 }, .sbb => .{ .opc = 0x81, .modrm_ext = 0x3 }, .cmp => .{ .opc = 0x81, .modrm_ext = 0x7 }, - .mov => .{ .opc = 0xc7, .modrm_ext = 0x0 }, else => unreachable, }, .mr => { @@ -579,7 +582,6 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { .@"or" => 0x09, .sbb => 0x19, .cmp => 0x39, - .mov => 0x89, else => unreachable, }; return .{ .opc = opc, .modrm_ext = undefined }; @@ -594,7 +596,6 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { .@"or" => 0x0b, .sbb => 0x1b, .cmp => 0x3b, - .mov => 0x8b, else => unreachable, }; return .{ .opc = opc, .modrm_ext = undefined }; @@ -723,7 +724,7 @@ fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!voi // OP [reg1 + imm32], imm32 // OP r/m64, imm32 const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; + const imm_pair = Mir.extraData(emit.mir.extra, Mir.ImmPair, payload).data; const opcode = getArithOpCode(tag, .mi); const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; 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 const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; + const imm_pair = Mir.extraData(emit.mir.extra, Mir.ImmPair, payload).data; const opcode = getArithOpCode(tag, .mi); const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; 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 encoder.imm32(imm_pair.operand); } +fn mirMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { + return mirMovImpl( + emit.mir.instructions, + emit.mir.extra, + inst, + emit.code, + ) catch |err| switch (err) { + // TODO better formating of operands in case of an error + error.OperandSizeMismatch => emit.fail("operand size mismatch", .{}), + else => emit.fail("emit failed with error: {}", .{err}), + }; +} + +fn mirMovImpl( + mir_instructions: std.MultiArrayList(Mir.Inst).Slice, + mir_extra: []const u32, + inst: Mir.Inst.Index, + code: *std.ArrayList(u8), +) EmitError!void { + const ops = Mir.Ops.decode(mir_instructions.items(.ops)[inst]); + switch (ops.flags) { + 0b00 => blk: { + if (ops.reg2 == .none) { + // mov reg1, imm32 + // MI + const imm = mir_instructions.items(.data)[inst].imm; + const opc: u8 = if (ops.reg1.size() == 8) 0xc6 else 0xc7; + const modrm_ext: u3 = 0x0; + const encoder = try Encoder.init(code, 7); + if (ops.reg1.size() == 16) { + // 0x66 prefix switches to the non-default size; here we assume a switch from + // the default 32bits to 16bits operand-size. + // 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 + encoder.opcode_1byte(0x66); + } + encoder.rex(.{ + .w = ops.reg1.size() == 64, + .b = ops.reg1.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_direct(modrm_ext, ops.reg1.lowId()); + switch (ops.reg1.size()) { + 8 => { + const imm8 = math.cast(i8, imm) catch return error.OperandSizeMismatch; + encoder.imm8(imm8); + }, + 16 => { + const imm16 = math.cast(i16, imm) catch return error.OperandSizeMismatch; + encoder.imm16(imm16); + }, + 32, 64 => { + encoder.imm32(imm); + }, + else => unreachable, + } + break :blk; + } + // mov reg1, reg2 + // MR + if (ops.reg1.size() != ops.reg2.size()) { + return error.OperandSizeMismatch; + } + const opc: u8 = if (ops.reg1.size() == 8) 0x88 else 0x89; + const encoder = try Encoder.init(code, 3); + encoder.rex(.{ + .w = ops.reg1.size() == 64 and ops.reg2.size() == 64, + .r = ops.reg2.isExtended(), + .b = ops.reg1.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_direct(ops.reg2.lowId(), ops.reg1.lowId()); + }, + 0b01 => blk: { + const imm = mir_instructions.items(.data)[inst].imm; + const opc: u8 = if (ops.reg1.size() == 8) 0x8a else 0x8b; + if (ops.reg2 == .none) { + // mov reg1, [imm32] + // RM + const encoder = try Encoder.init(code, 9); + if (ops.reg1.size() == 16) { + encoder.opcode_1byte(0x66); + } + encoder.rex(.{ + .w = ops.reg1.size() == 64, + .r = ops.reg1.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_SIBDisp0(ops.reg1.lowId()); + encoder.sib_disp32(); + encoder.disp32(imm); + break :blk; + } + // mov reg1, [reg2 + imm32] + // RM + // TODO handle 32-bit base register - requires prefix 0x67 + // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 + if (ops.reg2.size() != 64) { + return error.OperandSizeMismatch; + } + const encoder = try Encoder.init(code, 8); + if (ops.reg1.size() == 16) { + encoder.opcode_1byte(0x66); + } + encoder.rex(.{ + .w = ops.reg1.size() == 64, + .r = ops.reg1.isExtended(), + .b = ops.reg2.isExtended(), + }); + encoder.opcode_1byte(opc); + if (immOpSize(imm) == 8) { + encoder.modRm_indirectDisp8(ops.reg1.lowId(), ops.reg2.lowId()); + encoder.disp8(@intCast(i8, imm)); + } else { + encoder.modRm_indirectDisp32(ops.reg1.lowId(), ops.reg2.lowId()); + encoder.disp32(imm); + } + }, + 0b10 => blk: { + // TODO handle 32-bit base register - requires prefix 0x67 + // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 + if (ops.reg1.size() != 64) { + return error.OperandSizeMismatch; + } + if (ops.reg2 == .none) { + // mov [reg1 + 0], imm32 + // MI + // Base register reg1 can either be 64bit or 32bit in size. + // TODO for memory operand, immediate operand pair, we currently + // have no way of flagging whether the immediate can be 8-, 16- or + // 32-bit and whether the corresponding memory operand is respectively + // a byte, word or dword ptr. + // TODO we currently don't have a way to flag imm32 64bit sign extended + const imm = mir_instructions.items(.data)[inst].imm; + const opc: u8 = 0xc7; + const modrm_ext: u3 = 0x0; + const encoder = try Encoder.init(code, 7); + encoder.rex(.{ + .w = false, + .b = ops.reg1.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_indirectDisp0(modrm_ext, ops.reg1.lowId()); + encoder.imm32(imm); + break :blk; + } + // mov [reg1 + imm32], reg2 + // MR + // We use size of source register reg2 to work out which + // variant of memory ptr to pick: + // * reg2 is 64bit - qword ptr + // * reg2 is 32bit - dword ptr + // * reg2 is 16bit - word ptr + // * reg2 is 8bit - byte ptr + const imm = mir_instructions.items(.data)[inst].imm; + const opc: u8 = if (ops.reg2.size() == 8) 0x88 else 0x89; + const encoder = try Encoder.init(code, 5); + if (ops.reg2.size() == 16) { + encoder.opcode_1byte(0x66); + } + encoder.rex(.{ + .w = ops.reg2.size() == 64, + .r = ops.reg2.isExtended(), + .b = ops.reg1.isExtended(), + }); + encoder.opcode_1byte(opc); + if (immOpSize(imm) == 8) { + encoder.modRm_indirectDisp8(ops.reg2.lowId(), ops.reg1.lowId()); + encoder.disp8(@intCast(i8, imm)); + } else { + encoder.modRm_indirectDisp32(ops.reg2.lowId(), ops.reg1.lowId()); + encoder.disp32(imm); + } + }, + 0b11 => blk: { + if (ops.reg2 == .none) { + // mov [reg1 + imm32], imm32 + // MI + // Base register reg1 can either be 64bit or 32bit in size. + // TODO for memory operand, immediate operand pair, we currently + // have no way of flagging whether the immediate can be 8-, 16- or + // 32-bit and whether the corresponding memory operand is respectively + // a byte, word or dword ptr. + // TODO we currently don't have a way to flag imm32 64bit sign extended + if (ops.reg1.size() != 64) { + return error.OperandSizeMismatch; + } + const payload = mir_instructions.items(.data)[inst].payload; + const imm_pair = Mir.extraData(mir_extra, Mir.ImmPair, payload).data; + const imm_op = imm_pair.operand; + const opc: u8 = 0xc7; + const modrm_ext: u3 = 0x0; + const encoder = try Encoder.init(code, 10); + encoder.rex(.{ + .w = false, + .b = ops.reg1.isExtended(), + }); + encoder.opcode_1byte(opc); + if (immOpSize(imm_pair.dest_off) == 8) { + encoder.modRm_indirectDisp8(modrm_ext, ops.reg1.lowId()); + encoder.disp8(@intCast(i8, imm_pair.dest_off)); + } else { + encoder.modRm_indirectDisp32(modrm_ext, ops.reg1.lowId()); + encoder.disp32(imm_pair.dest_off); + } + encoder.imm32(imm_op); + break :blk; + } + // mov reg1, reg2 + // RM + const opc: u8 = if (ops.reg1.size() == 8) 0x8a else 0x8b; + const encoder = try Encoder.init(code, 3); + encoder.rex(.{ + .w = ops.reg1.size() == 64 and ops.reg2.size() == 64, + .r = ops.reg1.isExtended(), + .b = ops.reg2.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_direct(ops.reg1.lowId(), ops.reg2.lowId()); + }, + } +} + +fn immOpSize(imm: i32) u8 { + blk: { + _ = math.cast(i8, imm) catch break :blk; + return 8; + } + blk: { + _ = math.cast(i16, imm) catch break :blk; + return 16; + } + return 32; +} + fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .movabs); @@ -897,7 +1132,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { if (is_64) { const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm64 = emit.mir.extraData(Mir.Imm64, payload).data; + const imm64 = Mir.extraData(emit.mir.extra, Mir.Imm64, payload).data; encoder.imm64(imm64.decode()); } else { const imm = emit.mir.instructions.items(.data)[inst].imm; @@ -1003,7 +1238,7 @@ fn mirLeaRip(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const end_offset = emit.code.items.len; if (@truncate(u1, ops.flags) == 0b0) { const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode(); + const imm = Mir.extraData(emit.mir.extra, Mir.Imm64, payload).data.decode(); encoder.disp32(@intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset + 4))); } else { 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 { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .dbg_line); const payload = emit.mir.instructions.items(.data)[inst].payload; - const dbg_line_column = emit.mir.extraData(Mir.DbgLineColumn, payload).data; + const dbg_line_column = Mir.extraData(emit.mir.extra, Mir.DbgLineColumn, payload).data; try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column); } @@ -1143,7 +1378,7 @@ fn mirArgDbgInfo(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .arg_dbg_info); const payload = emit.mir.instructions.items(.data)[inst].payload; - const arg_dbg_info = emit.mir.extraData(Mir.ArgDbgInfo, payload).data; + const arg_dbg_info = Mir.extraData(emit.mir.extra, Mir.ArgDbgInfo, payload).data; const mcv = emit.mir.function.args[arg_dbg_info.arg_index]; try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv); } @@ -1206,3 +1441,384 @@ fn addDbgInfoTypeReloc(emit: *Emit, ty: Type) !void { .none => {}, } } + +const Mock = struct { + const gpa = testing.allocator; + + mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, + mir_extra: std.ArrayList(u32), + code: std.ArrayList(u8), + + fn init() Mock { + return .{ + .mir_extra = std.ArrayList(u32).init(gpa), + .code = std.ArrayList(u8).init(gpa), + }; + } + + fn deinit(self: *Mock) void { + self.mir_instructions.deinit(gpa); + self.mir_extra.deinit(); + self.code.deinit(); + } + + fn addInst(self: *Mock, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { + try self.mir_instructions.ensureUnusedCapacity(gpa, 1); + const result_index = @intCast(Air.Inst.Index, self.mir_instructions.len); + self.mir_instructions.appendAssumeCapacity(inst); + return result_index; + } + + fn addExtra(self: *Mock, extra: anytype) Allocator.Error!u32 { + const fields = std.meta.fields(@TypeOf(extra)); + try self.mir_extra.ensureUnusedCapacity(fields.len); + return self.addExtraAssumeCapacity(extra); + } + + fn addExtraAssumeCapacity(self: *Mock, extra: anytype) u32 { + const fields = std.meta.fields(@TypeOf(extra)); + const result = @intCast(u32, self.mir_extra.items.len); + inline for (fields) |field| { + self.mir_extra.appendAssumeCapacity(switch (field.field_type) { + u32 => @field(extra, field.name), + i32 => @bitCast(u32, @field(extra, field.name)), + else => @compileError("bad field type"), + }); + } + return result; + } + + fn testEmitSingleSuccess( + self: *Mock, + mir_inst: Mir.Inst, + expected_enc: []const u8, + assembly: []const u8, + ) !void { + const code_index = self.code.items.len; + const mir_index = try self.addInst(mir_inst); + switch (mir_inst.tag) { + .mov => try mirMovImpl( + self.mir_instructions.slice(), + self.mir_extra.items, + mir_index, + &self.code, + ), + else => unreachable, + } + const code_len = if (self.code.items[code_index..].len >= expected_enc.len) + expected_enc.len + else + self.code.items.len - code_index; + try expectEqualHexStrings(expected_enc, self.code.items[code_index..][0..code_len], assembly); + } + + fn testEmitSingleError(self: *Mock, mir_inst: Mir.Inst, err: EmitError) !void { + const index = try self.addInst(mir_inst); + const res = switch (mir_inst.tag) { + .mov => mirMovImpl( + self.mir_instructions.slice(), + self.mir_extra.items, + index, + &self.code, + ), + else => unreachable, + }; + try testing.expectError(err, res); + } +}; + +fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void { + assert(expected.len > 0); + if (mem.eql(u8, expected, given)) return; + const expected_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(expected)}); + defer testing.allocator.free(expected_fmt); + const given_fmt = try std.fmt.allocPrint(testing.allocator, "{x}", .{std.fmt.fmtSliceHexLower(given)}); + defer testing.allocator.free(given_fmt); + const idx = mem.indexOfDiff(u8, expected_fmt, given_fmt).?; + var padding = try testing.allocator.alloc(u8, idx + 5); + defer testing.allocator.free(padding); + mem.set(u8, padding, ' '); + std.debug.print("\nASM: {s}\nEXP: {s}\nGIV: {s}\n{s}^ -- first differing byte\n", .{ + assembly, + expected_fmt, + given_fmt, + padding, + }); + return error.TestFailed; +} + +test "mov dst_reg, src_reg" { + var mock = Mock.init(); + defer mock.deinit(); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .rsp }).encode(), + .data = undefined, + }, "\x48\x89\xe5", "mov rbp, rsp"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .rax }).encode(), + .data = undefined, + }, "\x49\x89\xc4", "mov r12, rax"); + try mock.testEmitSingleError(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .eax }).encode(), + .data = undefined, + }, error.OperandSizeMismatch); + try mock.testEmitSingleError(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .rax }).encode(), + .data = undefined, + }, error.OperandSizeMismatch); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .eax }).encode(), + .data = undefined, + }, "\x41\x89\xc4", "mov r12d, eax"); + + // TODO mov r12b, ah requires a codepath without REX prefix + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r12b, .reg2 = .al }).encode(), + .data = undefined, + }, "\x41\x88\xc4", "mov r12b, al"); +} + +test "mov dst_reg, imm" { + var mock = Mock.init(); + defer mock.deinit(); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rcx }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x48\xc7\xc1\x10\x00\x00\x00", "mov rcx, 0x10"); + + // TODO we are wasting one byte here: this could be encoded as OI with the encoding opc + rd, imm8/16/32 + // b9 10 00 00 00 + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .ecx }).encode(), + .data = .{ .imm = 0x10 }, + }, "\xc7\xc1\x10\x00\x00\x00", "mov ecx, 0x10"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .cx }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x66\xc7\xc1\x10\x00", "mov cx, 0x10"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11w }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x66\x41\xC7\xC3\x10\x00", "mov r11w, 0x10"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .cl }).encode(), + .data = .{ .imm = 0x10 }, + }, "\xc6\xc1\x10", "mov cl, 0x10"); + try mock.testEmitSingleError(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .cx }).encode(), + .data = .{ .imm = 0x10000000 }, + }, error.OperandSizeMismatch); + try mock.testEmitSingleError(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .cl }).encode(), + .data = .{ .imm = 0x1000 }, + }, error.OperandSizeMismatch); +} + +test "mov dst_reg, [imm32]" { + var mock = Mock.init(); + defer mock.deinit(); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rcx, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x48\x8B\x0C\x25\x10\x00\x00\x00", "mov rcx, [0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x4C\x8B\x1C\x25\x10\x00\x00\x00", "mov r11, [0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x44\x8B\x1C\x25\x10\x00\x00\x00", "mov r11d, [0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11w, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x66\x44\x8B\x1C\x25\x10\x00\x00\x00", "mov r11w, [0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11b, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x44\x8A\x1C\x25\x10\x00\x00\x00", "mov r11b, [0x10]"); +} + +test "mov dst_reg, [src_reg + imm]" { + var mock = Mock.init(); + defer mock.deinit(); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x48\x8B\x4D\x10", "mov rcx, [rbp + 0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10000000 }, + }, "\x48\x8B\x8D\x00\x00\x00\x10", "mov rcx, [rbp + 0x10000000]"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11b, .reg2 = .rbp, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x44\x8A\x5D\x10", "mov r11b, [rbp + 0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .rbp, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10000000 }, + }, "\x66\x44\x8B\x9D\x00\x00\x00\x10", "mov r11w, [rbp + 0x10000000]"); +} + +test "mov [dst_reg + 0], imm" { + var mock = Mock.init(); + defer mock.deinit(); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x41\xC7\x03\x10\x00\x00\x00", "mov dword ptr [r11 + 0], 0x10"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10000000 }, + }, "\xC7\x00\x00\x00\x00\x10", "mov dword ptr [rax + 0], 0x10000000"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x1000 }, + }, "\xC7\x00\x00\x10\x00\x00", "mov dword ptr [rax + 0], 0x1000"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\xC7\x00\x10\x00\x00\x00", "mov dword ptr [rax + 0], 0x10"); + try mock.testEmitSingleError(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .eax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, error.OperandSizeMismatch); +} + +test "mov [dst_reg + imm32], src_reg" { + var mock = Mock.init(); + defer mock.deinit(); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x4c\x89\x5d\x10", "mov qword ptr [rbp + 0x10], r11"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11d, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x44\x89\x5d\x10", "mov dword ptr [rbp + 0x10], r11d"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11w, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x66\x44\x89\x5d\x10", "mov word ptr [rbp + 0x10], r11w"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11b, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x44\x88\x5d\x10", "mov byte ptr [rbp + 0x10], r11b"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .rax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x49\x89\x43\x10", "mov qword ptr [r11 + 0x10], rax"); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .eax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x41\x89\x43\x10", "mov dword ptr [r11 + 0x10], eax"); + try mock.testEmitSingleError(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .ax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, error.OperandSizeMismatch); +} + +test "mov [dst_reg + imm32], imm32" { + var mock = Mock.init(); + defer mock.deinit(); + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10, + .operand = 0x20000000, + }); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, "\xC7\x45\x10\x00\x00\x00\x20", "mov dword ptr [rbp + 0x10], 0x20000000"); + } + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10, + .operand = 0x2000, + }); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, "\xC7\x45\x10\x00\x20\x00\x00", "mov dword ptr [rbp + 0x10], 0x2000"); + } + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10, + .operand = 0x20, + }); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, "\xc7\x45\x10\x20\x00\x00\x00", "mov dword ptr [rbp + 0x10], 0x20"); + } + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10, + .operand = 0x20000000, + }); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, "\x41\xC7\x43\x10\x00\x00\x00\x20", "mov dword ptr [r11 + 0x10], 0x20000000"); + } + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10000000, + .operand = 0x20000000, + }); + try mock.testEmitSingleSuccess(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, "\x41\xC7\x83\x00\x00\x00\x10\x00\x00\x00\x20", "mov dword ptr [r11 + 0x10], 0x20000000"); + } + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10, + .operand = 0x20, + }); + try mock.testEmitSingleError(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, error.OperandSizeMismatch); + } +} diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 501a5428d2..fe94310639 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -136,10 +136,24 @@ pub const Inst = struct { cmp_scale_src, cmp_scale_dst, cmp_scale_imm, + + /// ops flags: form: + /// 0b00 reg1, reg2 (MR) + /// 0b00 reg1, imm32 + /// 0b01 reg1, [reg2 + imm32] + /// 0b01 reg1, [ds:imm32] + /// 0b10 [reg1 + imm32], reg2 + /// 0b10 [reg1 + 0], imm32 + /// 0b11 [reg1 + imm32], imm32 + /// 0b11 reg1, reg2 (RM) + /// Notes: + /// * If reg2 is `none` then it means Data field `imm` is used as the immediate. + /// * When two imm32 values are required, Data field `payload` points at `ImmPair`. mov, mov_scale_src, mov_scale_dst, mov_scale_imm, + lea, lea_scale_src, lea_scale_dst, @@ -292,6 +306,7 @@ pub const Inst = struct { /// Another instruction. inst: Index, /// A 32-bit immediate value. + /// TODO we should add support for 16- and 8-bit immediate values. imm: i32, /// An extern function. /// Index into the linker's string table. @@ -378,14 +393,14 @@ pub const Ops = struct { } }; -pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } { +pub fn extraData(mir_extra: []const u32, comptime T: type, index: usize) struct { data: T, end: usize } { const fields = std.meta.fields(T); var i: usize = index; var result: T = undefined; inline for (fields) |field| { @field(result, field.name) = switch (field.field_type) { - u32 => mir.extra[i], - i32 => @bitCast(i32, mir.extra[i]), + u32 => mir_extra[i], + i32 => @bitCast(i32, mir_extra[i]), else => @compileError("bad field type"), }; i += 1; diff --git a/src/arch/x86_64/PrintMir.zig b/src/arch/x86_64/PrintMir.zig index b69e871fc3..0c9b952c74 100644 --- a/src/arch/x86_64/PrintMir.zig +++ b/src/arch/x86_64/PrintMir.zig @@ -481,7 +481,7 @@ fn mirArith(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index, w: any 0b11 => { if (ops.reg2 == .none) { const payload = print.mir.instructions.items(.data)[inst].payload; - const imm_pair = print.mir.extraData(Mir.ImmPair, payload).data; + const imm_pair = Mir.extraData(print.mir.extra, Mir.ImmPair, payload).data; try w.print("[{s} + {d}], {d}", .{ @tagName(ops.reg1), imm_pair.dest_off, imm_pair.operand }); } try w.writeAll("TODO"); @@ -516,7 +516,7 @@ fn mirArithScaleImm(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index const ops = Mir.Ops.decode(print.mir.instructions.items(.ops)[inst]); const scale = ops.flags; const payload = print.mir.instructions.items(.data)[inst].payload; - const imm_pair = print.mir.extraData(Mir.ImmPair, payload).data; + const imm_pair = Mir.extraData(print.mir.extra, Mir.ImmPair, payload).data; try w.print("{s} [{s} + {d}*rcx + {d}], {d}\n", .{ @tagName(tag), @tagName(ops.reg1), scale, imm_pair.dest_off, imm_pair.operand }); } @@ -528,7 +528,7 @@ fn mirMovabs(print: *const Print, inst: Mir.Inst.Index, w: anytype) !void { const is_64 = ops.reg1.size() == 64; const imm: i128 = if (is_64) blk: { const payload = print.mir.instructions.items(.data)[inst].payload; - const imm64 = print.mir.extraData(Mir.Imm64, payload).data; + const imm64 = Mir.extraData(print.mir.extra, Mir.Imm64, payload).data; break :blk imm64.decode(); } else print.mir.instructions.items(.data)[inst].imm; if (ops.flags == 0b00) { From 4a40c0a80c8f7014097f3ac6f523c7e21cda80a9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 20 Dec 2021 17:50:08 +0100 Subject: [PATCH 02/19] stage2: refactor errors thrown for size mismatch in mirMovImpl * introduce `EmitResult` wrapper struct for easier manipulation of intermediate emit results - this is mainly to track errors such as size mismatch between operands * create an informative `ErrorMsg` directly at the callsite --- src/arch/x86_64/Emit.zig | 146 +++++++++++++++++++++++++++++---------- 1 file changed, 110 insertions(+), 36 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index af87e0161a..a8260fa5c2 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -48,9 +48,31 @@ const InnerError = error{ EmitFail, }; -const EmitError = error{ - OutOfMemory, - OperandSizeMismatch, +const EmitResult = union(enum) { + ok: void, + err: *ErrorMsg, + + fn ok() EmitResult { + return EmitResult{ .ok = .{} }; + } + + fn err( + allocator: Allocator, + src_loc: Module.SrcLoc, + comptime format: []const u8, + args: anytype, + ) error{OutOfMemory}!EmitResult { + return EmitResult{ + .err = try ErrorMsg.create(allocator, src_loc, format, args), + }; + } + + fn deinit(res: EmitResult, allocator: Allocator) void { + switch (res) { + .ok => {}, + .err => |err_msg| err_msg.destroy(allocator), + } + } }; const Reloc = struct { @@ -167,9 +189,15 @@ pub fn deinit(emit: *Emit) void { } fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError { + @setCold(true); + const err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args); + return emit.failWithErrorMsg(err_msg); +} + +fn failWithErrorMsg(emit: *Emit, err_msg: *ErrorMsg) InnerError { @setCold(true); assert(emit.err_msg == null); - emit.err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args); + emit.err_msg = err_msg; return error.EmitFail; } @@ -858,24 +886,28 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE } fn mirMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - return mirMovImpl( + const res = try mirMovImpl( + emit.bin_file.allocator, emit.mir.instructions, emit.mir.extra, inst, + emit.src_loc, emit.code, - ) catch |err| switch (err) { - // TODO better formating of operands in case of an error - error.OperandSizeMismatch => emit.fail("operand size mismatch", .{}), - else => emit.fail("emit failed with error: {}", .{err}), - }; + ); + switch (res) { + .ok => {}, + .err => |err_msg| return emit.failWithErrorMsg(err_msg), + } } fn mirMovImpl( + allocator: Allocator, mir_instructions: std.MultiArrayList(Mir.Inst).Slice, mir_extra: []const u32, inst: Mir.Inst.Index, + src_loc: Module.SrcLoc, code: *std.ArrayList(u8), -) EmitError!void { +) error{OutOfMemory}!EmitResult { const ops = Mir.Ops.decode(mir_instructions.items(.ops)[inst]); switch (ops.flags) { 0b00 => blk: { @@ -900,11 +932,31 @@ fn mirMovImpl( encoder.modRm_direct(modrm_ext, ops.reg1.lowId()); switch (ops.reg1.size()) { 8 => { - const imm8 = math.cast(i8, imm) catch return error.OperandSizeMismatch; + const imm8 = math.cast(i8, imm) catch { + return EmitResult.err( + allocator, + src_loc, + "size mismatch: sizeof {} != sizeof 0x{x}", + .{ + ops.reg1, + imm, + }, + ); + }; encoder.imm8(imm8); }, 16 => { - const imm16 = math.cast(i16, imm) catch return error.OperandSizeMismatch; + const imm16 = math.cast(i16, imm) catch { + return EmitResult.err( + allocator, + src_loc, + "size mismatch: sizeof {} != sizeof 0x{x}", + .{ + ops.reg1, + imm, + }, + ); + }; encoder.imm16(imm16); }, 32, 64 => { @@ -917,7 +969,10 @@ fn mirMovImpl( // mov reg1, reg2 // MR if (ops.reg1.size() != ops.reg2.size()) { - return error.OperandSizeMismatch; + return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != sizeof {}", .{ + ops.reg1, + ops.reg2, + }); } const opc: u8 = if (ops.reg1.size() == 8) 0x88 else 0x89; const encoder = try Encoder.init(code, 3); @@ -954,7 +1009,7 @@ fn mirMovImpl( // TODO handle 32-bit base register - requires prefix 0x67 // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 if (ops.reg2.size() != 64) { - return error.OperandSizeMismatch; + return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg2}); } const encoder = try Encoder.init(code, 8); if (ops.reg1.size() == 16) { @@ -978,7 +1033,7 @@ fn mirMovImpl( // TODO handle 32-bit base register - requires prefix 0x67 // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 if (ops.reg1.size() != 64) { - return error.OperandSizeMismatch; + return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1}); } if (ops.reg2 == .none) { // mov [reg1 + 0], imm32 @@ -1041,7 +1096,7 @@ fn mirMovImpl( // a byte, word or dword ptr. // TODO we currently don't have a way to flag imm32 64bit sign extended if (ops.reg1.size() != 64) { - return error.OperandSizeMismatch; + return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1}); } const payload = mir_instructions.items(.data)[inst].payload; const imm_pair = Mir.extraData(mir_extra, Mir.ImmPair, payload).data; @@ -1077,6 +1132,7 @@ fn mirMovImpl( encoder.modRm_direct(ops.reg1.lowId(), ops.reg2.lowId()); }, } + return EmitResult.ok(); } fn immOpSize(imm: i32) u8 { @@ -1488,23 +1544,36 @@ const Mock = struct { return result; } + fn dummySrcLoc() Module.SrcLoc { + return .{ + .file_scope = undefined, + .parent_decl_node = 0, + .lazy = .unneeded, + }; + } + fn testEmitSingleSuccess( self: *Mock, mir_inst: Mir.Inst, expected_enc: []const u8, assembly: []const u8, ) !void { + const dummy_src_loc = Mock.dummySrcLoc(); const code_index = self.code.items.len; const mir_index = try self.addInst(mir_inst); - switch (mir_inst.tag) { + const res = switch (mir_inst.tag) { .mov => try mirMovImpl( + testing.allocator, self.mir_instructions.slice(), self.mir_extra.items, mir_index, + dummy_src_loc, &self.code, ), else => unreachable, - } + }; + defer res.deinit(testing.allocator); + try testing.expect(res == .ok); const code_len = if (self.code.items[code_index..].len >= expected_enc.len) expected_enc.len else @@ -1512,18 +1581,23 @@ const Mock = struct { try expectEqualHexStrings(expected_enc, self.code.items[code_index..][0..code_len], assembly); } - fn testEmitSingleError(self: *Mock, mir_inst: Mir.Inst, err: EmitError) !void { + fn testEmitSingleFail(self: *Mock, mir_inst: Mir.Inst, msg: []const u8) !void { + const dummy_src_loc = Mock.dummySrcLoc(); const index = try self.addInst(mir_inst); const res = switch (mir_inst.tag) { - .mov => mirMovImpl( + .mov => try mirMovImpl( + testing.allocator, self.mir_instructions.slice(), self.mir_extra.items, index, + dummy_src_loc, &self.code, ), else => unreachable, }; - try testing.expectError(err, res); + defer res.deinit(testing.allocator); + try testing.expect(res == .err); + try testing.expectEqualStrings(msg, res.err.msg); } }; @@ -1560,16 +1634,16 @@ test "mov dst_reg, src_reg" { .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .rax }).encode(), .data = undefined, }, "\x49\x89\xc4", "mov r12, rax"); - try mock.testEmitSingleError(.{ + try mock.testEmitSingleFail(.{ .tag = .mov, .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .eax }).encode(), .data = undefined, - }, error.OperandSizeMismatch); - try mock.testEmitSingleError(.{ + }, "size mismatch: sizeof Register.r12 != sizeof Register.eax"); + try mock.testEmitSingleFail(.{ .tag = .mov, .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .rax }).encode(), .data = undefined, - }, error.OperandSizeMismatch); + }, "size mismatch: sizeof Register.r12d != sizeof Register.rax"); try mock.testEmitSingleSuccess(.{ .tag = .mov, .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .eax }).encode(), @@ -1615,16 +1689,16 @@ test "mov dst_reg, imm" { .ops = (Mir.Ops{ .reg1 = .cl }).encode(), .data = .{ .imm = 0x10 }, }, "\xc6\xc1\x10", "mov cl, 0x10"); - try mock.testEmitSingleError(.{ + try mock.testEmitSingleFail(.{ .tag = .mov, .ops = (Mir.Ops{ .reg1 = .cx }).encode(), .data = .{ .imm = 0x10000000 }, - }, error.OperandSizeMismatch); - try mock.testEmitSingleError(.{ + }, "size mismatch: sizeof Register.cx != sizeof 0x10000000"); + try mock.testEmitSingleFail(.{ .tag = .mov, .ops = (Mir.Ops{ .reg1 = .cl }).encode(), .data = .{ .imm = 0x1000 }, - }, error.OperandSizeMismatch); + }, "size mismatch: sizeof Register.cl != sizeof 0x1000"); } test "mov dst_reg, [imm32]" { @@ -1705,11 +1779,11 @@ test "mov [dst_reg + 0], imm" { .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), .data = .{ .imm = 0x10 }, }, "\xC7\x00\x10\x00\x00\x00", "mov dword ptr [rax + 0], 0x10"); - try mock.testEmitSingleError(.{ + try mock.testEmitSingleFail(.{ .tag = .mov, .ops = (Mir.Ops{ .reg1 = .eax, .flags = 0b10 }).encode(), .data = .{ .imm = 0x10 }, - }, error.OperandSizeMismatch); + }, "size mismatch: sizeof Register.eax != 8"); } test "mov [dst_reg + imm32], src_reg" { @@ -1745,11 +1819,11 @@ test "mov [dst_reg + imm32], src_reg" { .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .eax, .flags = 0b10 }).encode(), .data = .{ .imm = 0x10 }, }, "\x41\x89\x43\x10", "mov dword ptr [r11 + 0x10], eax"); - try mock.testEmitSingleError(.{ + try mock.testEmitSingleFail(.{ .tag = .mov, .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .ax, .flags = 0b10 }).encode(), .data = .{ .imm = 0x10 }, - }, error.OperandSizeMismatch); + }, "size mismatch: sizeof Register.r11w != 8"); } test "mov [dst_reg + imm32], imm32" { @@ -1815,10 +1889,10 @@ test "mov [dst_reg + imm32], imm32" { .dest_off = 0x10, .operand = 0x20, }); - try mock.testEmitSingleError(.{ + try mock.testEmitSingleFail(.{ .tag = .mov, .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b11 }).encode(), .data = .{ .payload = payload }, - }, error.OperandSizeMismatch); + }, "size mismatch: sizeof Register.r11d != 8"); } } From 71c5eebd32d44ca01fedcb48674c5e7185f10289 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 20 Dec 2021 18:20:06 +0100 Subject: [PATCH 03/19] stage2: remove obsolete MOV variant 0b11 * variant `0b11` when both `reg1 != .none` and `reg2 != .none` is identical to `0b00` therefore it can safely be removed * fix proper destination register size calculation when setting register from another source register --- src/arch/x86_64/CodeGen.zig | 3 +-- src/arch/x86_64/Emit.zig | 12 +----------- src/arch/x86_64/Mir.zig | 2 +- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 0a209b8807..66bf43fb29 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2968,9 +2968,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void _ = try self.addInst(.{ .tag = .mov, .ops = (Mir.Ops{ - .reg1 = reg, + .reg1 = registerAlias(reg, @divExact(src_reg.size(), 8)), .reg2 = src_reg, - .flags = 0b11, }).encode(), .data = undefined, }); diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index a8260fa5c2..10aff52813 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -1119,17 +1119,7 @@ fn mirMovImpl( encoder.imm32(imm_op); break :blk; } - // mov reg1, reg2 - // RM - const opc: u8 = if (ops.reg1.size() == 8) 0x8a else 0x8b; - const encoder = try Encoder.init(code, 3); - encoder.rex(.{ - .w = ops.reg1.size() == 64 and ops.reg2.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_direct(ops.reg1.lowId(), ops.reg2.lowId()); + return EmitResult.err(allocator, src_loc, "TODO unused variant: mov reg1, reg2, 0b11", .{}); }, } return EmitResult.ok(); diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index fe94310639..e2cc3cab30 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -145,7 +145,7 @@ pub const Inst = struct { /// 0b10 [reg1 + imm32], reg2 /// 0b10 [reg1 + 0], imm32 /// 0b11 [reg1 + imm32], imm32 - /// 0b11 reg1, reg2 (RM) + /// 0b11 AVAILABLE /// Notes: /// * If reg2 is `none` then it means Data field `imm` is used as the immediate. /// * When two imm32 values are required, Data field `payload` points at `ImmPair`. From 5156ccd552fa6d0959c8349cbbe176099ac4c356 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 20 Dec 2021 19:38:15 +0100 Subject: [PATCH 04/19] stage2: merge MOV back with arith instructions * turns out MOV and other arithmetic instructions such as ADD can naturally share the same lowering codepath (for the same variants) * there are variants that are specific to ADD, or MOV which will be implemented as standalone MIR tags * tweak Isel tests to generate corresponding test cases for all arithmetic instructions in comptime --- src/arch/x86_64/CodeGen.zig | 15 +- src/arch/x86_64/Emit.zig | 1179 ++++++++++++++++------------------- src/arch/x86_64/Mir.zig | 13 - 3 files changed, 549 insertions(+), 658 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 66bf43fb29..2660a02ca3 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -1608,18 +1608,18 @@ fn genBinMathOpMir( _ = try self.addInst(.{ .tag = mir_tag, .ops = (Mir.Ops{ - .reg1 = src_reg, - .reg2 = dst_reg, - .flags = 0b11, + .reg1 = registerAlias(dst_reg, @divExact(src_reg.size(), 8)), + .reg2 = src_reg, }).encode(), .data = undefined, }); }, .immediate => |imm| { + // TODO I am not quite sure why we need to set the size of the register here... _ = try self.addInst(.{ .tag = mir_tag, .ops = (Mir.Ops{ - .reg1 = dst_reg, + .reg1 = registerAlias(dst_reg, 4), }).encode(), .data = .{ .imm = @intCast(i32, imm) }, }); @@ -1637,7 +1637,7 @@ fn genBinMathOpMir( .tag = mir_tag, .ops = (Mir.Ops{ .reg1 = registerAlias(dst_reg, @intCast(u32, abi_size)), - .reg2 = registerAlias(.rbp, @intCast(u32, abi_size)), + .reg2 = .rbp, .flags = 0b01, }).encode(), .data = .{ .imm = -@intCast(i32, adj_off) }, @@ -1667,8 +1667,8 @@ fn genBinMathOpMir( _ = try self.addInst(.{ .tag = mir_tag, .ops = (Mir.Ops{ - .reg1 = registerAlias(src_reg, @intCast(u32, abi_size)), - .reg2 = registerAlias(.rbp, @intCast(u32, abi_size)), + .reg1 = .rbp, + .reg2 = registerAlias(src_reg, @intCast(u32, abi_size)), .flags = 0b10, }).encode(), .data = .{ .imm = -@intCast(i32, adj_off) }, @@ -2924,6 +2924,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void } if (x <= math.maxInt(i32)) { // Next best case: if we set the lower four bytes, the upper four will be zeroed. + // TODO I am not quite sure why we need to set the size of the register here... _ = try self.addInst(.{ .tag = .mov, .ops = (Mir.Ops{ diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 10aff52813..ee8856a741 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -93,46 +93,41 @@ pub fn emitMir(emit: *Emit) InnerError!void { const inst = @intCast(u32, index); try emit.code_offset_mapping.putNoClobber(emit.bin_file.allocator, inst, emit.code.items.len); switch (tag) { - .adc => try emit.mirArith(.adc, inst), - .add => try emit.mirArith(.add, inst), - .sub => try emit.mirArith(.sub, inst), - .xor => try emit.mirArith(.xor, inst), - .@"and" => try emit.mirArith(.@"and", inst), - .@"or" => try emit.mirArith(.@"or", inst), - .sbb => try emit.mirArith(.sbb, inst), - .cmp => try emit.mirArith(.cmp, inst), + .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov => try emit.mirArith(tag, inst), - .adc_scale_src => try emit.mirArithScaleSrc(.adc, inst), - .add_scale_src => try emit.mirArithScaleSrc(.add, inst), - .sub_scale_src => try emit.mirArithScaleSrc(.sub, inst), - .xor_scale_src => try emit.mirArithScaleSrc(.xor, inst), - .and_scale_src => try emit.mirArithScaleSrc(.@"and", inst), - .or_scale_src => try emit.mirArithScaleSrc(.@"or", inst), - .sbb_scale_src => try emit.mirArithScaleSrc(.sbb, inst), - .cmp_scale_src => try emit.mirArithScaleSrc(.cmp, inst), + .adc_scale_src, + .add_scale_src, + .sub_scale_src, + .xor_scale_src, + .and_scale_src, + .or_scale_src, + .sbb_scale_src, + .cmp_scale_src, + .mov_scale_src, + => try emit.mirArithScaleSrc(tag, inst), - .adc_scale_dst => try emit.mirArithScaleDst(.adc, inst), - .add_scale_dst => try emit.mirArithScaleDst(.add, inst), - .sub_scale_dst => try emit.mirArithScaleDst(.sub, inst), - .xor_scale_dst => try emit.mirArithScaleDst(.xor, inst), - .and_scale_dst => try emit.mirArithScaleDst(.@"and", inst), - .or_scale_dst => try emit.mirArithScaleDst(.@"or", inst), - .sbb_scale_dst => try emit.mirArithScaleDst(.sbb, inst), - .cmp_scale_dst => try emit.mirArithScaleDst(.cmp, inst), + .adc_scale_dst, + .add_scale_dst, + .sub_scale_dst, + .xor_scale_dst, + .and_scale_dst, + .or_scale_dst, + .sbb_scale_dst, + .cmp_scale_dst, + .mov_scale_dst, + => try emit.mirArithScaleDst(tag, inst), - .adc_scale_imm => try emit.mirArithScaleImm(.adc, inst), - .add_scale_imm => try emit.mirArithScaleImm(.add, inst), - .sub_scale_imm => try emit.mirArithScaleImm(.sub, inst), - .xor_scale_imm => try emit.mirArithScaleImm(.xor, inst), - .and_scale_imm => try emit.mirArithScaleImm(.@"and", inst), - .or_scale_imm => try emit.mirArithScaleImm(.@"or", inst), - .sbb_scale_imm => try emit.mirArithScaleImm(.sbb, inst), - .cmp_scale_imm => try emit.mirArithScaleImm(.cmp, inst), + .adc_scale_imm, + .add_scale_imm, + .sub_scale_imm, + .xor_scale_imm, + .and_scale_imm, + .or_scale_imm, + .sbb_scale_imm, + .cmp_scale_imm, + .mov_scale_imm, + => try emit.mirArithScaleImm(tag, inst), - .mov => try emit.mirMov(inst), - .mov_scale_src => try emit.mirArithScaleSrc(.mov, inst), - .mov_scale_dst => try emit.mirArithScaleDst(.mov, inst), - .mov_scale_imm => try emit.mirArithScaleImm(.mov, inst), .movabs => try emit.mirMovabs(inst), .lea => try emit.mirLea(inst), @@ -140,19 +135,19 @@ pub fn emitMir(emit: *Emit) InnerError!void { .imul_complex => try emit.mirIMulComplex(inst), - .push => try emit.mirPushPop(.push, inst), - .pop => try emit.mirPushPop(.pop, inst), + .push, .pop => try emit.mirPushPop(tag, inst), - .jmp => try emit.mirJmpCall(.jmp, inst), - .call => try emit.mirJmpCall(.call, inst), + .jmp, .call => try emit.mirJmpCall(tag, inst), - .cond_jmp_greater_less => try emit.mirCondJmp(.cond_jmp_greater_less, inst), - .cond_jmp_above_below => try emit.mirCondJmp(.cond_jmp_above_below, inst), - .cond_jmp_eq_ne => try emit.mirCondJmp(.cond_jmp_eq_ne, inst), + .cond_jmp_greater_less, + .cond_jmp_above_below, + .cond_jmp_eq_ne, + => try emit.mirCondJmp(tag, inst), - .cond_set_byte_greater_less => try emit.mirCondSetByte(.cond_set_byte_greater_less, inst), - .cond_set_byte_above_below => try emit.mirCondSetByte(.cond_set_byte_above_below, inst), - .cond_set_byte_eq_ne => try emit.mirCondSetByte(.cond_set_byte_eq_ne, inst), + .cond_set_byte_greater_less, + .cond_set_byte_above_below, + .cond_set_byte_eq_ne, + => try emit.mirCondSetByte(tag, inst), .ret => try emit.mirRet(inst), @@ -598,6 +593,7 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { .@"or" => .{ .opc = 0x81, .modrm_ext = 0x1 }, .sbb => .{ .opc = 0x81, .modrm_ext = 0x3 }, .cmp => .{ .opc = 0x81, .modrm_ext = 0x7 }, + .mov => .{ .opc = 0xc7, .modrm_ext = 0x0 }, else => unreachable, }, .mr => { @@ -610,6 +606,7 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { .@"or" => 0x09, .sbb => 0x19, .cmp => 0x39, + .mov => 0x89, else => unreachable, }; return .{ .opc = opc, .modrm_ext = undefined }; @@ -624,6 +621,7 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { .@"or" => 0x0b, .sbb => 0x1b, .cmp => 0x3b, + .mov => 0x8b, else => unreachable, }; return .{ .opc = opc, .modrm_ext = undefined }; @@ -632,54 +630,121 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { } fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { - const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); + const res = try mirArithImpl( + emit.bin_file.allocator, + tag, + emit.mir.instructions, + emit.mir.extra, + inst, + emit.src_loc, + emit.code, + ); + switch (res) { + .ok => {}, + .err => |err_msg| return emit.failWithErrorMsg(err_msg), + } +} + +fn mirArithImpl( + allocator: Allocator, + tag: Mir.Inst.Tag, + mir_instructions: std.MultiArrayList(Mir.Inst).Slice, + mir_extra: []const u32, + inst: Mir.Inst.Index, + src_loc: Module.SrcLoc, + code: *std.ArrayList(u8), +) error{OutOfMemory}!EmitResult { + const ops = Mir.Ops.decode(mir_instructions.items(.ops)[inst]); switch (ops.flags) { 0b00 => blk: { if (ops.reg2 == .none) { - // OP reg1, imm32 - // OP r/m64, imm32 - const imm = emit.mir.instructions.items(.data)[inst].imm; + // mov reg1, imm32 + // MI + const imm = mir_instructions.items(.data)[inst].imm; const opcode = getArithOpCode(tag, .mi); - const encoder = try Encoder.init(emit.code, 7); + const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; + const encoder = try Encoder.init(code, 7); + if (ops.reg1.size() == 16) { + // 0x66 prefix switches to the non-default size; here we assume a switch from + // the default 32bits to 16bits operand-size. + // 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 + encoder.opcode_1byte(0x66); + } encoder.rex(.{ .w = ops.reg1.size() == 64, .b = ops.reg1.isExtended(), }); - if (tag != .mov and imm <= math.maxInt(i8)) { - encoder.opcode_1byte(opcode.opc + 2); - encoder.modRm_direct(opcode.modrm_ext, ops.reg1.lowId()); - encoder.imm8(@intCast(i8, imm)); - } else { - encoder.opcode_1byte(opcode.opc); - encoder.modRm_direct(opcode.modrm_ext, ops.reg1.lowId()); - encoder.imm32(imm); + encoder.opcode_1byte(opc); + encoder.modRm_direct(opcode.modrm_ext, ops.reg1.lowId()); + switch (ops.reg1.size()) { + 8 => { + const imm8 = math.cast(i8, imm) catch { + return EmitResult.err( + allocator, + src_loc, + "size mismatch: sizeof {} != sizeof 0x{x}", + .{ + ops.reg1, + imm, + }, + ); + }; + encoder.imm8(imm8); + }, + 16 => { + const imm16 = math.cast(i16, imm) catch { + return EmitResult.err( + allocator, + src_loc, + "size mismatch: sizeof {} != sizeof 0x{x}", + .{ + ops.reg1, + imm, + }, + ); + }; + encoder.imm16(imm16); + }, + 32, 64 => { + encoder.imm32(imm); + }, + else => unreachable, } break :blk; } - // OP reg1, reg2 - // OP r/m64, r64 + // mov reg1, reg2 + // MR + if (ops.reg1.size() != ops.reg2.size()) { + return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != sizeof {}", .{ + ops.reg1, + ops.reg2, + }); + } const opcode = getArithOpCode(tag, .mr); - const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; - const encoder = try Encoder.init(emit.code, 3); + const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; + const encoder = try Encoder.init(code, 3); encoder.rex(.{ .w = ops.reg1.size() == 64 and ops.reg2.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), + .r = ops.reg2.isExtended(), + .b = ops.reg1.isExtended(), }); encoder.opcode_1byte(opc); - encoder.modRm_direct(ops.reg1.lowId(), ops.reg2.lowId()); + encoder.modRm_direct(ops.reg2.lowId(), ops.reg1.lowId()); }, 0b01 => blk: { - const imm = emit.mir.instructions.items(.data)[inst].imm; + const imm = mir_instructions.items(.data)[inst].imm; const opcode = getArithOpCode(tag, .rm); - const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; + const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; if (ops.reg2 == .none) { - // OP reg1, [imm32] - // OP r64, r/m64 - const encoder = try Encoder.init(emit.code, 8); + // mov reg1, [imm32] + // RM + const encoder = try Encoder.init(code, 9); + if (ops.reg1.size() == 16) { + encoder.opcode_1byte(0x66); + } encoder.rex(.{ .w = ops.reg1.size() == 64, - .b = ops.reg1.isExtended(), + .r = ops.reg1.isExtended(), }); encoder.opcode_1byte(opc); encoder.modRm_SIBDisp0(ops.reg1.lowId()); @@ -687,16 +752,24 @@ fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!voi encoder.disp32(imm); break :blk; } - // OP reg1, [reg2 + imm32] - // OP r64, r/m64 - const encoder = try Encoder.init(emit.code, 7); + // mov reg1, [reg2 + imm32] + // RM + // TODO handle 32-bit base register - requires prefix 0x67 + // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 + if (ops.reg2.size() != 64) { + return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg2}); + } + const encoder = try Encoder.init(code, 8); + if (ops.reg1.size() == 16) { + encoder.opcode_1byte(0x66); + } encoder.rex(.{ .w = ops.reg1.size() == 64, .r = ops.reg1.isExtended(), .b = ops.reg2.isExtended(), }); encoder.opcode_1byte(opc); - if (imm <= math.maxInt(i8)) { + if (immOpSize(imm) == 8) { encoder.modRm_indirectDisp8(ops.reg1.lowId(), ops.reg2.lowId()); encoder.disp8(@intCast(i8, imm)); } else { @@ -705,86 +778,110 @@ fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!voi } }, 0b10 => blk: { + // TODO handle 32-bit base register - requires prefix 0x67 + // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 + if (ops.reg1.size() != 64) { + return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1}); + } if (ops.reg2 == .none) { - // OP [reg1 + 0], imm32 - // OP r/m64, imm32 - const imm = emit.mir.instructions.items(.data)[inst].imm; + // mov [reg1 + 0], imm32 + // MI + // Base register reg1 can either be 64bit or 32bit in size. + // TODO for memory operand, immediate operand pair, we currently + // have no way of flagging whether the immediate can be 8-, 16- or + // 32-bit and whether the corresponding memory operand is respectively + // a byte, word or dword ptr. + // TODO we currently don't have a way to flag imm32 64bit sign extended + const imm = mir_instructions.items(.data)[inst].imm; const opcode = getArithOpCode(tag, .mi); - const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; - const encoder = try Encoder.init(emit.code, 7); + const encoder = try Encoder.init(code, 7); encoder.rex(.{ - .w = ops.reg1.size() == 64, + .w = false, .b = ops.reg1.isExtended(), }); - encoder.opcode_1byte(opc); + encoder.opcode_1byte(opcode.opc); encoder.modRm_indirectDisp0(opcode.modrm_ext, ops.reg1.lowId()); - if (imm <= math.maxInt(i8)) { - encoder.imm8(@intCast(i8, imm)); - } else if (imm <= math.maxInt(i16)) { - encoder.imm16(@intCast(i16, imm)); - } else { - encoder.imm32(imm); - } + encoder.imm32(imm); break :blk; } - // OP [reg1 + imm32], reg2 - // OP r/m64, r64 - const imm = emit.mir.instructions.items(.data)[inst].imm; + // mov [reg1 + imm32], reg2 + // MR + // We use size of source register reg2 to work out which + // variant of memory ptr to pick: + // * reg2 is 64bit - qword ptr + // * reg2 is 32bit - dword ptr + // * reg2 is 16bit - word ptr + // * reg2 is 8bit - byte ptr + const imm = mir_instructions.items(.data)[inst].imm; const opcode = getArithOpCode(tag, .mr); - const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; - const encoder = try Encoder.init(emit.code, 7); + const opc: u8 = if (ops.reg2.size() == 8) opcode.opc - 1 else opcode.opc; + const encoder = try Encoder.init(code, 5); + if (ops.reg2.size() == 16) { + encoder.opcode_1byte(0x66); + } encoder.rex(.{ .w = ops.reg2.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), + .r = ops.reg2.isExtended(), + .b = ops.reg1.isExtended(), }); encoder.opcode_1byte(opc); - if (imm <= math.maxInt(i8)) { - encoder.modRm_indirectDisp8(ops.reg1.lowId(), ops.reg2.lowId()); + if (immOpSize(imm) == 8) { + encoder.modRm_indirectDisp8(ops.reg2.lowId(), ops.reg1.lowId()); encoder.disp8(@intCast(i8, imm)); } else { - encoder.modRm_indirectDisp32(ops.reg1.lowId(), ops.reg2.lowId()); + encoder.modRm_indirectDisp32(ops.reg2.lowId(), ops.reg1.lowId()); encoder.disp32(imm); } }, 0b11 => blk: { if (ops.reg2 == .none) { - // OP [reg1 + imm32], imm32 - // OP r/m64, imm32 - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm_pair = Mir.extraData(emit.mir.extra, Mir.ImmPair, payload).data; + // mov [reg1 + imm32], imm32 + // MI + // Base register reg1 can either be 64bit or 32bit in size. + // TODO for memory operand, immediate operand pair, we currently + // have no way of flagging whether the immediate can be 8-, 16- or + // 32-bit and whether the corresponding memory operand is respectively + // a byte, word or dword ptr. + // TODO we currently don't have a way to flag imm32 64bit sign extended + if (ops.reg1.size() != 64) { + return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1}); + } + const payload = mir_instructions.items(.data)[inst].payload; + const imm_pair = Mir.extraData(mir_extra, Mir.ImmPair, payload).data; + const imm_op = imm_pair.operand; const opcode = getArithOpCode(tag, .mi); - const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; - const encoder = try Encoder.init(emit.code, 11); + const encoder = try Encoder.init(code, 10); encoder.rex(.{ .w = false, .b = ops.reg1.isExtended(), }); - encoder.opcode_1byte(opc); - if (imm_pair.dest_off <= math.maxInt(i8)) { + encoder.opcode_1byte(opcode.opc); + if (immOpSize(imm_pair.dest_off) == 8) { encoder.modRm_indirectDisp8(opcode.modrm_ext, ops.reg1.lowId()); encoder.disp8(@intCast(i8, imm_pair.dest_off)); } else { encoder.modRm_indirectDisp32(opcode.modrm_ext, ops.reg1.lowId()); encoder.disp32(imm_pair.dest_off); } - encoder.imm32(imm_pair.operand); + encoder.imm32(imm_op); break :blk; } - // TODO clearly mov doesn't belong here; for other, arithemtic ops, - // this is the same as 0b00. - const opcode = getArithOpCode(tag, if (tag == .mov) .rm else .mr); - const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; - const encoder = try Encoder.init(emit.code, 3); - encoder.rex(.{ - .w = ops.reg1.size() == 64 and ops.reg2.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_direct(ops.reg1.lowId(), ops.reg2.lowId()); + return EmitResult.err(allocator, src_loc, "TODO unused variant: mov reg1, reg2, 0b11", .{}); }, } + return EmitResult.ok(); +} + +fn immOpSize(imm: i32) u8 { + blk: { + _ = math.cast(i8, imm) catch break :blk; + return 8; + } + blk: { + _ = math.cast(i16, imm) catch break :blk; + return 16; + } + return 32; } fn mirArithScaleSrc(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { @@ -885,258 +982,6 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE encoder.imm32(imm_pair.operand); } -fn mirMov(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const res = try mirMovImpl( - emit.bin_file.allocator, - emit.mir.instructions, - emit.mir.extra, - inst, - emit.src_loc, - emit.code, - ); - switch (res) { - .ok => {}, - .err => |err_msg| return emit.failWithErrorMsg(err_msg), - } -} - -fn mirMovImpl( - allocator: Allocator, - mir_instructions: std.MultiArrayList(Mir.Inst).Slice, - mir_extra: []const u32, - inst: Mir.Inst.Index, - src_loc: Module.SrcLoc, - code: *std.ArrayList(u8), -) error{OutOfMemory}!EmitResult { - const ops = Mir.Ops.decode(mir_instructions.items(.ops)[inst]); - switch (ops.flags) { - 0b00 => blk: { - if (ops.reg2 == .none) { - // mov reg1, imm32 - // MI - const imm = mir_instructions.items(.data)[inst].imm; - const opc: u8 = if (ops.reg1.size() == 8) 0xc6 else 0xc7; - const modrm_ext: u3 = 0x0; - const encoder = try Encoder.init(code, 7); - if (ops.reg1.size() == 16) { - // 0x66 prefix switches to the non-default size; here we assume a switch from - // the default 32bits to 16bits operand-size. - // 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 - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_direct(modrm_ext, ops.reg1.lowId()); - switch (ops.reg1.size()) { - 8 => { - const imm8 = math.cast(i8, imm) catch { - return EmitResult.err( - allocator, - src_loc, - "size mismatch: sizeof {} != sizeof 0x{x}", - .{ - ops.reg1, - imm, - }, - ); - }; - encoder.imm8(imm8); - }, - 16 => { - const imm16 = math.cast(i16, imm) catch { - return EmitResult.err( - allocator, - src_loc, - "size mismatch: sizeof {} != sizeof 0x{x}", - .{ - ops.reg1, - imm, - }, - ); - }; - encoder.imm16(imm16); - }, - 32, 64 => { - encoder.imm32(imm); - }, - else => unreachable, - } - break :blk; - } - // mov reg1, reg2 - // MR - if (ops.reg1.size() != ops.reg2.size()) { - return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != sizeof {}", .{ - ops.reg1, - ops.reg2, - }); - } - const opc: u8 = if (ops.reg1.size() == 8) 0x88 else 0x89; - const encoder = try Encoder.init(code, 3); - encoder.rex(.{ - .w = ops.reg1.size() == 64 and ops.reg2.size() == 64, - .r = ops.reg2.isExtended(), - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_direct(ops.reg2.lowId(), ops.reg1.lowId()); - }, - 0b01 => blk: { - const imm = mir_instructions.items(.data)[inst].imm; - const opc: u8 = if (ops.reg1.size() == 8) 0x8a else 0x8b; - if (ops.reg2 == .none) { - // mov reg1, [imm32] - // RM - const encoder = try Encoder.init(code, 9); - if (ops.reg1.size() == 16) { - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_SIBDisp0(ops.reg1.lowId()); - encoder.sib_disp32(); - encoder.disp32(imm); - break :blk; - } - // mov reg1, [reg2 + imm32] - // RM - // TODO handle 32-bit base register - requires prefix 0x67 - // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 - if (ops.reg2.size() != 64) { - return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg2}); - } - const encoder = try Encoder.init(code, 8); - if (ops.reg1.size() == 16) { - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), - }); - encoder.opcode_1byte(opc); - if (immOpSize(imm) == 8) { - encoder.modRm_indirectDisp8(ops.reg1.lowId(), ops.reg2.lowId()); - encoder.disp8(@intCast(i8, imm)); - } else { - encoder.modRm_indirectDisp32(ops.reg1.lowId(), ops.reg2.lowId()); - encoder.disp32(imm); - } - }, - 0b10 => blk: { - // TODO handle 32-bit base register - requires prefix 0x67 - // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 - if (ops.reg1.size() != 64) { - return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1}); - } - if (ops.reg2 == .none) { - // mov [reg1 + 0], imm32 - // MI - // Base register reg1 can either be 64bit or 32bit in size. - // TODO for memory operand, immediate operand pair, we currently - // have no way of flagging whether the immediate can be 8-, 16- or - // 32-bit and whether the corresponding memory operand is respectively - // a byte, word or dword ptr. - // TODO we currently don't have a way to flag imm32 64bit sign extended - const imm = mir_instructions.items(.data)[inst].imm; - const opc: u8 = 0xc7; - const modrm_ext: u3 = 0x0; - const encoder = try Encoder.init(code, 7); - encoder.rex(.{ - .w = false, - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_indirectDisp0(modrm_ext, ops.reg1.lowId()); - encoder.imm32(imm); - break :blk; - } - // mov [reg1 + imm32], reg2 - // MR - // We use size of source register reg2 to work out which - // variant of memory ptr to pick: - // * reg2 is 64bit - qword ptr - // * reg2 is 32bit - dword ptr - // * reg2 is 16bit - word ptr - // * reg2 is 8bit - byte ptr - const imm = mir_instructions.items(.data)[inst].imm; - const opc: u8 = if (ops.reg2.size() == 8) 0x88 else 0x89; - const encoder = try Encoder.init(code, 5); - if (ops.reg2.size() == 16) { - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = ops.reg2.size() == 64, - .r = ops.reg2.isExtended(), - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - if (immOpSize(imm) == 8) { - encoder.modRm_indirectDisp8(ops.reg2.lowId(), ops.reg1.lowId()); - encoder.disp8(@intCast(i8, imm)); - } else { - encoder.modRm_indirectDisp32(ops.reg2.lowId(), ops.reg1.lowId()); - encoder.disp32(imm); - } - }, - 0b11 => blk: { - if (ops.reg2 == .none) { - // mov [reg1 + imm32], imm32 - // MI - // Base register reg1 can either be 64bit or 32bit in size. - // TODO for memory operand, immediate operand pair, we currently - // have no way of flagging whether the immediate can be 8-, 16- or - // 32-bit and whether the corresponding memory operand is respectively - // a byte, word or dword ptr. - // TODO we currently don't have a way to flag imm32 64bit sign extended - if (ops.reg1.size() != 64) { - return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1}); - } - const payload = mir_instructions.items(.data)[inst].payload; - const imm_pair = Mir.extraData(mir_extra, Mir.ImmPair, payload).data; - const imm_op = imm_pair.operand; - const opc: u8 = 0xc7; - const modrm_ext: u3 = 0x0; - const encoder = try Encoder.init(code, 10); - encoder.rex(.{ - .w = false, - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - if (immOpSize(imm_pair.dest_off) == 8) { - encoder.modRm_indirectDisp8(modrm_ext, ops.reg1.lowId()); - encoder.disp8(@intCast(i8, imm_pair.dest_off)); - } else { - encoder.modRm_indirectDisp32(modrm_ext, ops.reg1.lowId()); - encoder.disp32(imm_pair.dest_off); - } - encoder.imm32(imm_op); - break :blk; - } - return EmitResult.err(allocator, src_loc, "TODO unused variant: mov reg1, reg2, 0b11", .{}); - }, - } - return EmitResult.ok(); -} - -fn immOpSize(imm: i32) u8 { - blk: { - _ = math.cast(i8, imm) catch break :blk; - return 8; - } - blk: { - _ = math.cast(i16, imm) catch break :blk; - return 16; - } - return 32; -} - fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .movabs); @@ -1552,8 +1397,9 @@ const Mock = struct { const code_index = self.code.items.len; const mir_index = try self.addInst(mir_inst); const res = switch (mir_inst.tag) { - .mov => try mirMovImpl( + .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov => try mirArithImpl( testing.allocator, + mir_inst.tag, self.mir_instructions.slice(), self.mir_extra.items, mir_index, @@ -1575,8 +1421,9 @@ const Mock = struct { const dummy_src_loc = Mock.dummySrcLoc(); const index = try self.addInst(mir_inst); const res = switch (mir_inst.tag) { - .mov => try mirMovImpl( + .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov => try mirArithImpl( testing.allocator, + mir_inst.tag, self.mir_instructions.slice(), self.mir_extra.items, index, @@ -1611,278 +1458,334 @@ fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []co return error.TestFailed; } -test "mov dst_reg, src_reg" { +test "ARITH_OP/MOV dst_reg, src_reg" { var mock = Mock.init(); defer mock.deinit(); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .rsp }).encode(), - .data = undefined, - }, "\x48\x89\xe5", "mov rbp, rsp"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .rax }).encode(), - .data = undefined, - }, "\x49\x89\xc4", "mov r12, rax"); - try mock.testEmitSingleFail(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .eax }).encode(), - .data = undefined, - }, "size mismatch: sizeof Register.r12 != sizeof Register.eax"); - try mock.testEmitSingleFail(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .rax }).encode(), - .data = undefined, - }, "size mismatch: sizeof Register.r12d != sizeof Register.rax"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .eax }).encode(), - .data = undefined, - }, "\x41\x89\xc4", "mov r12d, eax"); - - // TODO mov r12b, ah requires a codepath without REX prefix - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r12b, .reg2 = .al }).encode(), - .data = undefined, - }, "\x41\x88\xc4", "mov r12b, al"); -} - -test "mov dst_reg, imm" { - var mock = Mock.init(); - defer mock.deinit(); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rcx }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x48\xc7\xc1\x10\x00\x00\x00", "mov rcx, 0x10"); - - // TODO we are wasting one byte here: this could be encoded as OI with the encoding opc + rd, imm8/16/32 - // b9 10 00 00 00 - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .ecx }).encode(), - .data = .{ .imm = 0x10 }, - }, "\xc7\xc1\x10\x00\x00\x00", "mov ecx, 0x10"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .cx }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x66\xc7\xc1\x10\x00", "mov cx, 0x10"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11w }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x66\x41\xC7\xC3\x10\x00", "mov r11w, 0x10"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .cl }).encode(), - .data = .{ .imm = 0x10 }, - }, "\xc6\xc1\x10", "mov cl, 0x10"); - try mock.testEmitSingleFail(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .cx }).encode(), - .data = .{ .imm = 0x10000000 }, - }, "size mismatch: sizeof Register.cx != sizeof 0x10000000"); - try mock.testEmitSingleFail(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .cl }).encode(), - .data = .{ .imm = 0x1000 }, - }, "size mismatch: sizeof Register.cl != sizeof 0x1000"); -} - -test "mov dst_reg, [imm32]" { - var mock = Mock.init(); - defer mock.deinit(); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rcx, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x48\x8B\x0C\x25\x10\x00\x00\x00", "mov rcx, [0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x4C\x8B\x1C\x25\x10\x00\x00\x00", "mov r11, [0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x44\x8B\x1C\x25\x10\x00\x00\x00", "mov r11d, [0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11w, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x66\x44\x8B\x1C\x25\x10\x00\x00\x00", "mov r11w, [0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11b, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x44\x8A\x1C\x25\x10\x00\x00\x00", "mov r11b, [0x10]"); -} - -test "mov dst_reg, [src_reg + imm]" { - var mock = Mock.init(); - defer mock.deinit(); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x48\x8B\x4D\x10", "mov rcx, [rbp + 0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10000000 }, - }, "\x48\x8B\x8D\x00\x00\x00\x10", "mov rcx, [rbp + 0x10000000]"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11b, .reg2 = .rbp, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x44\x8A\x5D\x10", "mov r11b, [rbp + 0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .rbp, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10000000 }, - }, "\x66\x44\x8B\x9D\x00\x00\x00\x10", "mov r11w, [rbp + 0x10000000]"); -} - -test "mov [dst_reg + 0], imm" { - var mock = Mock.init(); - defer mock.deinit(); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x41\xC7\x03\x10\x00\x00\x00", "mov dword ptr [r11 + 0], 0x10"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10000000 }, - }, "\xC7\x00\x00\x00\x00\x10", "mov dword ptr [rax + 0], 0x10000000"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x1000 }, - }, "\xC7\x00\x00\x10\x00\x00", "mov dword ptr [rax + 0], 0x1000"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\xC7\x00\x10\x00\x00\x00", "mov dword ptr [rax + 0], 0x10"); - try mock.testEmitSingleFail(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .eax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "size mismatch: sizeof Register.eax != 8"); -} - -test "mov [dst_reg + imm32], src_reg" { - var mock = Mock.init(); - defer mock.deinit(); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x4c\x89\x5d\x10", "mov qword ptr [rbp + 0x10], r11"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11d, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x44\x89\x5d\x10", "mov dword ptr [rbp + 0x10], r11d"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11w, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x66\x44\x89\x5d\x10", "mov word ptr [rbp + 0x10], r11w"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11b, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x44\x88\x5d\x10", "mov byte ptr [rbp + 0x10], r11b"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .rax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x49\x89\x43\x10", "mov qword ptr [r11 + 0x10], rax"); - try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .eax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x41\x89\x43\x10", "mov dword ptr [r11 + 0x10], eax"); - try mock.testEmitSingleFail(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .ax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "size mismatch: sizeof Register.r11w != 8"); -} - -test "mov [dst_reg + imm32], imm32" { - var mock = Mock.init(); - defer mock.deinit(); - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10, - .operand = 0x20000000, - }); + inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { + const opcode = comptime getArithOpCode(tag, .mr); + const opc = [1]u8{opcode.opc}; + const opc_1 = [1]u8{opcode.opc - 1}; try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, "\xC7\x45\x10\x00\x00\x00\x20", "mov dword ptr [rbp + 0x10], 0x20000000"); - } - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10, - .operand = 0x2000, - }); + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .rsp }).encode(), + .data = undefined, + }, "\x48" ++ opc ++ "\xe5", @tagName(tag) ++ " rbp, rsp"); try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, "\xC7\x45\x10\x00\x20\x00\x00", "mov dword ptr [rbp + 0x10], 0x2000"); - } - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10, - .operand = 0x20, - }); + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .rax }).encode(), + .data = undefined, + }, "\x49" ++ opc ++ "\xc4", @tagName(tag) ++ " r12, rax"); + try mock.testEmitSingleFail(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .eax }).encode(), + .data = undefined, + }, "size mismatch: sizeof Register.r12 != sizeof Register.eax"); + try mock.testEmitSingleFail(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .rax }).encode(), + .data = undefined, + }, "size mismatch: sizeof Register.r12d != sizeof Register.rax"); try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, "\xc7\x45\x10\x20\x00\x00\x00", "mov dword ptr [rbp + 0x10], 0x20"); - } - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10, - .operand = 0x20000000, - }); + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .eax }).encode(), + .data = undefined, + }, "\x41" ++ opc ++ "\xc4", @tagName(tag) ++ " r12d, eax"); + // TODO mov r12b, ah requires a codepath without REX prefix try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, "\x41\xC7\x43\x10\x00\x00\x00\x20", "mov dword ptr [r11 + 0x10], 0x20000000"); + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r12b, .reg2 = .al }).encode(), + .data = undefined, + }, "\x41" ++ opc_1 ++ "\xc4", @tagName(tag) ++ " r12b, al"); } - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10000000, - .operand = 0x20000000, - }); +} + +test "ARITH_OP/MOV dst_reg, imm" { + var mock = Mock.init(); + defer mock.deinit(); + + const ModRmByte = struct { + inline fn get(tag: Mir.Inst.Tag, reg: u8) [1]u8 { + const modrm: u8 = getArithOpCode(tag, .mi).modrm_ext; + return .{0xc0 + (modrm << 3) + reg}; + } + }; + + inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { + const opcode = comptime getArithOpCode(tag, .mi); + const opc = [1]u8{opcode.opc}; + const opc_1 = [1]u8{opcode.opc - 1}; try mock.testEmitSingleSuccess(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, "\x41\xC7\x83\x00\x00\x00\x10\x00\x00\x00\x20", "mov dword ptr [r11 + 0x10], 0x20000000"); - } - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10, - .operand = 0x20, - }); + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rcx }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x48" ++ opc ++ ModRmByte.get(tag, 1) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " rcx, 0x10"); + // TODO we are wasting one byte here: this could be encoded as OI with the encoding + // opc + rd, imm8/16/32 + // b9 10 00 00 00 + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .ecx }).encode(), + .data = .{ .imm = 0x10 }, + }, opc ++ ModRmByte.get(tag, 1) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " ecx, 0x10"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .cx }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x66" ++ opc ++ ModRmByte.get(tag, 1) ++ "\x10\x00", @tagName(tag) ++ " cx, 0x10"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11w }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x66\x41" ++ opc ++ ModRmByte.get(tag, 3) ++ "\x10\x00", @tagName(tag) ++ " r11w, 0x10"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .cl }).encode(), + .data = .{ .imm = 0x10 }, + }, opc_1 ++ ModRmByte.get(tag, 1) ++ "\x10", @tagName(tag) ++ " cl, 0x10"); try mock.testEmitSingleFail(.{ .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, "size mismatch: sizeof Register.r11d != 8"); + .ops = (Mir.Ops{ .reg1 = .cx }).encode(), + .data = .{ .imm = 0x10000000 }, + }, "size mismatch: sizeof Register.cx != sizeof 0x10000000"); + try mock.testEmitSingleFail(.{ + .tag = .mov, + .ops = (Mir.Ops{ .reg1 = .cl }).encode(), + .data = .{ .imm = 0x1000 }, + }, "size mismatch: sizeof Register.cl != sizeof 0x1000"); + } +} + +test "ARITH_OP/MOV dst_reg, [imm32]" { + var mock = Mock.init(); + defer mock.deinit(); + inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { + const opcode = comptime getArithOpCode(tag, .rm); + const opc = [1]u8{opcode.opc}; + const opc_1 = [1]u8{opcode.opc - 1}; + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rcx, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x48" ++ opc ++ "\x0C\x25\x10\x00\x00\x00", @tagName(tag) ++ " rcx, [0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x4C" ++ opc ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11, [0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x44" ++ opc ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11d, [0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11w, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x66\x44" ++ opc ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11w, [0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11b, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x44" ++ opc_1 ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11b, [0x10]"); + } +} + +test "ARITH_OP/MOV dst_reg, [src_reg + imm]" { + var mock = Mock.init(); + defer mock.deinit(); + inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { + const opcode = comptime getArithOpCode(tag, .rm); + const opc = [1]u8{opcode.opc}; + const opc_1 = [1]u8{opcode.opc - 1}; + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x48" ++ opc ++ "\x4D\x10", @tagName(tag) ++ " rcx, [rbp + 0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10000000 }, + }, "\x48" ++ opc ++ "\x8D\x00\x00\x00\x10", @tagName(tag) ++ " rcx, [rbp + 0x10000000]"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11b, .reg2 = .rbp, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x44" ++ opc_1 ++ "\x5D\x10", @tagName(tag) ++ " r11b, [rbp + 0x10]"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .rbp, .flags = 0b01 }).encode(), + .data = .{ .imm = 0x10000000 }, + }, "\x66\x44" ++ opc ++ "\x9D\x00\x00\x00\x10", @tagName(tag) ++ " r11w, [rbp + 0x10000000]"); + } +} + +test "ARITH_OP/MOV [dst_reg + 0], imm" { + var mock = Mock.init(); + defer mock.deinit(); + + const ModRmByte = struct { + inline fn get(tag: Mir.Inst.Tag, reg: u8) [1]u8 { + const modrm: u8 = getArithOpCode(tag, .mi).modrm_ext; + return .{(modrm << 3) + reg}; + } + }; + + inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { + const opcode = comptime getArithOpCode(tag, .mi); + const opc = [1]u8{opcode.opc}; + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x41" ++ opc ++ ModRmByte.get(tag, 3) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " dword ptr [r11 + 0], 0x10"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10000000 }, + }, opc ++ ModRmByte.get(tag, 0) ++ "\x00\x00\x00\x10", @tagName(tag) ++ " dword ptr [rax + 0], 0x10000000"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x1000 }, + }, opc ++ ModRmByte.get(tag, 0) ++ "\x00\x10\x00\x00", @tagName(tag) ++ " dword ptr [rax + 0], 0x1000"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, opc ++ ModRmByte.get(tag, 0) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " dword ptr [rax + 0], 0x10"); + try mock.testEmitSingleFail(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .eax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "size mismatch: sizeof Register.eax != 8"); + } +} + +test "ARITH_OP/MOV [dst_reg + imm32], src_reg" { + var mock = Mock.init(); + defer mock.deinit(); + inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { + const opcode = comptime getArithOpCode(tag, .mr); + const opc = [1]u8{opcode.opc}; + const opc_1 = [1]u8{opcode.opc - 1}; + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x4c" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " qword ptr [rbp + 0x10], r11"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11d, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x44" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " dword ptr [rbp + 0x10], r11d"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11w, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x66\x44" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " word ptr [rbp + 0x10], r11w"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11b, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x44" ++ opc_1 ++ "\x5d\x10", @tagName(tag) ++ " byte ptr [rbp + 0x10], r11b"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .rax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x49" ++ opc ++ "\x43\x10", @tagName(tag) ++ " qword ptr [r11 + 0x10], rax"); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .eax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "\x41" ++ opc ++ "\x43\x10", @tagName(tag) ++ " dword ptr [r11 + 0x10], eax"); + try mock.testEmitSingleFail(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .ax, .flags = 0b10 }).encode(), + .data = .{ .imm = 0x10 }, + }, "size mismatch: sizeof Register.r11w != 8"); + } +} + +test "ARITH_OP/MOV [dst_reg + imm32], imm32" { + var mock = Mock.init(); + defer mock.deinit(); + + const ModRmByte = struct { + inline fn get(tag: Mir.Inst.Tag, disp: u2, reg: u8) [1]u8 { + const modrm: u8 = getArithOpCode(tag, .mi).modrm_ext; + return .{(@as(u8, disp) << 6) + (modrm << 3) + reg}; + } + }; + + inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { + const opcode = comptime getArithOpCode(tag, .mi); + const opc = [1]u8{opcode.opc}; + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10, + .operand = 0x20000000, + }); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x00\x00\x00\x20", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x20000000"); + } + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10, + .operand = 0x2000, + }); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x00\x20\x00\x00", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x2000"); + } + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10, + .operand = 0x20, + }); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x20\x00\x00\x00", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x20"); + } + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10, + .operand = 0x20000000, + }); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, "\x41" ++ opc ++ ModRmByte.get(tag, 1, 3) ++ "\x10\x00\x00\x00\x20", @tagName(tag) ++ " dword ptr [r11 + 0x10], 0x20000000"); + } + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10000000, + .operand = 0x20000000, + }); + try mock.testEmitSingleSuccess(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, "\x41" ++ opc ++ ModRmByte.get(tag, 2, 3) ++ "\x00\x00\x00\x10\x00\x00\x00\x20", @tagName(tag) ++ " dword ptr [r11 + 0x10], 0x20000000"); + } + { + const payload = try mock.addExtra(Mir.ImmPair{ + .dest_off = 0x10, + .operand = 0x20, + }); + try mock.testEmitSingleFail(.{ + .tag = tag, + .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b11 }).encode(), + .data = .{ .payload = payload }, + }, "size mismatch: sizeof Register.r11d != 8"); + } } } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index e2cc3cab30..51420b47e6 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -136,19 +136,6 @@ pub const Inst = struct { cmp_scale_src, cmp_scale_dst, cmp_scale_imm, - - /// ops flags: form: - /// 0b00 reg1, reg2 (MR) - /// 0b00 reg1, imm32 - /// 0b01 reg1, [reg2 + imm32] - /// 0b01 reg1, [ds:imm32] - /// 0b10 [reg1 + imm32], reg2 - /// 0b10 [reg1 + 0], imm32 - /// 0b11 [reg1 + imm32], imm32 - /// 0b11 AVAILABLE - /// Notes: - /// * If reg2 is `none` then it means Data field `imm` is used as the immediate. - /// * When two imm32 values are required, Data field `payload` points at `ImmPair`. mov, mov_scale_src, mov_scale_dst, From 2b5de9403d51561642b241f5940d5e7630993849 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 21 Dec 2021 16:52:50 +0100 Subject: [PATCH 05/19] stage2: create generic lowering fns for MI, RM, and MR encodings This way I am hopeful they can be reused for every MIR lowering function which follows a given encoding. Currently, support MI, RM and MR encodings without SIB scaling. --- src/arch/x86_64/Emit.zig | 1071 ++++++++++++---------------------- src/arch/x86_64/Mir.zig | 6 +- src/arch/x86_64/PrintMir.zig | 6 +- 3 files changed, 390 insertions(+), 693 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index ee8856a741..5742d82254 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -48,33 +48,6 @@ const InnerError = error{ EmitFail, }; -const EmitResult = union(enum) { - ok: void, - err: *ErrorMsg, - - fn ok() EmitResult { - return EmitResult{ .ok = .{} }; - } - - fn err( - allocator: Allocator, - src_loc: Module.SrcLoc, - comptime format: []const u8, - args: anytype, - ) error{OutOfMemory}!EmitResult { - return EmitResult{ - .err = try ErrorMsg.create(allocator, src_loc, format, args), - }; - } - - fn deinit(res: EmitResult, allocator: Allocator) void { - switch (res) { - .ok => {}, - .err => |err_msg| err_msg.destroy(allocator), - } - } -}; - const Reloc = struct { /// Offset of the instruction. source: u64, @@ -184,15 +157,9 @@ pub fn deinit(emit: *Emit) void { } fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError { - @setCold(true); - const err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args); - return emit.failWithErrorMsg(err_msg); -} - -fn failWithErrorMsg(emit: *Emit, err_msg: *ErrorMsg) InnerError { @setCold(true); assert(emit.err_msg == null); - emit.err_msg = err_msg; + emit.err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args); return error.EmitFail; } @@ -565,7 +532,7 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } -const EncType = enum { +const Encoding = enum { /// OP r/m64, imm32 mi, @@ -578,11 +545,11 @@ const EncType = enum { const OpCode = struct { opc: u8, - /// Only used if `EncType == .mi`. + /// Only used if `Encoding == .mi`. modrm_ext: u3, }; -inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { +inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) OpCode { switch (enc) { .mi => return switch (tag) { .adc => .{ .opc = 0x81, .modrm_ext = 0x2 }, @@ -629,247 +596,298 @@ inline fn getArithOpCode(tag: Mir.Inst.Tag, enc: EncType) OpCode { } } -fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { - const res = try mirArithImpl( - emit.bin_file.allocator, - tag, - emit.mir.instructions, - emit.mir.extra, - inst, - emit.src_loc, - emit.code, - ); - switch (res) { - .ok => {}, - .err => |err_msg| return emit.failWithErrorMsg(err_msg), +const ScaleIndexBase = struct { + scale: u2, + index_reg: ?Register, + base_reg: ?Register, +}; + +const Memory = struct { + reg: ?Register, + disp: i32, + sib: ?ScaleIndexBase = null, +}; + +const RegisterOrMemory = union(enum) { + register: Register, + memory: Memory, + + fn reg(register: Register) RegisterOrMemory { + return .{ .register = register }; + } + + fn mem(register: ?Register, disp: i32) RegisterOrMemory { + return .{ + .memory = .{ + .reg = register, + .disp = disp, + }, + }; + } +}; + +fn lowerToMiEnc( + tag: Mir.Inst.Tag, + reg_or_mem: RegisterOrMemory, + imm: i32, + code: *std.ArrayList(u8), +) InnerError!void { + const opcode = getOpCode(tag, .mi); + switch (reg_or_mem) { + .register => |dst_reg| { + const opc: u8 = if (dst_reg.size() == 8) opcode.opc - 1 else opcode.opc; + const encoder = try Encoder.init(code, 7); + if (dst_reg.size() == 16) { + // 0x66 prefix switches to the non-default size; here we assume a switch from + // the default 32bits to 16bits operand-size. + // 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 + encoder.opcode_1byte(0x66); + } + encoder.rex(.{ + .w = dst_reg.size() == 64, + .b = dst_reg.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_direct(opcode.modrm_ext, dst_reg.lowId()); + switch (dst_reg.size()) { + 8 => { + const imm8 = try math.cast(i8, imm); + encoder.imm8(imm8); + }, + 16 => { + const imm16 = try math.cast(i16, imm); + encoder.imm16(imm16); + }, + 32, 64 => encoder.imm32(imm), + else => unreachable, + } + }, + .memory => |dst_mem| { + const encoder = try Encoder.init(code, 12); + if (dst_mem.reg) |dst_reg| { + // Register dst_reg can either be 64bit or 32bit in size. + // TODO for memory operand, immediate operand pair, we currently + // have no way of flagging whether the immediate can be 8-, 16- or + // 32-bit and whether the corresponding memory operand is respectively + // a byte, word or dword ptr. + // TODO we currently don't have a way to flag imm32 64bit sign extended + if (dst_reg.size() != 64) return error.EmitFail; + encoder.rex(.{ + .w = false, + .b = dst_reg.isExtended(), + }); + encoder.opcode_1byte(opcode.opc); + if (dst_mem.disp == 0) { + encoder.modRm_indirectDisp0(opcode.modrm_ext, dst_reg.lowId()); + } else if (immOpSize(dst_mem.disp) == 8) { + encoder.modRm_indirectDisp8(opcode.modrm_ext, dst_reg.lowId()); + encoder.disp8(@intCast(i8, dst_mem.disp)); + } else { + if (dst_reg.lowId() == 4) { + encoder.modRm_SIBDisp32(opcode.modrm_ext); + encoder.sib_baseDisp32(dst_reg.lowId()); + encoder.disp32(dst_mem.disp); + } else { + encoder.modRm_indirectDisp32(opcode.modrm_ext, dst_reg.lowId()); + encoder.disp32(dst_mem.disp); + } + } + } else { + encoder.opcode_1byte(opcode.opc); + encoder.modRm_SIBDisp0(opcode.modrm_ext); + encoder.sib_disp32(); + encoder.disp32(dst_mem.disp); + } + encoder.imm32(imm); + }, } } -fn mirArithImpl( - allocator: Allocator, +fn lowerToRmEnc( tag: Mir.Inst.Tag, - mir_instructions: std.MultiArrayList(Mir.Inst).Slice, - mir_extra: []const u32, - inst: Mir.Inst.Index, - src_loc: Module.SrcLoc, + reg: Register, + reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8), -) error{OutOfMemory}!EmitResult { - const ops = Mir.Ops.decode(mir_instructions.items(.ops)[inst]); +) InnerError!void { + const opcode = getOpCode(tag, .rm); + const opc: u8 = if (reg.size() == 8) opcode.opc - 1 else opcode.opc; + switch (reg_or_mem) { + .register => |src_reg| { + if (reg.size() != src_reg.size()) return error.EmitFail; + const encoder = try Encoder.init(code, 3); + encoder.rex(.{ + .w = reg.size() == 64, + .r = reg.isExtended(), + .b = src_reg.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_direct(reg.lowId(), src_reg.lowId()); + }, + .memory => |src_mem| { + const encoder = try Encoder.init(code, 9); + if (reg.size() == 16) { + encoder.opcode_1byte(0x66); + } + if (src_mem.reg) |src_reg| { + // TODO handle 32-bit base register - requires prefix 0x67 + // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 + if (src_reg.size() != 64) return error.EmitFail; + encoder.rex(.{ + .w = reg.size() == 64, + .r = reg.isExtended(), + .b = src_reg.isExtended(), + }); + encoder.opcode_1byte(opc); + if (src_mem.disp == 0) { + encoder.modRm_indirectDisp0(reg.lowId(), src_reg.lowId()); + } else if (immOpSize(src_mem.disp) == 8) { + encoder.modRm_indirectDisp8(reg.lowId(), src_reg.lowId()); + encoder.disp8(@intCast(i8, src_mem.disp)); + } else { + if (src_reg.lowId() == 4) { + encoder.modRm_SIBDisp32(reg.lowId()); + encoder.sib_baseDisp32(src_reg.lowId()); + encoder.disp32(src_mem.disp); + } else { + encoder.modRm_indirectDisp32(reg.lowId(), src_reg.lowId()); + encoder.disp32(src_mem.disp); + } + } + } else { + encoder.rex(.{ + .w = reg.size() == 64, + .r = reg.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_SIBDisp0(reg.lowId()); + encoder.sib_disp32(); + encoder.disp32(src_mem.disp); + } + }, + } +} + +fn lowerToMrEnc( + tag: Mir.Inst.Tag, + reg_or_mem: RegisterOrMemory, + reg: Register, + code: *std.ArrayList(u8), +) InnerError!void { + // We use size of source register reg to work out which + // variant of memory ptr to pick: + // * reg is 64bit - qword ptr + // * reg is 32bit - dword ptr + // * reg is 16bit - word ptr + // * reg is 8bit - byte ptr + const opcode = getOpCode(tag, .mr); + const opc: u8 = if (reg.size() == 8) opcode.opc - 1 else opcode.opc; + switch (reg_or_mem) { + .register => |dst_reg| { + if (dst_reg.size() != reg.size()) return error.EmitFail; + const encoder = try Encoder.init(code, 3); + encoder.rex(.{ + .w = dst_reg.size() == 64, + .r = reg.isExtended(), + .b = dst_reg.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_direct(reg.lowId(), dst_reg.lowId()); + }, + .memory => |dst_mem| { + const encoder = try Encoder.init(code, 9); + if (reg.size() == 16) { + encoder.opcode_1byte(0x66); + } + if (dst_mem.reg) |dst_reg| { + if (dst_reg.size() != 64) return error.EmitFail; + encoder.rex(.{ + .w = reg.size() == 64, + .r = reg.isExtended(), + .b = dst_reg.isExtended(), + }); + encoder.opcode_1byte(opc); + if (dst_mem.disp == 0) { + encoder.modRm_indirectDisp0(reg.lowId(), dst_reg.lowId()); + } else if (immOpSize(dst_mem.disp) == 8) { + encoder.modRm_indirectDisp8(reg.lowId(), dst_reg.lowId()); + encoder.disp8(@intCast(i8, dst_mem.disp)); + } else { + if (dst_reg.lowId() == 4) { + encoder.modRm_SIBDisp32(reg.lowId()); + encoder.sib_baseDisp32(dst_reg.lowId()); + encoder.disp32(dst_mem.disp); + } else { + encoder.modRm_indirectDisp32(reg.lowId(), dst_reg.lowId()); + encoder.disp32(dst_mem.disp); + } + } + } else { + encoder.rex(.{ + .w = reg.size() == 64, + .r = reg.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_SIBDisp0(reg.lowId()); + encoder.sib_disp32(); + encoder.disp32(dst_mem.disp); + } + }, + } +} + +fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { + const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); switch (ops.flags) { - 0b00 => blk: { + 0b00 => { if (ops.reg2 == .none) { // mov reg1, imm32 // MI - const imm = mir_instructions.items(.data)[inst].imm; - const opcode = getArithOpCode(tag, .mi); - const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; - const encoder = try Encoder.init(code, 7); - if (ops.reg1.size() == 16) { - // 0x66 prefix switches to the non-default size; here we assume a switch from - // the default 32bits to 16bits operand-size. - // 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 - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_direct(opcode.modrm_ext, ops.reg1.lowId()); - switch (ops.reg1.size()) { - 8 => { - const imm8 = math.cast(i8, imm) catch { - return EmitResult.err( - allocator, - src_loc, - "size mismatch: sizeof {} != sizeof 0x{x}", - .{ - ops.reg1, - imm, - }, - ); - }; - encoder.imm8(imm8); - }, - 16 => { - const imm16 = math.cast(i16, imm) catch { - return EmitResult.err( - allocator, - src_loc, - "size mismatch: sizeof {} != sizeof 0x{x}", - .{ - ops.reg1, - imm, - }, - ); - }; - encoder.imm16(imm16); - }, - 32, 64 => { - encoder.imm32(imm); - }, - else => unreachable, - } - break :blk; + const imm = emit.mir.instructions.items(.data)[inst].imm; + return lowerToMiEnc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code); } // mov reg1, reg2 - // MR - if (ops.reg1.size() != ops.reg2.size()) { - return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != sizeof {}", .{ - ops.reg1, - ops.reg2, - }); - } - const opcode = getArithOpCode(tag, .mr); - const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; - const encoder = try Encoder.init(code, 3); - encoder.rex(.{ - .w = ops.reg1.size() == 64 and ops.reg2.size() == 64, - .r = ops.reg2.isExtended(), - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_direct(ops.reg2.lowId(), ops.reg1.lowId()); + // RM + return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); }, - 0b01 => blk: { - const imm = mir_instructions.items(.data)[inst].imm; - const opcode = getArithOpCode(tag, .rm); - const opc: u8 = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; + 0b01 => { + const imm = emit.mir.instructions.items(.data)[inst].imm; if (ops.reg2 == .none) { // mov reg1, [imm32] // RM - const encoder = try Encoder.init(code, 9); - if (ops.reg1.size() == 16) { - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_SIBDisp0(ops.reg1.lowId()); - encoder.sib_disp32(); - encoder.disp32(imm); - break :blk; + return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(null, imm), emit.code); } // mov reg1, [reg2 + imm32] // RM - // TODO handle 32-bit base register - requires prefix 0x67 - // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 - if (ops.reg2.size() != 64) { - return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg2}); - } - const encoder = try Encoder.init(code, 8); - if (ops.reg1.size() == 16) { - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), - }); - encoder.opcode_1byte(opc); - if (immOpSize(imm) == 8) { - encoder.modRm_indirectDisp8(ops.reg1.lowId(), ops.reg2.lowId()); - encoder.disp8(@intCast(i8, imm)); - } else { - encoder.modRm_indirectDisp32(ops.reg1.lowId(), ops.reg2.lowId()); - encoder.disp32(imm); - } + return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(ops.reg2, imm), emit.code); }, - 0b10 => blk: { - // TODO handle 32-bit base register - requires prefix 0x67 - // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 - if (ops.reg1.size() != 64) { - return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1}); - } + 0b10 => { if (ops.reg2 == .none) { - // mov [reg1 + 0], imm32 + // mov dword ptr [reg1 + 0], imm32 // MI - // Base register reg1 can either be 64bit or 32bit in size. - // TODO for memory operand, immediate operand pair, we currently - // have no way of flagging whether the immediate can be 8-, 16- or - // 32-bit and whether the corresponding memory operand is respectively - // a byte, word or dword ptr. - // TODO we currently don't have a way to flag imm32 64bit sign extended - const imm = mir_instructions.items(.data)[inst].imm; - const opcode = getArithOpCode(tag, .mi); - const encoder = try Encoder.init(code, 7); - encoder.rex(.{ - .w = false, - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opcode.opc); - encoder.modRm_indirectDisp0(opcode.modrm_ext, ops.reg1.lowId()); - encoder.imm32(imm); - break :blk; + const imm = emit.mir.instructions.items(.data)[inst].imm; + return lowerToMiEnc(tag, RegisterOrMemory.mem(ops.reg1, 0), imm, emit.code); } // mov [reg1 + imm32], reg2 // MR - // We use size of source register reg2 to work out which - // variant of memory ptr to pick: - // * reg2 is 64bit - qword ptr - // * reg2 is 32bit - dword ptr - // * reg2 is 16bit - word ptr - // * reg2 is 8bit - byte ptr - const imm = mir_instructions.items(.data)[inst].imm; - const opcode = getArithOpCode(tag, .mr); - const opc: u8 = if (ops.reg2.size() == 8) opcode.opc - 1 else opcode.opc; - const encoder = try Encoder.init(code, 5); - if (ops.reg2.size() == 16) { - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = ops.reg2.size() == 64, - .r = ops.reg2.isExtended(), - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - if (immOpSize(imm) == 8) { - encoder.modRm_indirectDisp8(ops.reg2.lowId(), ops.reg1.lowId()); - encoder.disp8(@intCast(i8, imm)); - } else { - encoder.modRm_indirectDisp32(ops.reg2.lowId(), ops.reg1.lowId()); - encoder.disp32(imm); - } + const imm = emit.mir.instructions.items(.data)[inst].imm; + return lowerToMrEnc(tag, RegisterOrMemory.mem(ops.reg1, imm), ops.reg2, emit.code); }, - 0b11 => blk: { + 0b11 => { if (ops.reg2 == .none) { - // mov [reg1 + imm32], imm32 + // mov dword ptr [reg1 + imm32], imm32 // MI - // Base register reg1 can either be 64bit or 32bit in size. - // TODO for memory operand, immediate operand pair, we currently - // have no way of flagging whether the immediate can be 8-, 16- or - // 32-bit and whether the corresponding memory operand is respectively - // a byte, word or dword ptr. - // TODO we currently don't have a way to flag imm32 64bit sign extended - if (ops.reg1.size() != 64) { - return EmitResult.err(allocator, src_loc, "size mismatch: sizeof {} != 8", .{ops.reg1}); - } - const payload = mir_instructions.items(.data)[inst].payload; - const imm_pair = Mir.extraData(mir_extra, Mir.ImmPair, payload).data; - const imm_op = imm_pair.operand; - const opcode = getArithOpCode(tag, .mi); - const encoder = try Encoder.init(code, 10); - encoder.rex(.{ - .w = false, - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opcode.opc); - if (immOpSize(imm_pair.dest_off) == 8) { - encoder.modRm_indirectDisp8(opcode.modrm_ext, ops.reg1.lowId()); - encoder.disp8(@intCast(i8, imm_pair.dest_off)); - } else { - encoder.modRm_indirectDisp32(opcode.modrm_ext, ops.reg1.lowId()); - encoder.disp32(imm_pair.dest_off); - } - encoder.imm32(imm_op); - break :blk; + const payload = emit.mir.instructions.items(.data)[inst].payload; + const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; + return lowerToMiEnc( + tag, + RegisterOrMemory.mem(ops.reg1, imm_pair.dest_off), + imm_pair.operand, + emit.code, + ); } - return EmitResult.err(allocator, src_loc, "TODO unused variant: mov reg1, reg2, 0b11", .{}); + return emit.fail("TODO unused variant: mov reg1, reg2, 0b11", .{}); }, } - return EmitResult.ok(); } fn immOpSize(imm: i32) u8 { @@ -888,7 +906,7 @@ fn mirArithScaleSrc(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; // OP reg1, [reg2 + scale*rcx + imm32] - const opcode = getArithOpCode(tag, .rm); + const opcode = getOpCode(tag, .rm); const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; const imm = emit.mir.instructions.items(.data)[inst].imm; 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 if (ops.reg2 == .none) { // OP [reg1 + scale*rax + 0], imm32 - const opcode = getArithOpCode(tag, .mi); + const opcode = getOpCode(tag, .mi); const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; const encoder = try Encoder.init(emit.code, 8); encoder.rex(.{ @@ -937,7 +955,7 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE } // OP [reg1 + scale*rax + imm32], reg2 - const opcode = getArithOpCode(tag, .mr); + const opcode = getOpCode(tag, .mr); const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; const encoder = try Encoder.init(emit.code, 8); encoder.rex(.{ @@ -961,8 +979,8 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm_pair = Mir.extraData(emit.mir.extra, Mir.ImmPair, payload).data; - const opcode = getArithOpCode(tag, .mi); + const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; + const opcode = getOpCode(tag, .mi); const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; const encoder = try Encoder.init(emit.code, 2); encoder.rex(.{ @@ -1023,7 +1041,7 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { if (is_64) { const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm64 = Mir.extraData(emit.mir.extra, Mir.Imm64, payload).data; + const imm64 = emit.mir.extraData(Mir.Imm64, payload).data; encoder.imm64(imm64.decode()); } else { const imm = emit.mir.instructions.items(.data)[inst].imm; @@ -1129,7 +1147,7 @@ fn mirLeaRip(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const end_offset = emit.code.items.len; if (@truncate(u1, ops.flags) == 0b0) { const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = Mir.extraData(emit.mir.extra, Mir.Imm64, payload).data.decode(); + const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode(); encoder.disp32(@intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset + 4))); } else { 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 { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .dbg_line); const payload = emit.mir.instructions.items(.data)[inst].payload; - const dbg_line_column = Mir.extraData(emit.mir.extra, Mir.DbgLineColumn, payload).data; + const dbg_line_column = emit.mir.extraData(Mir.DbgLineColumn, payload).data; try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column); } @@ -1269,7 +1287,7 @@ fn mirArgDbgInfo(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .arg_dbg_info); const payload = emit.mir.instructions.items(.data)[inst].payload; - const arg_dbg_info = Mir.extraData(emit.mir.extra, Mir.ArgDbgInfo, payload).data; + const arg_dbg_info = emit.mir.extraData(Mir.ArgDbgInfo, payload).data; const mcv = emit.mir.function.args[arg_dbg_info.arg_index]; try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv); } @@ -1333,111 +1351,6 @@ fn addDbgInfoTypeReloc(emit: *Emit, ty: Type) !void { } } -const Mock = struct { - const gpa = testing.allocator; - - mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, - mir_extra: std.ArrayList(u32), - code: std.ArrayList(u8), - - fn init() Mock { - return .{ - .mir_extra = std.ArrayList(u32).init(gpa), - .code = std.ArrayList(u8).init(gpa), - }; - } - - fn deinit(self: *Mock) void { - self.mir_instructions.deinit(gpa); - self.mir_extra.deinit(); - self.code.deinit(); - } - - fn addInst(self: *Mock, inst: Mir.Inst) error{OutOfMemory}!Mir.Inst.Index { - try self.mir_instructions.ensureUnusedCapacity(gpa, 1); - const result_index = @intCast(Air.Inst.Index, self.mir_instructions.len); - self.mir_instructions.appendAssumeCapacity(inst); - return result_index; - } - - fn addExtra(self: *Mock, extra: anytype) Allocator.Error!u32 { - const fields = std.meta.fields(@TypeOf(extra)); - try self.mir_extra.ensureUnusedCapacity(fields.len); - return self.addExtraAssumeCapacity(extra); - } - - fn addExtraAssumeCapacity(self: *Mock, extra: anytype) u32 { - const fields = std.meta.fields(@TypeOf(extra)); - const result = @intCast(u32, self.mir_extra.items.len); - inline for (fields) |field| { - self.mir_extra.appendAssumeCapacity(switch (field.field_type) { - u32 => @field(extra, field.name), - i32 => @bitCast(u32, @field(extra, field.name)), - else => @compileError("bad field type"), - }); - } - return result; - } - - fn dummySrcLoc() Module.SrcLoc { - return .{ - .file_scope = undefined, - .parent_decl_node = 0, - .lazy = .unneeded, - }; - } - - fn testEmitSingleSuccess( - self: *Mock, - mir_inst: Mir.Inst, - expected_enc: []const u8, - assembly: []const u8, - ) !void { - const dummy_src_loc = Mock.dummySrcLoc(); - const code_index = self.code.items.len; - const mir_index = try self.addInst(mir_inst); - const res = switch (mir_inst.tag) { - .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov => try mirArithImpl( - testing.allocator, - mir_inst.tag, - self.mir_instructions.slice(), - self.mir_extra.items, - mir_index, - dummy_src_loc, - &self.code, - ), - else => unreachable, - }; - defer res.deinit(testing.allocator); - try testing.expect(res == .ok); - const code_len = if (self.code.items[code_index..].len >= expected_enc.len) - expected_enc.len - else - self.code.items.len - code_index; - try expectEqualHexStrings(expected_enc, self.code.items[code_index..][0..code_len], assembly); - } - - fn testEmitSingleFail(self: *Mock, mir_inst: Mir.Inst, msg: []const u8) !void { - const dummy_src_loc = Mock.dummySrcLoc(); - const index = try self.addInst(mir_inst); - const res = switch (mir_inst.tag) { - .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov => try mirArithImpl( - testing.allocator, - mir_inst.tag, - self.mir_instructions.slice(), - self.mir_extra.items, - index, - dummy_src_loc, - &self.code, - ), - else => unreachable, - }; - defer res.deinit(testing.allocator); - try testing.expect(res == .err); - try testing.expectEqualStrings(msg, res.err.msg); - } -}; - fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []const u8) !void { assert(expected.len > 0); if (mem.eql(u8, expected, given)) return; @@ -1458,334 +1371,118 @@ fn expectEqualHexStrings(expected: []const u8, given: []const u8, assembly: []co return error.TestFailed; } -test "ARITH_OP/MOV dst_reg, src_reg" { - var mock = Mock.init(); - defer mock.deinit(); - inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { - const opcode = comptime getArithOpCode(tag, .mr); - const opc = [1]u8{opcode.opc}; - const opc_1 = [1]u8{opcode.opc - 1}; - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .rsp }).encode(), - .data = undefined, - }, "\x48" ++ opc ++ "\xe5", @tagName(tag) ++ " rbp, rsp"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .rax }).encode(), - .data = undefined, - }, "\x49" ++ opc ++ "\xc4", @tagName(tag) ++ " r12, rax"); - try mock.testEmitSingleFail(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r12, .reg2 = .eax }).encode(), - .data = undefined, - }, "size mismatch: sizeof Register.r12 != sizeof Register.eax"); - try mock.testEmitSingleFail(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .rax }).encode(), - .data = undefined, - }, "size mismatch: sizeof Register.r12d != sizeof Register.rax"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r12d, .reg2 = .eax }).encode(), - .data = undefined, - }, "\x41" ++ opc ++ "\xc4", @tagName(tag) ++ " r12d, eax"); - // TODO mov r12b, ah requires a codepath without REX prefix - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r12b, .reg2 = .al }).encode(), - .data = undefined, - }, "\x41" ++ opc_1 ++ "\xc4", @tagName(tag) ++ " r12b, al"); +const TestEmitCode = struct { + buf: std.ArrayList(u8), + next: usize = 0, + + fn init() TestEmitCode { + return .{ + .buf = std.ArrayList(u8).init(testing.allocator), + }; } + + fn deinit(emit: *TestEmitCode) void { + emit.buf.deinit(); + emit.next = undefined; + } + + fn buffer(emit: *TestEmitCode) *std.ArrayList(u8) { + emit.next = emit.buf.items.len; + return &emit.buf; + } + + fn emitted(emit: TestEmitCode) []const u8 { + return emit.buf.items[emit.next..]; + } +}; + +test "lower MI encoding" { + var code = TestEmitCode.init(); + defer code.deinit(); + try lowerToMiEnc(.mov, RegisterOrMemory.reg(.rax), 0x10, code.buffer()); + try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", code.emitted(), "mov rax, 0x10"); + try lowerToMiEnc(.mov, RegisterOrMemory.mem(.r11, 0), 0x10, code.buffer()); + try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", code.emitted(), "mov dword ptr [r11 + 0], 0x10"); + try lowerToMiEnc(.add, RegisterOrMemory.mem(.rdx, -8), 0x10, code.buffer()); + try expectEqualHexStrings("\x81\x42\xF8\x10\x00\x00\x00", code.emitted(), "add dword ptr [rdx - 8], 0x10"); + try lowerToMiEnc(.sub, RegisterOrMemory.mem(.r11, 0x10000000), 0x10, code.buffer()); + try expectEqualHexStrings( + "\x41\x81\xab\x00\x00\x00\x10\x10\x00\x00\x00", + code.emitted(), + "sub dword ptr [r11 + 0x10000000], 0x10", + ); + try lowerToMiEnc(.@"and", RegisterOrMemory.mem(null, 0x10000000), 0x10, code.buffer()); + try expectEqualHexStrings( + "\x81\x24\x25\x00\x00\x00\x10\x10\x00\x00\x00", + code.emitted(), + "and dword ptr [ds:0x10000000], 0x10", + ); + try lowerToMiEnc(.@"and", RegisterOrMemory.mem(.r12, 0x10000000), 0x10, code.buffer()); + try expectEqualHexStrings( + "\x41\x81\xA4\x24\x00\x00\x00\x10\x10\x00\x00\x00", + code.emitted(), + "and dword ptr [r12 + 0x10000000], 0x10", + ); } -test "ARITH_OP/MOV dst_reg, imm" { - var mock = Mock.init(); - defer mock.deinit(); - - const ModRmByte = struct { - inline fn get(tag: Mir.Inst.Tag, reg: u8) [1]u8 { - const modrm: u8 = getArithOpCode(tag, .mi).modrm_ext; - return .{0xc0 + (modrm << 3) + reg}; - } - }; - - inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { - const opcode = comptime getArithOpCode(tag, .mi); - const opc = [1]u8{opcode.opc}; - const opc_1 = [1]u8{opcode.opc - 1}; - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rcx }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x48" ++ opc ++ ModRmByte.get(tag, 1) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " rcx, 0x10"); - // TODO we are wasting one byte here: this could be encoded as OI with the encoding - // opc + rd, imm8/16/32 - // b9 10 00 00 00 - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .ecx }).encode(), - .data = .{ .imm = 0x10 }, - }, opc ++ ModRmByte.get(tag, 1) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " ecx, 0x10"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .cx }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x66" ++ opc ++ ModRmByte.get(tag, 1) ++ "\x10\x00", @tagName(tag) ++ " cx, 0x10"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11w }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x66\x41" ++ opc ++ ModRmByte.get(tag, 3) ++ "\x10\x00", @tagName(tag) ++ " r11w, 0x10"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .cl }).encode(), - .data = .{ .imm = 0x10 }, - }, opc_1 ++ ModRmByte.get(tag, 1) ++ "\x10", @tagName(tag) ++ " cl, 0x10"); - try mock.testEmitSingleFail(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .cx }).encode(), - .data = .{ .imm = 0x10000000 }, - }, "size mismatch: sizeof Register.cx != sizeof 0x10000000"); - try mock.testEmitSingleFail(.{ - .tag = .mov, - .ops = (Mir.Ops{ .reg1 = .cl }).encode(), - .data = .{ .imm = 0x1000 }, - }, "size mismatch: sizeof Register.cl != sizeof 0x1000"); - } +test "lower RM encoding" { + var code = TestEmitCode.init(); + defer code.deinit(); + try lowerToRmEnc(.mov, .rax, RegisterOrMemory.reg(.rbx), code.buffer()); + try expectEqualHexStrings("\x48\x8b\xc3", code.emitted(), "mov rax, rbx"); + try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.r11, 0), code.buffer()); + try expectEqualHexStrings("\x49\x8b\x03", code.emitted(), "mov rax, qword ptr [r11 + 0]"); + try lowerToRmEnc(.add, .r11, RegisterOrMemory.mem(null, 0x10000000), code.buffer()); + try expectEqualHexStrings( + "\x4C\x03\x1C\x25\x00\x00\x00\x10", + code.emitted(), + "add r11, qword ptr [ds:0x10000000]", + ); + try lowerToRmEnc(.add, .r12b, RegisterOrMemory.mem(null, 0x10000000), code.buffer()); + try expectEqualHexStrings( + "\x44\x02\x24\x25\x00\x00\x00\x10", + code.emitted(), + "add r11b, byte ptr [ds:0x10000000]", + ); + try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.r13, 0x10000000), code.buffer()); + try expectEqualHexStrings( + "\x4D\x2B\x9D\x00\x00\x00\x10", + code.emitted(), + "sub r11, qword ptr [r13 + 0x10000000]", + ); + try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.r12, 0x10000000), code.buffer()); + try expectEqualHexStrings( + "\x4D\x2B\x9C\x24\x00\x00\x00\x10", + code.emitted(), + "sub r11, qword ptr [r12 + 0x10000000]", + ); + try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.rbp, -4), code.buffer()); + try expectEqualHexStrings("\x48\x8B\x45\xFC", code.emitted(), "mov rax, qword ptr [rbp - 4]"); } -test "ARITH_OP/MOV dst_reg, [imm32]" { - var mock = Mock.init(); - defer mock.deinit(); - inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { - const opcode = comptime getArithOpCode(tag, .rm); - const opc = [1]u8{opcode.opc}; - const opc_1 = [1]u8{opcode.opc - 1}; - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rcx, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x48" ++ opc ++ "\x0C\x25\x10\x00\x00\x00", @tagName(tag) ++ " rcx, [0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x4C" ++ opc ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11, [0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x44" ++ opc ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11d, [0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11w, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x66\x44" ++ opc ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11w, [0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11b, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x44" ++ opc_1 ++ "\x1C\x25\x10\x00\x00\x00", @tagName(tag) ++ " r11b, [0x10]"); - } -} - -test "ARITH_OP/MOV dst_reg, [src_reg + imm]" { - var mock = Mock.init(); - defer mock.deinit(); - inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { - const opcode = comptime getArithOpCode(tag, .rm); - const opc = [1]u8{opcode.opc}; - const opc_1 = [1]u8{opcode.opc - 1}; - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x48" ++ opc ++ "\x4D\x10", @tagName(tag) ++ " rcx, [rbp + 0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rcx, .reg2 = .rbp, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10000000 }, - }, "\x48" ++ opc ++ "\x8D\x00\x00\x00\x10", @tagName(tag) ++ " rcx, [rbp + 0x10000000]"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11b, .reg2 = .rbp, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x44" ++ opc_1 ++ "\x5D\x10", @tagName(tag) ++ " r11b, [rbp + 0x10]"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .rbp, .flags = 0b01 }).encode(), - .data = .{ .imm = 0x10000000 }, - }, "\x66\x44" ++ opc ++ "\x9D\x00\x00\x00\x10", @tagName(tag) ++ " r11w, [rbp + 0x10000000]"); - } -} - -test "ARITH_OP/MOV [dst_reg + 0], imm" { - var mock = Mock.init(); - defer mock.deinit(); - - const ModRmByte = struct { - inline fn get(tag: Mir.Inst.Tag, reg: u8) [1]u8 { - const modrm: u8 = getArithOpCode(tag, .mi).modrm_ext; - return .{(modrm << 3) + reg}; - } - }; - - inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { - const opcode = comptime getArithOpCode(tag, .mi); - const opc = [1]u8{opcode.opc}; - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x41" ++ opc ++ ModRmByte.get(tag, 3) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " dword ptr [r11 + 0], 0x10"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10000000 }, - }, opc ++ ModRmByte.get(tag, 0) ++ "\x00\x00\x00\x10", @tagName(tag) ++ " dword ptr [rax + 0], 0x10000000"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x1000 }, - }, opc ++ ModRmByte.get(tag, 0) ++ "\x00\x10\x00\x00", @tagName(tag) ++ " dword ptr [rax + 0], 0x1000"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, opc ++ ModRmByte.get(tag, 0) ++ "\x10\x00\x00\x00", @tagName(tag) ++ " dword ptr [rax + 0], 0x10"); - try mock.testEmitSingleFail(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .eax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "size mismatch: sizeof Register.eax != 8"); - } -} - -test "ARITH_OP/MOV [dst_reg + imm32], src_reg" { - var mock = Mock.init(); - defer mock.deinit(); - inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { - const opcode = comptime getArithOpCode(tag, .mr); - const opc = [1]u8{opcode.opc}; - const opc_1 = [1]u8{opcode.opc - 1}; - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x4c" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " qword ptr [rbp + 0x10], r11"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11d, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x44" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " dword ptr [rbp + 0x10], r11d"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11w, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x66\x44" ++ opc ++ "\x5d\x10", @tagName(tag) ++ " word ptr [rbp + 0x10], r11w"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rbp, .reg2 = .r11b, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x44" ++ opc_1 ++ "\x5d\x10", @tagName(tag) ++ " byte ptr [rbp + 0x10], r11b"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .rax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x49" ++ opc ++ "\x43\x10", @tagName(tag) ++ " qword ptr [r11 + 0x10], rax"); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11, .reg2 = .eax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "\x41" ++ opc ++ "\x43\x10", @tagName(tag) ++ " dword ptr [r11 + 0x10], eax"); - try mock.testEmitSingleFail(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11w, .reg2 = .ax, .flags = 0b10 }).encode(), - .data = .{ .imm = 0x10 }, - }, "size mismatch: sizeof Register.r11w != 8"); - } -} - -test "ARITH_OP/MOV [dst_reg + imm32], imm32" { - var mock = Mock.init(); - defer mock.deinit(); - - const ModRmByte = struct { - inline fn get(tag: Mir.Inst.Tag, disp: u2, reg: u8) [1]u8 { - const modrm: u8 = getArithOpCode(tag, .mi).modrm_ext; - return .{(@as(u8, disp) << 6) + (modrm << 3) + reg}; - } - }; - - inline for (&[_]Mir.Inst.Tag{ .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov }) |tag| { - const opcode = comptime getArithOpCode(tag, .mi); - const opc = [1]u8{opcode.opc}; - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10, - .operand = 0x20000000, - }); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x00\x00\x00\x20", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x20000000"); - } - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10, - .operand = 0x2000, - }); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x00\x20\x00\x00", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x2000"); - } - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10, - .operand = 0x20, - }); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .rbp, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, opc ++ ModRmByte.get(tag, 1, 5) ++ "\x10\x20\x00\x00\x00", @tagName(tag) ++ " dword ptr [rbp + 0x10], 0x20"); - } - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10, - .operand = 0x20000000, - }); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, "\x41" ++ opc ++ ModRmByte.get(tag, 1, 3) ++ "\x10\x00\x00\x00\x20", @tagName(tag) ++ " dword ptr [r11 + 0x10], 0x20000000"); - } - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10000000, - .operand = 0x20000000, - }); - try mock.testEmitSingleSuccess(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, "\x41" ++ opc ++ ModRmByte.get(tag, 2, 3) ++ "\x00\x00\x00\x10\x00\x00\x00\x20", @tagName(tag) ++ " dword ptr [r11 + 0x10], 0x20000000"); - } - { - const payload = try mock.addExtra(Mir.ImmPair{ - .dest_off = 0x10, - .operand = 0x20, - }); - try mock.testEmitSingleFail(.{ - .tag = tag, - .ops = (Mir.Ops{ .reg1 = .r11d, .flags = 0b11 }).encode(), - .data = .{ .payload = payload }, - }, "size mismatch: sizeof Register.r11d != 8"); - } - } +test "lower MR encoding" { + var code = TestEmitCode.init(); + defer code.deinit(); + try lowerToMrEnc(.mov, RegisterOrMemory.reg(.rax), .rbx, code.buffer()); + try expectEqualHexStrings("\x48\x89\xd8", code.emitted(), "mov rax, rbx"); + try lowerToMrEnc(.mov, RegisterOrMemory.mem(.rbp, -4), .r11, code.buffer()); + try expectEqualHexStrings("\x4c\x89\x5d\xfc", code.emitted(), "mov qword ptr [rbp - 4], r11"); + try lowerToMrEnc(.add, RegisterOrMemory.mem(null, 0x10000000), .r12b, code.buffer()); + try expectEqualHexStrings( + "\x44\x00\x24\x25\x00\x00\x00\x10", + code.emitted(), + "add byte ptr [ds:0x10000000], r12b", + ); + try lowerToMrEnc(.add, RegisterOrMemory.mem(null, 0x10000000), .r12d, code.buffer()); + try expectEqualHexStrings( + "\x44\x01\x24\x25\x00\x00\x00\x10", + code.emitted(), + "add dword ptr [ds:0x10000000], r12d", + ); + try lowerToMrEnc(.sub, RegisterOrMemory.mem(.r11, 0x10000000), .r12, code.buffer()); + try expectEqualHexStrings( + "\x4D\x29\xA3\x00\x00\x00\x10", + code.emitted(), + "sub qword ptr [r11 + 0x10000000], r12", + ); } diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index 51420b47e6..fbc4fd320b 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -380,14 +380,14 @@ pub const Ops = struct { } }; -pub fn extraData(mir_extra: []const u32, comptime T: type, index: usize) struct { data: T, end: usize } { +pub fn extraData(mir: Mir, comptime T: type, index: usize) struct { data: T, end: usize } { const fields = std.meta.fields(T); var i: usize = index; var result: T = undefined; inline for (fields) |field| { @field(result, field.name) = switch (field.field_type) { - u32 => mir_extra[i], - i32 => @bitCast(i32, mir_extra[i]), + u32 => mir.extra[i], + i32 => @bitCast(i32, mir.extra[i]), else => @compileError("bad field type"), }; i += 1; diff --git a/src/arch/x86_64/PrintMir.zig b/src/arch/x86_64/PrintMir.zig index 0c9b952c74..b69e871fc3 100644 --- a/src/arch/x86_64/PrintMir.zig +++ b/src/arch/x86_64/PrintMir.zig @@ -481,7 +481,7 @@ fn mirArith(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index, w: any 0b11 => { if (ops.reg2 == .none) { const payload = print.mir.instructions.items(.data)[inst].payload; - const imm_pair = Mir.extraData(print.mir.extra, Mir.ImmPair, payload).data; + const imm_pair = print.mir.extraData(Mir.ImmPair, payload).data; try w.print("[{s} + {d}], {d}", .{ @tagName(ops.reg1), imm_pair.dest_off, imm_pair.operand }); } try w.writeAll("TODO"); @@ -516,7 +516,7 @@ fn mirArithScaleImm(print: *const Print, tag: Mir.Inst.Tag, inst: Mir.Inst.Index const ops = Mir.Ops.decode(print.mir.instructions.items(.ops)[inst]); const scale = ops.flags; const payload = print.mir.instructions.items(.data)[inst].payload; - const imm_pair = Mir.extraData(print.mir.extra, Mir.ImmPair, payload).data; + const imm_pair = print.mir.extraData(Mir.ImmPair, payload).data; try w.print("{s} [{s} + {d}*rcx + {d}], {d}\n", .{ @tagName(tag), @tagName(ops.reg1), scale, imm_pair.dest_off, imm_pair.operand }); } @@ -528,7 +528,7 @@ fn mirMovabs(print: *const Print, inst: Mir.Inst.Index, w: anytype) !void { const is_64 = ops.reg1.size() == 64; const imm: i128 = if (is_64) blk: { const payload = print.mir.instructions.items(.data)[inst].payload; - const imm64 = Mir.extraData(print.mir.extra, Mir.Imm64, payload).data; + const imm64 = print.mir.extraData(Mir.Imm64, payload).data; break :blk imm64.decode(); } else print.mir.instructions.items(.data)[inst].imm; if (ops.flags == 0b00) { From b9a6f81d1ac0e04381b5872413f49ff54b872e04 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Dec 2021 18:53:20 +0100 Subject: [PATCH 06/19] stage2: add lowering fn for OI encoding Implement movabs using OI generic encoding. --- src/arch/x86_64/Emit.zig | 248 ++++++++++++++++++++++++++------------- 1 file changed, 164 insertions(+), 84 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 5742d82254..082e6d7cb0 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -541,61 +541,64 @@ const Encoding = enum { /// OP r64, r/m64 rm, + + /// OP r64, imm64 + oi, }; -const OpCode = struct { - opc: u8, - /// Only used if `Encoding == .mi`. - modrm_ext: u3, -}; - -inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) OpCode { +inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) u8 { switch (enc) { .mi => return switch (tag) { - .adc => .{ .opc = 0x81, .modrm_ext = 0x2 }, - .add => .{ .opc = 0x81, .modrm_ext = 0x0 }, - .sub => .{ .opc = 0x81, .modrm_ext = 0x5 }, - .xor => .{ .opc = 0x81, .modrm_ext = 0x6 }, - .@"and" => .{ .opc = 0x81, .modrm_ext = 0x4 }, - .@"or" => .{ .opc = 0x81, .modrm_ext = 0x1 }, - .sbb => .{ .opc = 0x81, .modrm_ext = 0x3 }, - .cmp => .{ .opc = 0x81, .modrm_ext = 0x7 }, - .mov => .{ .opc = 0xc7, .modrm_ext = 0x0 }, + .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp => 0x81, + .mov => 0xc7, else => unreachable, }, - .mr => { - const opc: u8 = switch (tag) { - .adc => 0x11, - .add => 0x01, - .sub => 0x29, - .xor => 0x31, - .@"and" => 0x21, - .@"or" => 0x09, - .sbb => 0x19, - .cmp => 0x39, - .mov => 0x89, - else => unreachable, - }; - return .{ .opc = opc, .modrm_ext = undefined }; + .mr => return switch (tag) { + .adc => 0x11, + .add => 0x01, + .sub => 0x29, + .xor => 0x31, + .@"and" => 0x21, + .@"or" => 0x09, + .sbb => 0x19, + .cmp => 0x39, + .mov => 0x89, + else => unreachable, }, - .rm => { - const opc: u8 = switch (tag) { - .adc => 0x13, - .add => 0x03, - .sub => 0x2b, - .xor => 0x33, - .@"and" => 0x23, - .@"or" => 0x0b, - .sbb => 0x1b, - .cmp => 0x3b, - .mov => 0x8b, - else => unreachable, - }; - return .{ .opc = opc, .modrm_ext = undefined }; + .rm => return switch (tag) { + .adc => 0x13, + .add => 0x03, + .sub => 0x2b, + .xor => 0x33, + .@"and" => 0x23, + .@"or" => 0x0b, + .sbb => 0x1b, + .cmp => 0x3b, + .mov => 0x8b, + else => unreachable, + }, + .oi => return switch (tag) { + .mov => 0xb8, + else => unreachable, }, } } +inline fn getMiModRmExt(tag: Mir.Inst.Tag) u3 { + return switch (tag) { + .adc => 0x2, + .add => 0x0, + .sub => 0x5, + .xor => 0x6, + .@"and" => 0x4, + .@"or" => 0x1, + .sbb => 0x3, + .cmp => 0x7, + .mov => 0x0, + else => unreachable, + }; +} + const ScaleIndexBase = struct { scale: u2, index_reg: ?Register, @@ -626,16 +629,56 @@ const RegisterOrMemory = union(enum) { } }; +fn lowerToOiEnc( + tag: Mir.Inst.Tag, + reg: Register, + imm: i64, + code: *std.ArrayList(u8), +) InnerError!void { + var opc = getOpCode(tag, .oi); + if (reg.size() != immOpSize(imm)) return error.EmitFail; + if (reg.size() == 8) { + opc -= 8; + } + const encoder = try Encoder.init(code, 10); + encoder.rex(.{ + .w = reg.size() == 64, + .b = reg.isExtended(), + }); + encoder.opcode_withReg(opc, reg.lowId()); + switch (reg.size()) { + 8 => { + const imm8 = try math.cast(i8, imm); + encoder.imm8(imm8); + }, + 16 => { + const imm16 = try math.cast(i16, imm); + encoder.imm16(imm16); + }, + 32 => { + const imm32 = try math.cast(i32, imm); + encoder.imm32(imm32); + }, + 64 => { + encoder.imm64(@bitCast(u64, imm)); + }, + else => unreachable, + } +} + fn lowerToMiEnc( tag: Mir.Inst.Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.ArrayList(u8), ) InnerError!void { - const opcode = getOpCode(tag, .mi); + var opc = getOpCode(tag, .mi); + const modrm_ext = getMiModRmExt(tag); switch (reg_or_mem) { .register => |dst_reg| { - const opc: u8 = if (dst_reg.size() == 8) opcode.opc - 1 else opcode.opc; + if (dst_reg.size() == 8) { + opc -= 1; + } const encoder = try Encoder.init(code, 7); if (dst_reg.size() == 16) { // 0x66 prefix switches to the non-default size; here we assume a switch from @@ -648,7 +691,7 @@ fn lowerToMiEnc( .b = dst_reg.isExtended(), }); encoder.opcode_1byte(opc); - encoder.modRm_direct(opcode.modrm_ext, dst_reg.lowId()); + encoder.modRm_direct(modrm_ext, dst_reg.lowId()); switch (dst_reg.size()) { 8 => { const imm8 = try math.cast(i8, imm); @@ -676,25 +719,25 @@ fn lowerToMiEnc( .w = false, .b = dst_reg.isExtended(), }); - encoder.opcode_1byte(opcode.opc); + encoder.opcode_1byte(opc); if (dst_mem.disp == 0) { - encoder.modRm_indirectDisp0(opcode.modrm_ext, dst_reg.lowId()); + encoder.modRm_indirectDisp0(modrm_ext, dst_reg.lowId()); } else if (immOpSize(dst_mem.disp) == 8) { - encoder.modRm_indirectDisp8(opcode.modrm_ext, dst_reg.lowId()); + encoder.modRm_indirectDisp8(modrm_ext, dst_reg.lowId()); encoder.disp8(@intCast(i8, dst_mem.disp)); } else { if (dst_reg.lowId() == 4) { - encoder.modRm_SIBDisp32(opcode.modrm_ext); + encoder.modRm_SIBDisp32(modrm_ext); encoder.sib_baseDisp32(dst_reg.lowId()); encoder.disp32(dst_mem.disp); } else { - encoder.modRm_indirectDisp32(opcode.modrm_ext, dst_reg.lowId()); + encoder.modRm_indirectDisp32(modrm_ext, dst_reg.lowId()); encoder.disp32(dst_mem.disp); } } } else { - encoder.opcode_1byte(opcode.opc); - encoder.modRm_SIBDisp0(opcode.modrm_ext); + encoder.opcode_1byte(opc); + encoder.modRm_SIBDisp0(modrm_ext); encoder.sib_disp32(); encoder.disp32(dst_mem.disp); } @@ -709,8 +752,10 @@ fn lowerToRmEnc( reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8), ) InnerError!void { - const opcode = getOpCode(tag, .rm); - const opc: u8 = if (reg.size() == 8) opcode.opc - 1 else opcode.opc; + var opc = getOpCode(tag, .rm); + if (reg.size() == 8) { + opc -= 1; + } switch (reg_or_mem) { .register => |src_reg| { if (reg.size() != src_reg.size()) return error.EmitFail; @@ -779,8 +824,10 @@ fn lowerToMrEnc( // * reg is 32bit - dword ptr // * reg is 16bit - word ptr // * reg is 8bit - byte ptr - const opcode = getOpCode(tag, .mr); - const opc: u8 = if (reg.size() == 8) opcode.opc - 1 else opcode.opc; + var opc = getOpCode(tag, .mr); + if (reg.size() == 8) { + opc -= 1; + } switch (reg_or_mem) { .register => |dst_reg| { 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 } } -fn immOpSize(imm: i32) u8 { +fn immOpSize(imm: i64) u8 { blk: { _ = math.cast(i8, imm) catch break :blk; return 8; @@ -899,15 +946,21 @@ fn immOpSize(imm: i32) u8 { _ = math.cast(i16, imm) catch break :blk; return 16; } - return 32; + blk: { + _ = math.cast(i32, imm) catch break :blk; + return 32; + } + return 64; } fn mirArithScaleSrc(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; // OP reg1, [reg2 + scale*rcx + imm32] - const opcode = getOpCode(tag, .rm); - const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; + var opc = getOpCode(tag, .rm); + if (ops.reg1.size() == 8) { + opc -= 1; + } const imm = emit.mir.instructions.items(.data)[inst].imm; const encoder = try Encoder.init(emit.code, 8); encoder.rex(.{ @@ -934,15 +987,18 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE if (ops.reg2 == .none) { // OP [reg1 + scale*rax + 0], imm32 - const opcode = getOpCode(tag, .mi); - const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; + var opc = getOpCode(tag, .mi); + const modrm_ext = getMiModRmExt(tag); + if (ops.reg1.size() == 8) { + opc -= 1; + } const encoder = try Encoder.init(emit.code, 8); encoder.rex(.{ .w = ops.reg1.size() == 64, .b = ops.reg1.isExtended(), }); encoder.opcode_1byte(opc); - encoder.modRm_SIBDisp0(opcode.modrm_ext); + encoder.modRm_SIBDisp0(modrm_ext); encoder.sib_scaleIndexBase(scale, Register.rax.lowId(), ops.reg1.lowId()); if (imm <= math.maxInt(i8)) { encoder.imm8(@intCast(i8, imm)); @@ -955,8 +1011,10 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE } // OP [reg1 + scale*rax + imm32], reg2 - const opcode = getOpCode(tag, .mr); - const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; + var opc = getOpCode(tag, .mr); + if (ops.reg1.size() == 8) { + opc -= 1; + } const encoder = try Encoder.init(emit.code, 8); encoder.rex(.{ .w = ops.reg1.size() == 64, @@ -980,8 +1038,11 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - const opcode = getOpCode(tag, .mi); - const opc = if (ops.reg1.size() == 8) opcode.opc - 1 else opcode.opc; + var opc = getOpCode(tag, .mi); + if (ops.reg1.size() == 8) { + opc -= 1; + } + const modrm_ext = getMiModRmExt(tag); const encoder = try Encoder.init(emit.code, 2); encoder.rex(.{ .w = ops.reg1.size() == 64, @@ -989,11 +1050,11 @@ fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE }); encoder.opcode_1byte(opc); if (imm_pair.dest_off <= math.maxInt(i8)) { - encoder.modRm_SIBDisp8(opcode.modrm_ext); + encoder.modRm_SIBDisp8(modrm_ext); encoder.sib_scaleIndexBaseDisp8(scale, Register.rax.lowId(), ops.reg1.lowId()); encoder.disp8(@intCast(i8, imm_pair.dest_off)); } else { - encoder.modRm_SIBDisp32(opcode.modrm_ext); + encoder.modRm_SIBDisp32(modrm_ext); encoder.sib_scaleIndexBaseDisp32(scale, Register.rax.lowId(), ops.reg1.lowId()); encoder.disp32(imm_pair.dest_off); } @@ -1005,21 +1066,19 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { assert(tag == .movabs); const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); + if (ops.flags == 0b00) { + // movabs reg, imm64 + // OI + const imm: i64 = if (ops.reg1.size() == 64) blk: { + const payload = emit.mir.instructions.items(.data)[inst].payload; + const imm = emit.mir.extraData(Mir.Imm64, payload).data; + break :blk @bitCast(i64, imm.decode()); + } else emit.mir.instructions.items(.data)[inst].imm; + return lowerToOiEnc(.mov, ops.reg1, imm, emit.code); + } + const encoder = try Encoder.init(emit.code, 10); const is_64 = blk: { - if (ops.flags == 0b00) { - // movabs reg, imm64 - const opc: u8 = if (ops.reg1.size() == 8) 0xb0 else 0xb8; - if (ops.reg1.size() == 64) { - encoder.rex(.{ - .w = true, - .b = ops.reg1.isExtended(), - }); - encoder.opcode_withReg(opc, ops.reg1.lowId()); - break :blk true; - } - break :blk false; - } if (ops.reg1 == .none) { // movabs moffs64, rax const opc: u8 = if (ops.reg2.size() == 8) 0xa2 else 0xa3; @@ -1486,3 +1545,24 @@ test "lower MR encoding" { "sub qword ptr [r11 + 0x10000000], r12", ); } + +test "lower OI encoding" { + var code = TestEmitCode.init(); + defer code.deinit(); + try lowerToOiEnc(.mov, .rax, 0x1000000000000000, code.buffer()); + try expectEqualHexStrings( + "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x10", + code.emitted(), + "movabs rax, 0x1000000000000000", + ); + try lowerToOiEnc(.mov, .r11, 0x1000000000000000, code.buffer()); + try expectEqualHexStrings( + "\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x10", + code.emitted(), + "movabs r11, 0x1000000000000000", + ); + try lowerToOiEnc(.mov, .r11d, 0x10000000, code.buffer()); + try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", code.emitted(), "mov r11d, 0x10000000"); + try lowerToOiEnc(.mov, .r11b, 0x10, code.buffer()); + try expectEqualHexStrings("\x41\xB3\x10", code.emitted(), "mov r11b, 0x10"); +} From b40e5adf54de878c2745bcf6e266239b7f5c40b6 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Dec 2021 22:36:56 +0100 Subject: [PATCH 07/19] stage2: add lowering for FD/TD encodings --- src/arch/x86_64/Emit.zig | 147 ++++++++++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 40 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 082e6d7cb0..3f20e9704d 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -544,6 +544,12 @@ const Encoding = enum { /// OP r64, imm64 oi, + + /// OP al/ax/eax/rax, moffs + fd, + + /// OP moffs, al/ax/eax/rax + td, }; inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) u8 { @@ -581,6 +587,14 @@ inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) u8 { .mov => 0xb8, else => unreachable, }, + .fd => return switch (tag) { + .mov => 0xa1, + else => unreachable, + }, + .td => return switch (tag) { + .mov => 0xa3, + else => unreachable, + }, } } @@ -629,6 +643,65 @@ const RegisterOrMemory = union(enum) { } }; +fn lowerToTdEnc( + tag: Mir.Inst.Tag, + moffs: i64, + reg: Register, + code: *std.ArrayList(u8), +) InnerError!void { + return lowerToTdFdEnc(tag, reg, moffs, code, true); +} + +fn lowerToFdEnc( + tag: Mir.Inst.Tag, + reg: Register, + moffs: i64, + code: *std.ArrayList(u8), +) InnerError!void { + return lowerToTdFdEnc(tag, reg, moffs, code, false); +} + +fn lowerToTdFdEnc( + tag: Mir.Inst.Tag, + reg: Register, + moffs: i64, + code: *std.ArrayList(u8), + td: bool, +) InnerError!void { + if (reg.lowId() != Register.rax.lowId()) return error.EmitFail; + if (reg.size() != immOpSize(moffs)) return error.EmitFail; + var opc = if (td) getOpCode(tag, .td) else getOpCode(tag, .fd); + if (reg.size() == 8) { + opc -= 1; + } + const encoder = try Encoder.init(code, 10); + if (reg.size() == 16) { + encoder.opcode_1byte(0x66); + } + encoder.rex(.{ + .w = reg.size() == 64, + }); + encoder.opcode_1byte(opc); + switch (reg.size()) { + 8 => { + const moffs8 = try math.cast(i8, moffs); + encoder.imm8(moffs8); + }, + 16 => { + const moffs16 = try math.cast(i16, moffs); + encoder.imm16(moffs16); + }, + 32 => { + const moffs32 = try math.cast(i32, moffs); + encoder.imm32(moffs32); + }, + 64 => { + encoder.imm64(@bitCast(u64, moffs)); + }, + else => unreachable, + } +} + fn lowerToOiEnc( tag: Mir.Inst.Tag, reg: Register, @@ -641,6 +714,9 @@ fn lowerToOiEnc( opc -= 8; } const encoder = try Encoder.init(code, 10); + if (reg.size() == 16) { + encoder.opcode_1byte(0x66); + } encoder.rex(.{ .w = reg.size() == 64, .b = reg.isExtended(), @@ -1065,52 +1141,24 @@ fn mirMovabs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .movabs); const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); - + const imm: i64 = if (ops.reg1.size() == 64) blk: { + const payload = emit.mir.instructions.items(.data)[inst].payload; + const imm = emit.mir.extraData(Mir.Imm64, payload).data; + break :blk @bitCast(i64, imm.decode()); + } else emit.mir.instructions.items(.data)[inst].imm; if (ops.flags == 0b00) { // movabs reg, imm64 // OI - const imm: i64 = if (ops.reg1.size() == 64) blk: { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data; - break :blk @bitCast(i64, imm.decode()); - } else emit.mir.instructions.items(.data)[inst].imm; return lowerToOiEnc(.mov, ops.reg1, imm, emit.code); } - - const encoder = try Encoder.init(emit.code, 10); - const is_64 = blk: { - if (ops.reg1 == .none) { - // movabs moffs64, rax - const opc: u8 = if (ops.reg2.size() == 8) 0xa2 else 0xa3; - encoder.rex(.{ - .w = ops.reg2.size() == 64, - }); - encoder.opcode_1byte(opc); - break :blk ops.reg2.size() == 64; - } else { - // movabs rax, moffs64 - const opc: u8 = if (ops.reg2.size() == 8) 0xa0 else 0xa1; - encoder.rex(.{ - .w = ops.reg1.size() == 64, - }); - encoder.opcode_1byte(opc); - break :blk ops.reg1.size() == 64; - } - }; - - if (is_64) { - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm64 = emit.mir.extraData(Mir.Imm64, payload).data; - encoder.imm64(imm64.decode()); + if (ops.reg1 == .none) { + // movabs moffs64, rax + // TD + return lowerToTdEnc(.mov, imm, ops.reg2, emit.code); } else { - const imm = emit.mir.instructions.items(.data)[inst].imm; - if (imm <= math.maxInt(i8)) { - encoder.imm8(@intCast(i8, imm)); - } else if (imm <= math.maxInt(i16)) { - encoder.imm16(@intCast(i16, imm)); - } else { - encoder.imm32(imm); - } + // movabs rax, moffs64 + // FD + return lowerToFdEnc(.mov, ops.reg1, imm, emit.code); } } @@ -1563,6 +1611,25 @@ test "lower OI encoding" { ); try lowerToOiEnc(.mov, .r11d, 0x10000000, code.buffer()); try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", code.emitted(), "mov r11d, 0x10000000"); + try lowerToOiEnc(.mov, .r11w, 0x1000, code.buffer()); + try expectEqualHexStrings("\x66\x41\xBB\x00\x10", code.emitted(), "mov r11w, 0x1000"); try lowerToOiEnc(.mov, .r11b, 0x10, code.buffer()); try expectEqualHexStrings("\x41\xB3\x10", code.emitted(), "mov r11b, 0x10"); } + +test "lower FD/TD encoding" { + var code = TestEmitCode.init(); + defer code.deinit(); + try lowerToFdEnc(.mov, .rax, 0x1000000000000000, code.buffer()); + try expectEqualHexStrings( + "\x48\xa1\x00\x00\x00\x00\x00\x00\x00\x10", + code.emitted(), + "mov rax, ds:0x1000000000000000", + ); + try lowerToFdEnc(.mov, .eax, 0x10000000, code.buffer()); + try expectEqualHexStrings("\xa1\x00\x00\x00\x10", code.emitted(), "mov eax, ds:0x10000000"); + try lowerToFdEnc(.mov, .ax, 0x1000, code.buffer()); + try expectEqualHexStrings("\x66\xa1\x00\x10", code.emitted(), "mov ax, ds:0x1000"); + try lowerToFdEnc(.mov, .al, 0x10, code.buffer()); + try expectEqualHexStrings("\xa0\x10", code.emitted(), "mov al, ds:0x10"); +} From 362eccf539db6bb89e222916f3c4a45b0eb2bece Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 22 Dec 2021 23:07:46 +0100 Subject: [PATCH 08/19] stage2: handle RIP relative addressing in MI, RM and MR --- src/arch/x86_64/Emit.zig | 94 ++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 3f20e9704d..600ec4520f 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -581,6 +581,7 @@ inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) u8 { .sbb => 0x1b, .cmp => 0x3b, .mov => 0x8b, + .lea => 0x8d, else => unreachable, }, .oi => return switch (tag) { @@ -621,6 +622,7 @@ const ScaleIndexBase = struct { const Memory = struct { reg: ?Register, + rip: bool = false, disp: i32, sib: ?ScaleIndexBase = null, }; @@ -641,6 +643,16 @@ const RegisterOrMemory = union(enum) { }, }; } + + fn rip(disp: i32) RegisterOrMemory { + return .{ + .memory = .{ + .reg = null, + .rip = true, + .disp = disp, + }, + }; + } }; fn lowerToTdEnc( @@ -813,8 +825,12 @@ fn lowerToMiEnc( } } else { encoder.opcode_1byte(opc); - encoder.modRm_SIBDisp0(modrm_ext); - encoder.sib_disp32(); + if (dst_mem.rip) { + encoder.modRm_RIPDisp32(modrm_ext); + } else { + encoder.modRm_SIBDisp0(modrm_ext); + encoder.sib_disp32(); + } encoder.disp32(dst_mem.disp); } encoder.imm32(imm); @@ -880,8 +896,12 @@ fn lowerToRmEnc( .r = reg.isExtended(), }); encoder.opcode_1byte(opc); - encoder.modRm_SIBDisp0(reg.lowId()); - encoder.sib_disp32(); + if (src_mem.rip) { + encoder.modRm_RIPDisp32(reg.lowId()); + } else { + encoder.modRm_SIBDisp0(reg.lowId()); + encoder.sib_disp32(); + } encoder.disp32(src_mem.disp); } }, @@ -950,8 +970,12 @@ fn lowerToMrEnc( .r = reg.isExtended(), }); encoder.opcode_1byte(opc); - encoder.modRm_SIBDisp0(reg.lowId()); - encoder.sib_disp32(); + if (dst_mem.rip) { + encoder.modRm_RIPDisp32(reg.lowId()); + } else { + encoder.modRm_SIBDisp0(reg.lowId()); + encoder.sib_disp32(); + } encoder.disp32(dst_mem.disp); } }, @@ -1206,37 +1230,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); assert(ops.flags == 0b01); const imm = emit.mir.instructions.items(.data)[inst].imm; - - if (imm == 0) { - const encoder = try Encoder.init(emit.code, 3); - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), - }); - encoder.opcode_1byte(0x8d); - encoder.modRm_indirectDisp0(ops.reg1.lowId(), ops.reg2.lowId()); - } else if (imm <= math.maxInt(i8)) { - const encoder = try Encoder.init(emit.code, 4); - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), - }); - encoder.opcode_1byte(0x8d); - encoder.modRm_indirectDisp8(ops.reg1.lowId(), ops.reg2.lowId()); - encoder.disp8(@intCast(i8, imm)); - } else { - const encoder = try Encoder.init(emit.code, 7); - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), - }); - encoder.opcode_1byte(0x8d); - encoder.modRm_indirectDisp32(ops.reg1.lowId(), ops.reg2.lowId()); - encoder.disp32(imm); - } + return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(ops.reg2, imm), emit.code); } fn mirLeaRip(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -1244,26 +1238,22 @@ fn mirLeaRip(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { assert(tag == .lea_rip); const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const start_offset = emit.code.items.len; - const encoder = try Encoder.init(emit.code, 7); - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(0x8d); - encoder.modRm_RIPDisp32(ops.reg1.lowId()); + try lowerToRmEnc(.lea, ops.reg1, RegisterOrMemory.rip(0), emit.code); const end_offset = emit.code.items.len; if (@truncate(u1, ops.flags) == 0b0) { + // Backpatch the displacement + // TODO figure out if this can be simplified const payload = emit.mir.instructions.items(.data)[inst].payload; const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode(); - encoder.disp32(@intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset + 4))); + const disp = @intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset)); + mem.writeIntLittle(i32, emit.code.items[end_offset - 4 ..][0..4], disp); } else { const got_entry = emit.mir.instructions.items(.data)[inst].got_entry; - encoder.disp32(0); if (emit.bin_file.cast(link.File.MachO)) |macho_file| { // TODO I think the reloc might be in the wrong place. const decl = macho_file.active_decl.?; try decl.link.macho.relocs.append(emit.bin_file.allocator, .{ - .offset = @intCast(u32, end_offset), + .offset = @intCast(u32, end_offset - 4), .target = .{ .local = got_entry }, .addend = 0, .subtractor = null, @@ -1530,6 +1520,12 @@ test "lower MI encoding" { code.emitted(), "and dword ptr [r12 + 0x10000000], 0x10", ); + try lowerToMiEnc(.mov, RegisterOrMemory.rip(0x10), 0x10, code.buffer()); + try expectEqualHexStrings( + "\xC7\x05\x10\x00\x00\x00\x10\x00\x00\x00", + code.emitted(), + "mov [rip + 0x10], 0x10", + ); } test "lower RM encoding" { @@ -1565,6 +1561,8 @@ test "lower RM encoding" { ); try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.rbp, -4), code.buffer()); try expectEqualHexStrings("\x48\x8B\x45\xFC", code.emitted(), "mov rax, qword ptr [rbp - 4]"); + try lowerToRmEnc(.lea, .rax, RegisterOrMemory.rip(0x10), code.buffer()); + try expectEqualHexStrings("\x48\x8D\x05\x10\x00\x00\x00", code.emitted(), "lea rax, [rip + 0x10]"); } test "lower MR encoding" { @@ -1592,6 +1590,8 @@ test "lower MR encoding" { code.emitted(), "sub qword ptr [r11 + 0x10000000], r12", ); + try lowerToMrEnc(.mov, RegisterOrMemory.rip(0x10), .r12, code.buffer()); + try expectEqualHexStrings("\x4C\x89\x25\x10\x00\x00\x00", code.emitted(), "mov qword ptr [rip + 0x10], r12"); } test "lower OI encoding" { From 1167e248ef8ab94aac3c4218574a09ba9346d34f Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Dec 2021 00:36:44 +0100 Subject: [PATCH 09/19] stage2: add lowering of D encoding Example of such encoding includes a near/far call and jmp instructions. --- src/arch/x86_64/Emit.zig | 222 ++++++++++++++++++++------------------- 1 file changed, 115 insertions(+), 107 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 600ec4520f..0c9027b260 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -66,40 +66,45 @@ pub fn emitMir(emit: *Emit) InnerError!void { const inst = @intCast(u32, index); try emit.code_offset_mapping.putNoClobber(emit.bin_file.allocator, inst, emit.code.items.len); switch (tag) { - .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp, .mov => try emit.mirArith(tag, inst), + .adc => try emit.mirArith(.adc, inst), + .add => try emit.mirArith(.add, inst), + .sub => try emit.mirArith(.sub, inst), + .xor => try emit.mirArith(.xor, inst), + .@"and" => try emit.mirArith(.@"and", inst), + .@"or" => try emit.mirArith(.@"or", inst), + .sbb => try emit.mirArith(.sbb, inst), + .cmp => try emit.mirArith(.cmp, inst), + .mov => try emit.mirArith(.mov, inst), - .adc_scale_src, - .add_scale_src, - .sub_scale_src, - .xor_scale_src, - .and_scale_src, - .or_scale_src, - .sbb_scale_src, - .cmp_scale_src, - .mov_scale_src, - => try emit.mirArithScaleSrc(tag, inst), + .adc_scale_src => try emit.mirArithScaleSrc(.adc, inst), + .add_scale_src => try emit.mirArithScaleSrc(.add, inst), + .sub_scale_src => try emit.mirArithScaleSrc(.sub, inst), + .xor_scale_src => try emit.mirArithScaleSrc(.xor, inst), + .and_scale_src => try emit.mirArithScaleSrc(.@"and", inst), + .or_scale_src => try emit.mirArithScaleSrc(.@"or", inst), + .sbb_scale_src => try emit.mirArithScaleSrc(.sbb, inst), + .cmp_scale_src => try emit.mirArithScaleSrc(.cmp, inst), + .mov_scale_src => try emit.mirArithScaleSrc(.mov, inst), - .adc_scale_dst, - .add_scale_dst, - .sub_scale_dst, - .xor_scale_dst, - .and_scale_dst, - .or_scale_dst, - .sbb_scale_dst, - .cmp_scale_dst, - .mov_scale_dst, - => try emit.mirArithScaleDst(tag, inst), + .adc_scale_dst => try emit.mirArithScaleDst(.adc, inst), + .add_scale_dst => try emit.mirArithScaleDst(.add, inst), + .sub_scale_dst => try emit.mirArithScaleDst(.sub, inst), + .xor_scale_dst => try emit.mirArithScaleDst(.xor, inst), + .and_scale_dst => try emit.mirArithScaleDst(.@"and", inst), + .or_scale_dst => try emit.mirArithScaleDst(.@"or", inst), + .sbb_scale_dst => try emit.mirArithScaleDst(.sbb, inst), + .cmp_scale_dst => try emit.mirArithScaleDst(.cmp, inst), + .mov_scale_dst => try emit.mirArithScaleDst(.mov, inst), - .adc_scale_imm, - .add_scale_imm, - .sub_scale_imm, - .xor_scale_imm, - .and_scale_imm, - .or_scale_imm, - .sbb_scale_imm, - .cmp_scale_imm, - .mov_scale_imm, - => try emit.mirArithScaleImm(tag, inst), + .adc_scale_imm => try emit.mirArithScaleImm(.adc, inst), + .add_scale_imm => try emit.mirArithScaleImm(.add, inst), + .sub_scale_imm => try emit.mirArithScaleImm(.sub, inst), + .xor_scale_imm => try emit.mirArithScaleImm(.xor, inst), + .and_scale_imm => try emit.mirArithScaleImm(.@"and", inst), + .or_scale_imm => try emit.mirArithScaleImm(.@"or", inst), + .sbb_scale_imm => try emit.mirArithScaleImm(.sbb, inst), + .cmp_scale_imm => try emit.mirArithScaleImm(.cmp, inst), + .mov_scale_imm => try emit.mirArithScaleImm(.mov, inst), .movabs => try emit.mirMovabs(inst), @@ -110,7 +115,8 @@ pub fn emitMir(emit: *Emit) InnerError!void { .push, .pop => try emit.mirPushPop(tag, inst), - .jmp, .call => try emit.mirJmpCall(tag, inst), + .jmp => try emit.mirJmpCall(.jmp_near, inst), + .call => try emit.mirJmpCall(.call_near, inst), .cond_jmp_greater_less, .cond_jmp_above_below, @@ -283,31 +289,24 @@ fn mirPushPopRegsFromCalleePreservedRegs(emit: *Emit, tag: Mir.Inst.Tag, inst: M } } -fn mirJmpCall(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const flag = @truncate(u1, ops.flags); if (flag == 0) { const target = emit.mir.instructions.items(.data)[inst].inst; - const opc: u8 = switch (tag) { - .jmp => 0xe9, - .call => 0xe8, - else => unreachable, - }; const source = emit.code.items.len; - const encoder = try Encoder.init(emit.code, 5); - encoder.opcode_1byte(opc); + try lowerToDEnc(tag, 0, emit.code); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, .target = target, - .offset = emit.code.items.len, + .offset = emit.code.items.len - 4, .length = 5, }); - encoder.imm32(0x0); return; } const modrm_ext: u3 = switch (tag) { - .jmp => 0x4, - .call => 0x2, + .jmp_near => 0x4, + .call_near => 0x2, else => unreachable, }; if (ops.reg1 == .none) { @@ -532,7 +531,28 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { } } +const Tag = enum { + adc, + add, + sub, + xor, + @"and", + @"or", + sbb, + cmp, + mov, + lea, + jmp_near, + call_near, +}; + const Encoding = enum { + /// OP rel32 + d, + + /// OP r/m64 + m, + /// OP r/m64, imm32 mi, @@ -552,12 +572,21 @@ const Encoding = enum { td, }; -inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) u8 { +inline fn getOpCode(tag: Tag, enc: Encoding) ?u8 { switch (enc) { + .d => return switch (tag) { + .jmp_near => 0xe9, + .call_near => 0xe8, + else => null, + }, + .m => return switch (tag) { + .jmp_near, .call_near => 0xff, + else => null, + }, .mi => return switch (tag) { .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp => 0x81, .mov => 0xc7, - else => unreachable, + else => null, }, .mr => return switch (tag) { .adc => 0x11, @@ -569,7 +598,7 @@ inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) u8 { .sbb => 0x19, .cmp => 0x39, .mov => 0x89, - else => unreachable, + else => null, }, .rm => return switch (tag) { .adc => 0x13, @@ -582,24 +611,24 @@ inline fn getOpCode(tag: Mir.Inst.Tag, enc: Encoding) u8 { .cmp => 0x3b, .mov => 0x8b, .lea => 0x8d, - else => unreachable, + else => null, }, .oi => return switch (tag) { .mov => 0xb8, - else => unreachable, + else => null, }, .fd => return switch (tag) { .mov => 0xa1, - else => unreachable, + else => null, }, .td => return switch (tag) { .mov => 0xa3, - else => unreachable, + else => null, }, } } -inline fn getMiModRmExt(tag: Mir.Inst.Tag) u3 { +inline fn getModRmExt(tag: Tag) u3 { return switch (tag) { .adc => 0x2, .add => 0x0, @@ -610,6 +639,7 @@ inline fn getMiModRmExt(tag: Mir.Inst.Tag) u3 { .sbb => 0x3, .cmp => 0x7, .mov => 0x0, + .call_near => 0x2, else => unreachable, }; } @@ -655,34 +685,25 @@ const RegisterOrMemory = union(enum) { } }; -fn lowerToTdEnc( - tag: Mir.Inst.Tag, - moffs: i64, - reg: Register, - code: *std.ArrayList(u8), -) InnerError!void { +fn lowerToDEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) InnerError!void { + const opc = getOpCode(tag, .d).?; + const encoder = try Encoder.init(code, 5); + encoder.opcode_1byte(opc); + encoder.imm32(imm); +} + +fn lowerToTdEnc(tag: Tag, moffs: i64, reg: Register, code: *std.ArrayList(u8)) InnerError!void { return lowerToTdFdEnc(tag, reg, moffs, code, true); } -fn lowerToFdEnc( - tag: Mir.Inst.Tag, - reg: Register, - moffs: i64, - code: *std.ArrayList(u8), -) InnerError!void { +fn lowerToFdEnc(tag: Tag, reg: Register, moffs: i64, code: *std.ArrayList(u8)) InnerError!void { return lowerToTdFdEnc(tag, reg, moffs, code, false); } -fn lowerToTdFdEnc( - tag: Mir.Inst.Tag, - reg: Register, - moffs: i64, - code: *std.ArrayList(u8), - td: bool, -) InnerError!void { +fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: i64, code: *std.ArrayList(u8), td: bool) InnerError!void { if (reg.lowId() != Register.rax.lowId()) return error.EmitFail; if (reg.size() != immOpSize(moffs)) return error.EmitFail; - var opc = if (td) getOpCode(tag, .td) else getOpCode(tag, .fd); + var opc = if (td) getOpCode(tag, .td).? else getOpCode(tag, .fd).?; if (reg.size() == 8) { opc -= 1; } @@ -714,13 +735,8 @@ fn lowerToTdFdEnc( } } -fn lowerToOiEnc( - tag: Mir.Inst.Tag, - reg: Register, - imm: i64, - code: *std.ArrayList(u8), -) InnerError!void { - var opc = getOpCode(tag, .oi); +fn lowerToOiEnc(tag: Tag, reg: Register, imm: i64, code: *std.ArrayList(u8)) InnerError!void { + var opc = getOpCode(tag, .oi).?; if (reg.size() != immOpSize(imm)) return error.EmitFail; if (reg.size() == 8) { opc -= 8; @@ -754,14 +770,9 @@ fn lowerToOiEnc( } } -fn lowerToMiEnc( - tag: Mir.Inst.Tag, - reg_or_mem: RegisterOrMemory, - imm: i32, - code: *std.ArrayList(u8), -) InnerError!void { - var opc = getOpCode(tag, .mi); - const modrm_ext = getMiModRmExt(tag); +fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.ArrayList(u8)) InnerError!void { + var opc = getOpCode(tag, .mi).?; + const modrm_ext = getModRmExt(tag); switch (reg_or_mem) { .register => |dst_reg| { if (dst_reg.size() == 8) { @@ -839,12 +850,12 @@ fn lowerToMiEnc( } fn lowerToRmEnc( - tag: Mir.Inst.Tag, + tag: Tag, reg: Register, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8), ) InnerError!void { - var opc = getOpCode(tag, .rm); + var opc = getOpCode(tag, .rm).?; if (reg.size() == 8) { opc -= 1; } @@ -909,7 +920,7 @@ fn lowerToRmEnc( } fn lowerToMrEnc( - tag: Mir.Inst.Tag, + tag: Tag, reg_or_mem: RegisterOrMemory, reg: Register, code: *std.ArrayList(u8), @@ -920,7 +931,7 @@ fn lowerToMrEnc( // * reg is 32bit - dword ptr // * reg is 16bit - word ptr // * reg is 8bit - byte ptr - var opc = getOpCode(tag, .mr); + var opc = getOpCode(tag, .mr).?; if (reg.size() == 8) { opc -= 1; } @@ -982,7 +993,7 @@ fn lowerToMrEnc( } } -fn mirArith(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); switch (ops.flags) { 0b00 => { @@ -1053,11 +1064,11 @@ fn immOpSize(imm: i64) u8 { return 64; } -fn mirArithScaleSrc(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithScaleSrc(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; // OP reg1, [reg2 + scale*rcx + imm32] - var opc = getOpCode(tag, .rm); + var opc = getOpCode(tag, .rm).?; if (ops.reg1.size() == 8) { opc -= 1; } @@ -1080,15 +1091,15 @@ fn mirArithScaleSrc(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE } } -fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; const imm = emit.mir.instructions.items(.data)[inst].imm; if (ops.reg2 == .none) { // OP [reg1 + scale*rax + 0], imm32 - var opc = getOpCode(tag, .mi); - const modrm_ext = getMiModRmExt(tag); + var opc = getOpCode(tag, .mi).?; + const modrm_ext = getModRmExt(tag); if (ops.reg1.size() == 8) { opc -= 1; } @@ -1111,7 +1122,7 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE } // OP [reg1 + scale*rax + imm32], reg2 - var opc = getOpCode(tag, .mr); + var opc = getOpCode(tag, .mr).?; if (ops.reg1.size() == 8) { opc -= 1; } @@ -1133,16 +1144,16 @@ fn mirArithScaleDst(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerE } } -fn mirArithScaleImm(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirArithScaleImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - var opc = getOpCode(tag, .mi); + var opc = getOpCode(tag, .mi).?; if (ops.reg1.size() == 8) { opc -= 1; } - const modrm_ext = getMiModRmExt(tag); + const modrm_ext = getModRmExt(tag); const encoder = try Encoder.init(emit.code, 2); encoder.rex(.{ .w = ops.reg1.size() == 64, @@ -1230,7 +1241,7 @@ fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); assert(ops.flags == 0b01); const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(ops.reg2, imm), emit.code); + return lowerToRmEnc(.lea, ops.reg1, RegisterOrMemory.mem(ops.reg2, imm), emit.code); } fn mirLeaRip(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -1272,12 +1283,9 @@ fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { assert(tag == .call_extern); const n_strx = emit.mir.instructions.items(.data)[inst].extern_fn; const offset = blk: { - const offset = @intCast(u32, emit.code.items.len + 1); // callq - const encoder = try Encoder.init(emit.code, 5); - encoder.opcode_1byte(0xe8); - encoder.imm32(0x0); - break :blk offset; + try lowerToDEnc(.call_near, 0, emit.code); + break :blk @intCast(u32, emit.code.items.len) - 4; }; if (emit.bin_file.cast(link.File.MachO)) |macho_file| { // Add relocation to the decl. From 9078cb0197e09de701fbc9d5941358b7910f38b2 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Dec 2021 01:22:07 +0100 Subject: [PATCH 10/19] stage2: add lowering of M encoding Examples include jmp / call near with memory or register operand like `jmp [rax]`, or even RIP-relative `call [rip + 0x10]`. --- src/arch/x86_64/Emit.zig | 178 ++++++++++++++++++++++++++++++--------- 1 file changed, 138 insertions(+), 40 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 0c9027b260..3f3c696dfb 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -304,25 +304,13 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { }); return; } - const modrm_ext: u3 = switch (tag) { - .jmp_near => 0x4, - .call_near => 0x2, - else => unreachable, - }; if (ops.reg1 == .none) { // JMP/CALL [imm] const imm = emit.mir.instructions.items(.data)[inst].imm; - const encoder = try Encoder.init(emit.code, 7); - encoder.opcode_1byte(0xff); - encoder.modRm_SIBDisp0(modrm_ext); - encoder.sib_disp32(); - encoder.imm32(imm); - return; + return lowerToMEnc(tag, RegisterOrMemory.mem(null, imm), emit.code); } // JMP/CALL reg - const encoder = try Encoder.init(emit.code, 2); - encoder.opcode_1byte(0xff); - encoder.modRm_direct(modrm_ext, ops.reg1.lowId()); + return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); } const CondType = enum { @@ -628,7 +616,7 @@ inline fn getOpCode(tag: Tag, enc: Encoding) ?u8 { } } -inline fn getModRmExt(tag: Tag) u3 { +inline fn getModRmExt(tag: Tag) ?u3 { return switch (tag) { .adc => 0x2, .add => 0x0, @@ -639,8 +627,9 @@ inline fn getModRmExt(tag: Tag) u3 { .sbb => 0x3, .cmp => 0x7, .mov => 0x0, + .jmp_near => 0x4, .call_near => 0x2, - else => unreachable, + else => null, }; } @@ -692,6 +681,67 @@ fn lowerToDEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) InnerError!void { encoder.imm32(imm); } +fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { + const opc = getOpCode(tag, .m).?; + const modrm_ext = getModRmExt(tag).?; + switch (reg_or_mem) { + .register => |reg| { + if (reg.size() != 64) return error.EmitFail; + const encoder = try Encoder.init(code, 3); + encoder.rex(.{ + .w = false, + .b = reg.isExtended(), + }); + encoder.opcode_1byte(opc); + encoder.modRm_direct(modrm_ext, reg.lowId()); + }, + .memory => |mem_op| { + const encoder = try Encoder.init(code, 8); + if (mem_op.reg) |reg| { + if (reg.size() != 64) return error.EmitFail; + encoder.rex(.{ + .w = false, + .b = reg.isExtended(), + }); + encoder.opcode_1byte(opc); + if (reg.lowId() == 4) { + if (mem_op.disp == 0) { + encoder.modRm_SIBDisp0(modrm_ext); + encoder.sib_base(reg.lowId()); + } else if (immOpSize(mem_op.disp) == 8) { + encoder.modRm_SIBDisp8(modrm_ext); + encoder.sib_baseDisp8(reg.lowId()); + encoder.disp8(@intCast(i8, mem_op.disp)); + } else { + encoder.modRm_SIBDisp32(modrm_ext); + encoder.sib_baseDisp32(reg.lowId()); + encoder.disp32(mem_op.disp); + } + } else { + if (mem_op.disp == 0) { + encoder.modRm_indirectDisp0(modrm_ext, reg.lowId()); + } else if (immOpSize(mem_op.disp) == 8) { + encoder.modRm_indirectDisp8(modrm_ext, reg.lowId()); + encoder.disp8(@intCast(i8, mem_op.disp)); + } else { + encoder.modRm_indirectDisp32(modrm_ext, reg.lowId()); + encoder.disp32(mem_op.disp); + } + } + } else { + encoder.opcode_1byte(opc); + if (mem_op.rip) { + encoder.modRm_RIPDisp32(modrm_ext); + } else { + encoder.modRm_SIBDisp0(modrm_ext); + encoder.sib_disp32(); + } + encoder.disp32(mem_op.disp); + } + }, + } +} + fn lowerToTdEnc(tag: Tag, moffs: i64, reg: Register, code: *std.ArrayList(u8)) InnerError!void { return lowerToTdFdEnc(tag, reg, moffs, code, true); } @@ -772,7 +822,7 @@ fn lowerToOiEnc(tag: Tag, reg: Register, imm: i64, code: *std.ArrayList(u8)) Inn fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.ArrayList(u8)) InnerError!void { var opc = getOpCode(tag, .mi).?; - const modrm_ext = getModRmExt(tag); + const modrm_ext = getModRmExt(tag).?; switch (reg_or_mem) { .register => |dst_reg| { if (dst_reg.size() == 8) { @@ -819,16 +869,25 @@ fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.Arr .b = dst_reg.isExtended(), }); encoder.opcode_1byte(opc); - if (dst_mem.disp == 0) { - encoder.modRm_indirectDisp0(modrm_ext, dst_reg.lowId()); - } else if (immOpSize(dst_mem.disp) == 8) { - encoder.modRm_indirectDisp8(modrm_ext, dst_reg.lowId()); - encoder.disp8(@intCast(i8, dst_mem.disp)); - } else { - if (dst_reg.lowId() == 4) { + if (dst_reg.lowId() == 4) { + if (dst_mem.disp == 0) { + encoder.modRm_SIBDisp0(modrm_ext); + encoder.sib_base(dst_reg.lowId()); + } else if (immOpSize(dst_mem.disp) == 8) { + encoder.modRm_SIBDisp8(modrm_ext); + encoder.sib_baseDisp8(dst_reg.lowId()); + encoder.disp8(@intCast(i8, dst_mem.disp)); + } else { encoder.modRm_SIBDisp32(modrm_ext); encoder.sib_baseDisp32(dst_reg.lowId()); encoder.disp32(dst_mem.disp); + } + } else { + if (dst_mem.disp == 0) { + encoder.modRm_indirectDisp0(modrm_ext, dst_reg.lowId()); + } else if (immOpSize(dst_mem.disp) == 8) { + encoder.modRm_indirectDisp8(modrm_ext, dst_reg.lowId()); + encoder.disp8(@intCast(i8, dst_mem.disp)); } else { encoder.modRm_indirectDisp32(modrm_ext, dst_reg.lowId()); encoder.disp32(dst_mem.disp); @@ -886,16 +945,25 @@ fn lowerToRmEnc( .b = src_reg.isExtended(), }); encoder.opcode_1byte(opc); - if (src_mem.disp == 0) { - encoder.modRm_indirectDisp0(reg.lowId(), src_reg.lowId()); - } else if (immOpSize(src_mem.disp) == 8) { - encoder.modRm_indirectDisp8(reg.lowId(), src_reg.lowId()); - encoder.disp8(@intCast(i8, src_mem.disp)); - } else { - if (src_reg.lowId() == 4) { + if (src_reg.lowId() == 4) { + if (src_mem.disp == 0) { + encoder.modRm_SIBDisp0(reg.lowId()); + encoder.sib_base(src_reg.lowId()); + } else if (immOpSize(src_mem.disp) == 8) { + encoder.modRm_SIBDisp8(reg.lowId()); + encoder.sib_baseDisp8(src_reg.lowId()); + encoder.disp8(@intCast(i8, src_mem.disp)); + } else { encoder.modRm_SIBDisp32(reg.lowId()); encoder.sib_baseDisp32(src_reg.lowId()); encoder.disp32(src_mem.disp); + } + } else { + if (src_mem.disp == 0) { + encoder.modRm_indirectDisp0(reg.lowId(), src_reg.lowId()); + } else if (immOpSize(src_mem.disp) == 8) { + encoder.modRm_indirectDisp8(reg.lowId(), src_reg.lowId()); + encoder.disp8(@intCast(i8, src_mem.disp)); } else { encoder.modRm_indirectDisp32(reg.lowId(), src_reg.lowId()); encoder.disp32(src_mem.disp); @@ -960,16 +1028,25 @@ fn lowerToMrEnc( .b = dst_reg.isExtended(), }); encoder.opcode_1byte(opc); - if (dst_mem.disp == 0) { - encoder.modRm_indirectDisp0(reg.lowId(), dst_reg.lowId()); - } else if (immOpSize(dst_mem.disp) == 8) { - encoder.modRm_indirectDisp8(reg.lowId(), dst_reg.lowId()); - encoder.disp8(@intCast(i8, dst_mem.disp)); - } else { - if (dst_reg.lowId() == 4) { + if (dst_reg.lowId() == 4) { + if (dst_mem.disp == 0) { + encoder.modRm_SIBDisp0(reg.lowId()); + encoder.sib_base(dst_reg.lowId()); + } else if (immOpSize(dst_mem.disp) == 8) { + encoder.modRm_SIBDisp8(reg.lowId()); + encoder.sib_baseDisp8(dst_reg.lowId()); + encoder.disp8(@intCast(i8, dst_mem.disp)); + } else { encoder.modRm_SIBDisp32(reg.lowId()); encoder.sib_baseDisp32(dst_reg.lowId()); encoder.disp32(dst_mem.disp); + } + } else { + if (dst_mem.disp == 0) { + encoder.modRm_indirectDisp0(reg.lowId(), dst_reg.lowId()); + } else if (immOpSize(dst_mem.disp) == 8) { + encoder.modRm_indirectDisp8(reg.lowId(), dst_reg.lowId()); + encoder.disp8(@intCast(i8, dst_mem.disp)); } else { encoder.modRm_indirectDisp32(reg.lowId(), dst_reg.lowId()); encoder.disp32(dst_mem.disp); @@ -1099,7 +1176,7 @@ fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void if (ops.reg2 == .none) { // OP [reg1 + scale*rax + 0], imm32 var opc = getOpCode(tag, .mi).?; - const modrm_ext = getModRmExt(tag); + const modrm_ext = getModRmExt(tag).?; if (ops.reg1.size() == 8) { opc -= 1; } @@ -1153,7 +1230,7 @@ fn mirArithScaleImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void if (ops.reg1.size() == 8) { opc -= 1; } - const modrm_ext = getModRmExt(tag); + const modrm_ext = getModRmExt(tag).?; const encoder = try Encoder.init(emit.code, 2); encoder.rex(.{ .w = ops.reg1.size() == 64, @@ -1641,3 +1718,24 @@ test "lower FD/TD encoding" { try lowerToFdEnc(.mov, .al, 0x10, code.buffer()); try expectEqualHexStrings("\xa0\x10", code.emitted(), "mov al, ds:0x10"); } + +test "lower M encoding" { + var code = TestEmitCode.init(); + defer code.deinit(); + try lowerToMEnc(.jmp_near, RegisterOrMemory.reg(.r12), code.buffer()); + try expectEqualHexStrings("\x41\xFF\xE4", code.emitted(), "jmp r12"); + try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0), code.buffer()); + try expectEqualHexStrings("\x41\xFF\x24\x24", code.emitted(), "jmp qword ptr [r12]"); + try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0x10), code.buffer()); + try expectEqualHexStrings("\x41\xFF\x64\x24\x10", code.emitted(), "jmp qword ptr [r12 + 0x10]"); + try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0x1000), code.buffer()); + try expectEqualHexStrings( + "\x41\xFF\xA4\x24\x00\x10\x00\x00", + code.emitted(), + "jmp qword ptr [r12 + 0x1000]", + ); + try lowerToMEnc(.jmp_near, RegisterOrMemory.rip(0x10), code.buffer()); + try expectEqualHexStrings("\xFF\x25\x10\x00\x00\x00", code.emitted(), "jmp qword ptr [rip + 0x10]"); + try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(null, 0x10), code.buffer()); + try expectEqualHexStrings("\xFF\x24\x25\x10\x00\x00\x00", code.emitted(), "jmp qword ptr [ds:0x10]"); +} From 603f826779bf0cd7fcd3b6f27a7ee6df8d158d8a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Dec 2021 01:30:25 +0100 Subject: [PATCH 11/19] stage2: migrate push/pop r/m64 to new lowering mechanism --- src/arch/x86_64/Emit.zig | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 3f3c696dfb..2849707643 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -113,7 +113,8 @@ pub fn emitMir(emit: *Emit) InnerError!void { .imul_complex => try emit.mirIMulComplex(inst), - .push, .pop => try emit.mirPushPop(tag, inst), + .push => try emit.mirPushPop(.push, inst), + .pop => try emit.mirPushPop(.pop, inst), .jmp => try emit.mirJmpCall(.jmp_near, inst), .call => try emit.mirJmpCall(.call_near, inst), @@ -198,7 +199,7 @@ fn mirSyscall(emit: *Emit) InnerError!void { encoder.opcode_2byte(0x0f, 0x05); } -fn mirPushPop(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); switch (ops.flags) { 0b00 => { @@ -217,25 +218,7 @@ fn mirPushPop(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!v 0b01 => { // PUSH/POP r/m64 const imm = emit.mir.instructions.items(.data)[inst].imm; - const opc: u8 = switch (tag) { - .push => 0xff, - .pop => 0x8f, - else => unreachable, - }; - const modrm_ext: u3 = switch (tag) { - .push => 0x6, - .pop => 0x0, - else => unreachable, - }; - const encoder = try Encoder.init(emit.code, 6); - encoder.opcode_1byte(opc); - if (math.cast(i8, imm)) |imm_i8| { - encoder.modRm_indirectDisp8(modrm_ext, ops.reg1.lowId()); - encoder.imm8(@intCast(i8, imm_i8)); - } else |_| { - encoder.modRm_indirectDisp32(modrm_ext, ops.reg1.lowId()); - encoder.imm32(imm); - } + return lowerToMEnc(tag, RegisterOrMemory.mem(ops.reg1, imm), emit.code); }, 0b10 => { // PUSH imm32 @@ -255,7 +238,7 @@ fn mirPushPop(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!v 0b11 => unreachable, } } -fn mirPushPopRegsFromCalleePreservedRegs(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirPushPopRegsFromCalleePreservedRegs(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const callee_preserved_regs = bits.callee_preserved_regs; // PUSH/POP reg const opc: u8 = switch (tag) { @@ -532,6 +515,8 @@ const Tag = enum { lea, jmp_near, call_near, + push, + pop, }; const Encoding = enum { @@ -568,7 +553,8 @@ inline fn getOpCode(tag: Tag, enc: Encoding) ?u8 { else => null, }, .m => return switch (tag) { - .jmp_near, .call_near => 0xff, + .jmp_near, .call_near, .push => 0xff, + .pop => 0x8f, else => null, }, .mi => return switch (tag) { @@ -629,6 +615,8 @@ inline fn getModRmExt(tag: Tag) ?u3 { .mov => 0x0, .jmp_near => 0x4, .call_near => 0x2, + .push => 0x6, + .pop => 0x0, else => null, }; } From b657956c440b355abab9fdf01a1517f99dd1ec90 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Dec 2021 09:47:38 +0100 Subject: [PATCH 12/19] stage2: add lowering to O encoding Example includes push/pop register. --- src/arch/x86_64/Emit.zig | 61 ++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 2849707643..c1331d7b51 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -204,16 +204,7 @@ fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { switch (ops.flags) { 0b00 => { // PUSH/POP reg - const opc: u8 = switch (tag) { - .push => 0x50, - .pop => 0x58, - else => unreachable, - }; - const encoder = try Encoder.init(emit.code, 2); - encoder.rex(.{ - .b = ops.reg1.isExtended(), - }); - encoder.opcode_withReg(opc, ops.reg1.lowId()); + return lowerToOEnc(tag, ops.reg1, emit.code); }, 0b01 => { // PUSH/POP r/m64 @@ -240,22 +231,11 @@ fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { } fn mirPushPopRegsFromCalleePreservedRegs(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const callee_preserved_regs = bits.callee_preserved_regs; - // PUSH/POP reg - const opc: u8 = switch (tag) { - .push => 0x50, - .pop => 0x58, - else => unreachable, - }; - const regs = emit.mir.instructions.items(.data)[inst].regs_to_push_or_pop; if (tag == .push) { for (callee_preserved_regs) |reg, i| { if ((regs >> @intCast(u5, i)) & 1 == 0) continue; - const encoder = try Encoder.init(emit.code, 2); - encoder.rex(.{ - .b = reg.isExtended(), - }); - encoder.opcode_withReg(opc, reg.lowId()); + try lowerToOEnc(.push, reg, emit.code); } } else { // pop in the reverse direction @@ -263,11 +243,7 @@ fn mirPushPopRegsFromCalleePreservedRegs(emit: *Emit, tag: Tag, inst: Mir.Inst.I while (i > 0) : (i -= 1) { const reg = callee_preserved_regs[i - 1]; if ((regs >> @intCast(u5, i - 1)) & 1 == 0) continue; - const encoder = try Encoder.init(emit.code, 2); - encoder.rex(.{ - .b = reg.isExtended(), - }); - encoder.opcode_withReg(opc, reg.lowId()); + try lowerToOEnc(.pop, reg, emit.code); } } } @@ -526,6 +502,9 @@ const Encoding = enum { /// OP r/m64 m, + /// OP r64 + o, + /// OP r/m64, imm32 mi, @@ -557,6 +536,11 @@ inline fn getOpCode(tag: Tag, enc: Encoding) ?u8 { .pop => 0x8f, else => null, }, + .o => return switch (tag) { + .push => 0x50, + .pop => 0x58, + else => null, + }, .mi => return switch (tag) { .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp => 0x81, .mov => 0xc7, @@ -662,6 +646,20 @@ const RegisterOrMemory = union(enum) { } }; +fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) InnerError!void { + if (reg.size() != 16 and reg.size() != 64) return error.EmitFail; // TODO correct for push/pop, but is it universal? + const opc = getOpCode(tag, .o).?; + const encoder = try Encoder.init(code, 3); + if (reg.size() == 16) { + encoder.opcode_1byte(0x66); + } + encoder.rex(.{ + .w = false, + .b = reg.isExtended(), + }); + encoder.opcode_withReg(opc, reg.lowId()); +} + fn lowerToDEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) InnerError!void { const opc = getOpCode(tag, .d).?; const encoder = try Encoder.init(code, 5); @@ -1727,3 +1725,12 @@ test "lower M encoding" { try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(null, 0x10), code.buffer()); try expectEqualHexStrings("\xFF\x24\x25\x10\x00\x00\x00", code.emitted(), "jmp qword ptr [ds:0x10]"); } + +test "lower O encoding" { + var code = TestEmitCode.init(); + defer code.deinit(); + try lowerToOEnc(.pop, .r12, code.buffer()); + try expectEqualHexStrings("\x41\x5c", code.emitted(), "pop r12"); + try lowerToOEnc(.push, .r12w, code.buffer()); + try expectEqualHexStrings("\x66\x41\x54", code.emitted(), "push r12w"); +} From a70b4068c6a8585da8b0a28326010807e5c427c1 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Dec 2021 10:01:03 +0100 Subject: [PATCH 13/19] stage2: add lowering to I encoding Examples include push imm32. --- src/arch/x86_64/Emit.zig | 90 +++++++++++++++++++++++++--------------- 1 file changed, 56 insertions(+), 34 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index c1331d7b51..c25785fa55 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -215,16 +215,7 @@ fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { // PUSH imm32 assert(tag == .push); const imm = emit.mir.instructions.items(.data)[inst].imm; - const opc: u8 = if (imm <= math.maxInt(i8)) 0x6a else 0x6b; - const encoder = try Encoder.init(emit.code, 2); - encoder.opcode_1byte(opc); - if (imm <= math.maxInt(i8)) { - encoder.imm8(@intCast(i8, imm)); - } else if (imm <= math.maxInt(i16)) { - encoder.imm16(@intCast(i16, imm)); - } else { - encoder.imm32(imm); - } + return lowerToIEnc(.push, imm, emit.code); }, 0b11 => unreachable, } @@ -422,31 +413,17 @@ fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { assert(tag == .@"test"); const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); switch (ops.flags) { - 0b00 => blk: { + 0b00 => { if (ops.reg2 == .none) { // TEST r/m64, imm32 + // MI const imm = emit.mir.instructions.items(.data)[inst].imm; if (ops.reg1.to64() == .rax) { - // TODO reduce the size of the instruction if the immediate - // is smaller than 32 bits - const encoder = try Encoder.init(emit.code, 6); - encoder.rex(.{ - .w = true, - }); - encoder.opcode_1byte(0xa9); - encoder.imm32(imm); - break :blk; + // TEST rax, imm32 + // I + return lowerToIEnc(.@"test", imm, emit.code); } - const opc: u8 = if (ops.reg1.size() == 8) 0xf6 else 0xf7; - const encoder = try Encoder.init(emit.code, 7); - encoder.rex(.{ - .w = true, - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_direct(0, ops.reg1.lowId()); - encoder.imm8(@intCast(i8, imm)); - break :blk; + return lowerToMiEnc(.@"test", RegisterOrMemory.reg(ops.reg1), imm, emit.code); } // TEST r/m64, r64 return emit.fail("TODO TEST r/m64, r64", .{}); @@ -463,16 +440,16 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { switch (ops.flags) { 0b00 => { // RETF imm16 + // I const imm = emit.mir.instructions.items(.data)[inst].imm; - encoder.opcode_1byte(0xca); - encoder.imm16(@intCast(i16, imm)); + return lowerToIEnc(.ret_far, imm, emit.code); }, 0b01 => encoder.opcode_1byte(0xcb), // RETF 0b10 => { // RET imm16 + // I const imm = emit.mir.instructions.items(.data)[inst].imm; - encoder.opcode_1byte(0xc2); - encoder.imm16(@intCast(i16, imm)); + return lowerToIEnc(.ret_near, imm, emit.code); }, 0b11 => encoder.opcode_1byte(0xc3), // RET } @@ -493,6 +470,9 @@ const Tag = enum { call_near, push, pop, + @"test", + ret_near, + ret_far, }; const Encoding = enum { @@ -505,6 +485,9 @@ const Encoding = enum { /// OP r64 o, + /// OP imm32 + i, + /// OP r/m64, imm32 mi, @@ -541,9 +524,17 @@ inline fn getOpCode(tag: Tag, enc: Encoding) ?u8 { .pop => 0x58, else => null, }, + .i => return switch (tag) { + .push => 0x68, + .@"test" => 0xa9, + .ret_near => 0xc2, + .ret_far => 0xca, + else => null, + }, .mi => return switch (tag) { .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp => 0x81, .mov => 0xc7, + .@"test" => 0xf7, else => null, }, .mr => return switch (tag) { @@ -601,6 +592,7 @@ inline fn getModRmExt(tag: Tag) ?u3 { .call_near => 0x2, .push => 0x6, .pop => 0x0, + .@"test" => 0x0, else => null, }; } @@ -646,6 +638,36 @@ const RegisterOrMemory = union(enum) { } }; +fn lowerToIEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) InnerError!void { + var opc = getOpCode(tag, .i).?; + if (tag == .ret_far or tag == .ret_near) { + const encoder = try Encoder.init(code, 3); + encoder.opcode_1byte(opc); + encoder.imm16(@intCast(i16, imm)); + return; + } + if (immOpSize(imm) == 8) { + // TODO I think getOpCode should track this + switch (tag) { + .push => opc += 2, + .@"test" => opc -= 1, + else => return error.EmitFail, + } + } + const encoder = try Encoder.init(code, 5); + if (immOpSize(imm) == 16) { + encoder.opcode_1byte(0x66); + } + encoder.opcode_1byte(opc); + if (immOpSize(imm) == 8) { + encoder.imm8(@intCast(i8, imm)); + } else if (immOpSize(imm) == 16) { + encoder.imm16(@intCast(i16, imm)); + } else { + encoder.imm32(imm); + } +} + fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) InnerError!void { if (reg.size() != 16 and reg.size() != 64) return error.EmitFail; // TODO correct for push/pop, but is it universal? const opc = getOpCode(tag, .o).?; From d23a1487bd397ce86108ae2727b67a1d0614f7a9 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Dec 2021 17:51:19 +0100 Subject: [PATCH 14/19] stage2: add lowering of ZO encoding ZO (probably) stands for zero operands encoding which is effectively only the opcode. --- src/arch/x86_64/Emit.zig | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index c25785fa55..118dc9cc76 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -185,13 +185,11 @@ fn fixupRelocs(emit: *Emit) InnerError!void { } fn mirBrk(emit: *Emit) InnerError!void { - const encoder = try Encoder.init(emit.code, 1); - encoder.opcode_1byte(0xcc); + return lowerToZoEnc(.brk, emit.code); } fn mirNop(emit: *Emit) InnerError!void { - const encoder = try Encoder.init(emit.code, 1); - encoder.opcode_1byte(0x90); + return lowerToZoEnc(.nop, emit.code); } fn mirSyscall(emit: *Emit) InnerError!void { @@ -436,7 +434,6 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const tag = emit.mir.instructions.items(.tag)[inst]; assert(tag == .ret); const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); - const encoder = try Encoder.init(emit.code, 3); switch (ops.flags) { 0b00 => { // RETF imm16 @@ -444,14 +441,14 @@ fn mirRet(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { const imm = emit.mir.instructions.items(.data)[inst].imm; return lowerToIEnc(.ret_far, imm, emit.code); }, - 0b01 => encoder.opcode_1byte(0xcb), // RETF + 0b01 => return lowerToZoEnc(.ret_far, emit.code), 0b10 => { // RET imm16 // I const imm = emit.mir.instructions.items(.data)[inst].imm; return lowerToIEnc(.ret_near, imm, emit.code); }, - 0b11 => encoder.opcode_1byte(0xc3), // RET + 0b11 => return lowerToZoEnc(.ret_near, emit.code), } } @@ -471,11 +468,16 @@ const Tag = enum { push, pop, @"test", + brk, + nop, ret_near, ret_far, }; const Encoding = enum { + /// OP + zo, + /// OP rel32 d, @@ -509,6 +511,13 @@ const Encoding = enum { inline fn getOpCode(tag: Tag, enc: Encoding) ?u8 { switch (enc) { + .zo => return switch (tag) { + .ret_near => 0xc3, + .ret_far => 0xcb, + .brk => 0xcc, + .nop => 0x90, + else => null, + }, .d => return switch (tag) { .jmp_near => 0xe9, .call_near => 0xe8, @@ -638,6 +647,12 @@ const RegisterOrMemory = union(enum) { } }; +fn lowerToZoEnc(tag: Tag, code: *std.ArrayList(u8)) InnerError!void { + const opc = getOpCode(tag, .zo).?; + const encoder = try Encoder.init(code, 1); + encoder.opcode_1byte(opc); +} + fn lowerToIEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) InnerError!void { var opc = getOpCode(tag, .i).?; if (tag == .ret_far or tag == .ret_near) { From 8c664d3f6a59e412f33bca8c969f70ceb3545b11 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Dec 2021 18:49:03 +0100 Subject: [PATCH 15/19] stage2: support multibyte opcodes and refactor 1byte opcode changes --- src/arch/x86_64/Emit.zig | 224 +++++++++++++++++++-------------------- 1 file changed, 110 insertions(+), 114 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 118dc9cc76..e766d45a01 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -193,8 +193,7 @@ fn mirNop(emit: *Emit) InnerError!void { } fn mirSyscall(emit: *Emit) InnerError!void { - const encoder = try Encoder.init(emit.code, 2); - encoder.opcode_2byte(0x0f, 0x05); + return lowerToZoEnc(.syscall, emit.code); } fn mirPushPop(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { @@ -470,6 +469,7 @@ const Tag = enum { @"test", brk, nop, + syscall, ret_near, ret_far, }; @@ -509,78 +509,104 @@ const Encoding = enum { td, }; -inline fn getOpCode(tag: Tag, enc: Encoding) ?u8 { +const OpCode = union(enum) { + one_byte: u8, + two_byte: struct { _1: u8, _2: u8 }, + + fn oneByte(opc: u8) OpCode { + return .{ .one_byte = opc }; + } + + fn twoByte(opc1: u8, opc2: u8) OpCode { + return .{ .two_byte = .{ ._1 = opc1, ._2 = opc2 } }; + } + + fn encode(opc: OpCode, encoder: Encoder) void { + switch (opc) { + .one_byte => |v| encoder.opcode_1byte(v), + .two_byte => |v| encoder.opcode_2byte(v._1, v._2), + } + } + + fn encodeWithReg(opc: OpCode, encoder: Encoder, reg: Register) void { + assert(opc == .one_byte); + encoder.opcode_withReg(opc.one_byte, reg.lowId()); + } +}; + +inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { switch (enc) { .zo => return switch (tag) { - .ret_near => 0xc3, - .ret_far => 0xcb, - .brk => 0xcc, - .nop => 0x90, + .ret_near => OpCode.oneByte(0xc3), + .ret_far => OpCode.oneByte(0xcb), + .brk => OpCode.oneByte(0xcc), + .nop => OpCode.oneByte(0x90), + .syscall => OpCode.twoByte(0x0f, 0x05), else => null, }, .d => return switch (tag) { - .jmp_near => 0xe9, - .call_near => 0xe8, + .jmp_near => OpCode.oneByte(0xe9), + .call_near => OpCode.oneByte(0xe8), else => null, }, .m => return switch (tag) { - .jmp_near, .call_near, .push => 0xff, - .pop => 0x8f, + .jmp_near, .call_near, .push => OpCode.oneByte(0xff), + .pop => OpCode.oneByte(0x8f), else => null, }, .o => return switch (tag) { - .push => 0x50, - .pop => 0x58, + .push => OpCode.oneByte(0x50), + .pop => OpCode.oneByte(0x58), else => null, }, .i => return switch (tag) { - .push => 0x68, - .@"test" => 0xa9, - .ret_near => 0xc2, - .ret_far => 0xca, + .push => OpCode.oneByte(if (is_one_byte) 0x6a else 0x68), + .@"test" => OpCode.oneByte(if (is_one_byte) 0xa8 else 0xa9), + .ret_near => OpCode.oneByte(0xc2), + .ret_far => OpCode.oneByte(0xca), else => null, }, .mi => return switch (tag) { - .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp => 0x81, - .mov => 0xc7, - .@"test" => 0xf7, + .adc, .add, .sub, .xor, .@"and", .@"or", .sbb, .cmp => OpCode.oneByte(if (is_one_byte) 0x80 else 0x81), + .mov => OpCode.oneByte(if (is_one_byte) 0xc6 else 0xc7), + .@"test" => OpCode.oneByte(if (is_one_byte) 0xf6 else 0xf7), else => null, }, .mr => return switch (tag) { - .adc => 0x11, - .add => 0x01, - .sub => 0x29, - .xor => 0x31, - .@"and" => 0x21, - .@"or" => 0x09, - .sbb => 0x19, - .cmp => 0x39, - .mov => 0x89, + .adc => OpCode.oneByte(if (is_one_byte) 0x10 else 0x11), + .add => OpCode.oneByte(if (is_one_byte) 0x00 else 0x01), + .sub => OpCode.oneByte(if (is_one_byte) 0x28 else 0x29), + .xor => OpCode.oneByte(if (is_one_byte) 0x30 else 0x31), + .@"and" => OpCode.oneByte(if (is_one_byte) 0x20 else 0x21), + .@"or" => OpCode.oneByte(if (is_one_byte) 0x08 else 0x09), + .sbb => OpCode.oneByte(if (is_one_byte) 0x18 else 0x19), + .cmp => OpCode.oneByte(if (is_one_byte) 0x38 else 0x39), + .mov => OpCode.oneByte(if (is_one_byte) 0x88 else 0x89), else => null, }, .rm => return switch (tag) { - .adc => 0x13, - .add => 0x03, - .sub => 0x2b, - .xor => 0x33, - .@"and" => 0x23, - .@"or" => 0x0b, - .sbb => 0x1b, - .cmp => 0x3b, - .mov => 0x8b, - .lea => 0x8d, + .adc => OpCode.oneByte(if (is_one_byte) 0x12 else 0x13), + .add => OpCode.oneByte(if (is_one_byte) 0x02 else 0x03), + .sub => OpCode.oneByte(if (is_one_byte) 0x2a else 0x2b), + .xor => OpCode.oneByte(if (is_one_byte) 0x32 else 0x33), + .@"and" => OpCode.oneByte(if (is_one_byte) 0x22 else 0x23), + .@"or" => OpCode.oneByte(if (is_one_byte) 0x0b else 0x0b), + .sbb => OpCode.oneByte(if (is_one_byte) 0x1a else 0x1b), + .cmp => OpCode.oneByte(if (is_one_byte) 0x3a else 0x3b), + .mov => OpCode.oneByte(if (is_one_byte) 0x8a else 0x8b), + .lea => OpCode.oneByte(if (is_one_byte) 0x8c else 0x8d), else => null, }, .oi => return switch (tag) { - .mov => 0xb8, + .mov => OpCode.oneByte(if (is_one_byte) 0xb0 else 0xb8), else => null, }, .fd => return switch (tag) { - .mov => 0xa1, + .mov => OpCode.oneByte(if (is_one_byte) 0xa0 else 0xa1), else => null, }, .td => return switch (tag) { - .mov => 0xa3, + .mov => OpCode.oneByte(if (is_one_byte) 0xa2 else 0xa3), else => null, }, } @@ -648,32 +674,25 @@ const RegisterOrMemory = union(enum) { }; fn lowerToZoEnc(tag: Tag, code: *std.ArrayList(u8)) InnerError!void { - const opc = getOpCode(tag, .zo).?; + const opc = getOpCode(tag, .zo, false).?; const encoder = try Encoder.init(code, 1); - encoder.opcode_1byte(opc); + opc.encode(encoder); } fn lowerToIEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) InnerError!void { - var opc = getOpCode(tag, .i).?; if (tag == .ret_far or tag == .ret_near) { const encoder = try Encoder.init(code, 3); - encoder.opcode_1byte(opc); + const opc = getOpCode(tag, .i, false).?; + opc.encode(encoder); encoder.imm16(@intCast(i16, imm)); return; } - if (immOpSize(imm) == 8) { - // TODO I think getOpCode should track this - switch (tag) { - .push => opc += 2, - .@"test" => opc -= 1, - else => return error.EmitFail, - } - } + const opc = getOpCode(tag, .i, immOpSize(imm) == 8).?; const encoder = try Encoder.init(code, 5); if (immOpSize(imm) == 16) { encoder.opcode_1byte(0x66); } - encoder.opcode_1byte(opc); + opc.encode(encoder); if (immOpSize(imm) == 8) { encoder.imm8(@intCast(i8, imm)); } else if (immOpSize(imm) == 16) { @@ -685,7 +704,7 @@ fn lowerToIEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) InnerError!void { fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) InnerError!void { if (reg.size() != 16 and reg.size() != 64) return error.EmitFail; // TODO correct for push/pop, but is it universal? - const opc = getOpCode(tag, .o).?; + const opc = getOpCode(tag, .o, false).?; const encoder = try Encoder.init(code, 3); if (reg.size() == 16) { encoder.opcode_1byte(0x66); @@ -694,18 +713,18 @@ fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) InnerError!voi .w = false, .b = reg.isExtended(), }); - encoder.opcode_withReg(opc, reg.lowId()); + opc.encodeWithReg(encoder, reg); } fn lowerToDEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) InnerError!void { - const opc = getOpCode(tag, .d).?; + const opc = getOpCode(tag, .d, false).?; const encoder = try Encoder.init(code, 5); - encoder.opcode_1byte(opc); + opc.encode(encoder); encoder.imm32(imm); } fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) InnerError!void { - const opc = getOpCode(tag, .m).?; + const opc = getOpCode(tag, .m, false).?; const modrm_ext = getModRmExt(tag).?; switch (reg_or_mem) { .register => |reg| { @@ -715,7 +734,7 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) .w = false, .b = reg.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); encoder.modRm_direct(modrm_ext, reg.lowId()); }, .memory => |mem_op| { @@ -726,7 +745,7 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) .w = false, .b = reg.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); if (reg.lowId() == 4) { if (mem_op.disp == 0) { encoder.modRm_SIBDisp0(modrm_ext); @@ -752,7 +771,7 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) } } } else { - encoder.opcode_1byte(opc); + opc.encode(encoder); if (mem_op.rip) { encoder.modRm_RIPDisp32(modrm_ext); } else { @@ -776,10 +795,10 @@ fn lowerToFdEnc(tag: Tag, reg: Register, moffs: i64, code: *std.ArrayList(u8)) I fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: i64, code: *std.ArrayList(u8), td: bool) InnerError!void { if (reg.lowId() != Register.rax.lowId()) return error.EmitFail; if (reg.size() != immOpSize(moffs)) return error.EmitFail; - var opc = if (td) getOpCode(tag, .td).? else getOpCode(tag, .fd).?; - if (reg.size() == 8) { - opc -= 1; - } + const opc = if (td) + getOpCode(tag, .td, reg.size() == 8).? + else + getOpCode(tag, .fd, reg.size() == 8).?; const encoder = try Encoder.init(code, 10); if (reg.size() == 16) { encoder.opcode_1byte(0x66); @@ -787,7 +806,7 @@ fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: i64, code: *std.ArrayList(u8), encoder.rex(.{ .w = reg.size() == 64, }); - encoder.opcode_1byte(opc); + opc.encode(encoder); switch (reg.size()) { 8 => { const moffs8 = try math.cast(i8, moffs); @@ -809,11 +828,8 @@ fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: i64, code: *std.ArrayList(u8), } fn lowerToOiEnc(tag: Tag, reg: Register, imm: i64, code: *std.ArrayList(u8)) InnerError!void { - var opc = getOpCode(tag, .oi).?; if (reg.size() != immOpSize(imm)) return error.EmitFail; - if (reg.size() == 8) { - opc -= 8; - } + const opc = getOpCode(tag, .oi, reg.size() == 8).?; const encoder = try Encoder.init(code, 10); if (reg.size() == 16) { encoder.opcode_1byte(0x66); @@ -822,7 +838,7 @@ fn lowerToOiEnc(tag: Tag, reg: Register, imm: i64, code: *std.ArrayList(u8)) Inn .w = reg.size() == 64, .b = reg.isExtended(), }); - encoder.opcode_withReg(opc, reg.lowId()); + opc.encodeWithReg(encoder, reg); switch (reg.size()) { 8 => { const imm8 = try math.cast(i8, imm); @@ -844,13 +860,10 @@ fn lowerToOiEnc(tag: Tag, reg: Register, imm: i64, code: *std.ArrayList(u8)) Inn } fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.ArrayList(u8)) InnerError!void { - var opc = getOpCode(tag, .mi).?; const modrm_ext = getModRmExt(tag).?; switch (reg_or_mem) { .register => |dst_reg| { - if (dst_reg.size() == 8) { - opc -= 1; - } + const opc = getOpCode(tag, .mi, dst_reg.size() == 8).?; const encoder = try Encoder.init(code, 7); if (dst_reg.size() == 16) { // 0x66 prefix switches to the non-default size; here we assume a switch from @@ -862,7 +875,7 @@ fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.Arr .w = dst_reg.size() == 64, .b = dst_reg.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); encoder.modRm_direct(modrm_ext, dst_reg.lowId()); switch (dst_reg.size()) { 8 => { @@ -878,6 +891,7 @@ fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.Arr } }, .memory => |dst_mem| { + const opc = getOpCode(tag, .mi, false).?; const encoder = try Encoder.init(code, 12); if (dst_mem.reg) |dst_reg| { // Register dst_reg can either be 64bit or 32bit in size. @@ -891,7 +905,7 @@ fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.Arr .w = false, .b = dst_reg.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); if (dst_reg.lowId() == 4) { if (dst_mem.disp == 0) { encoder.modRm_SIBDisp0(modrm_ext); @@ -917,7 +931,7 @@ fn lowerToMiEnc(tag: Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.Arr } } } else { - encoder.opcode_1byte(opc); + opc.encode(encoder); if (dst_mem.rip) { encoder.modRm_RIPDisp32(modrm_ext); } else { @@ -937,10 +951,7 @@ fn lowerToRmEnc( reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8), ) InnerError!void { - var opc = getOpCode(tag, .rm).?; - if (reg.size() == 8) { - opc -= 1; - } + const opc = getOpCode(tag, .rm, reg.size() == 8).?; switch (reg_or_mem) { .register => |src_reg| { if (reg.size() != src_reg.size()) return error.EmitFail; @@ -950,7 +961,7 @@ fn lowerToRmEnc( .r = reg.isExtended(), .b = src_reg.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); encoder.modRm_direct(reg.lowId(), src_reg.lowId()); }, .memory => |src_mem| { @@ -967,7 +978,7 @@ fn lowerToRmEnc( .r = reg.isExtended(), .b = src_reg.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); if (src_reg.lowId() == 4) { if (src_mem.disp == 0) { encoder.modRm_SIBDisp0(reg.lowId()); @@ -997,7 +1008,7 @@ fn lowerToRmEnc( .w = reg.size() == 64, .r = reg.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); if (src_mem.rip) { encoder.modRm_RIPDisp32(reg.lowId()); } else { @@ -1022,10 +1033,7 @@ fn lowerToMrEnc( // * reg is 32bit - dword ptr // * reg is 16bit - word ptr // * reg is 8bit - byte ptr - var opc = getOpCode(tag, .mr).?; - if (reg.size() == 8) { - opc -= 1; - } + const opc = getOpCode(tag, .mr, reg.size() == 8).?; switch (reg_or_mem) { .register => |dst_reg| { if (dst_reg.size() != reg.size()) return error.EmitFail; @@ -1035,7 +1043,7 @@ fn lowerToMrEnc( .r = reg.isExtended(), .b = dst_reg.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); encoder.modRm_direct(reg.lowId(), dst_reg.lowId()); }, .memory => |dst_mem| { @@ -1050,7 +1058,7 @@ fn lowerToMrEnc( .r = reg.isExtended(), .b = dst_reg.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); if (dst_reg.lowId() == 4) { if (dst_mem.disp == 0) { encoder.modRm_SIBDisp0(reg.lowId()); @@ -1080,7 +1088,7 @@ fn lowerToMrEnc( .w = reg.size() == 64, .r = reg.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); if (dst_mem.rip) { encoder.modRm_RIPDisp32(reg.lowId()); } else { @@ -1168,10 +1176,7 @@ fn mirArithScaleSrc(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; // OP reg1, [reg2 + scale*rcx + imm32] - var opc = getOpCode(tag, .rm).?; - if (ops.reg1.size() == 8) { - opc -= 1; - } + const opc = getOpCode(tag, .rm, ops.reg1.size() == 8).?; const imm = emit.mir.instructions.items(.data)[inst].imm; const encoder = try Encoder.init(emit.code, 8); encoder.rex(.{ @@ -1179,7 +1184,7 @@ fn mirArithScaleSrc(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void .r = ops.reg1.isExtended(), .b = ops.reg2.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); if (imm <= math.maxInt(i8)) { encoder.modRm_SIBDisp8(ops.reg1.lowId()); encoder.sib_scaleIndexBaseDisp8(scale, Register.rcx.lowId(), ops.reg2.lowId()); @@ -1198,17 +1203,14 @@ fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void if (ops.reg2 == .none) { // OP [reg1 + scale*rax + 0], imm32 - var opc = getOpCode(tag, .mi).?; + const opc = getOpCode(tag, .mi, ops.reg1.size() == 8).?; const modrm_ext = getModRmExt(tag).?; - if (ops.reg1.size() == 8) { - opc -= 1; - } const encoder = try Encoder.init(emit.code, 8); encoder.rex(.{ .w = ops.reg1.size() == 64, .b = ops.reg1.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); encoder.modRm_SIBDisp0(modrm_ext); encoder.sib_scaleIndexBase(scale, Register.rax.lowId(), ops.reg1.lowId()); if (imm <= math.maxInt(i8)) { @@ -1222,17 +1224,14 @@ fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void } // OP [reg1 + scale*rax + imm32], reg2 - var opc = getOpCode(tag, .mr).?; - if (ops.reg1.size() == 8) { - opc -= 1; - } + const opc = getOpCode(tag, .mr, ops.reg1.size() == 8).?; const encoder = try Encoder.init(emit.code, 8); encoder.rex(.{ .w = ops.reg1.size() == 64, .r = ops.reg2.isExtended(), .b = ops.reg1.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); if (imm <= math.maxInt(i8)) { encoder.modRm_SIBDisp8(ops.reg2.lowId()); encoder.sib_scaleIndexBaseDisp8(scale, Register.rax.lowId(), ops.reg1.lowId()); @@ -1249,17 +1248,14 @@ fn mirArithScaleImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void const scale = ops.flags; const payload = emit.mir.instructions.items(.data)[inst].payload; const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - var opc = getOpCode(tag, .mi).?; - if (ops.reg1.size() == 8) { - opc -= 1; - } + const opc = getOpCode(tag, .mi, ops.reg1.size() == 8).?; const modrm_ext = getModRmExt(tag).?; const encoder = try Encoder.init(emit.code, 2); encoder.rex(.{ .w = ops.reg1.size() == 64, .b = ops.reg1.isExtended(), }); - encoder.opcode_1byte(opc); + opc.encode(encoder); if (imm_pair.dest_off <= math.maxInt(i8)) { encoder.modRm_SIBDisp8(modrm_ext); encoder.sib_scaleIndexBaseDisp8(scale, Register.rax.lowId(), ops.reg1.lowId()); From c50bb2b80f3ccdc361a12034e7919f5288131c5e Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Dec 2021 20:29:34 +0100 Subject: [PATCH 16/19] stage2: lower jcc and setcc conditional jump/set instructions --- src/arch/x86_64/CodeGen.zig | 2 +- src/arch/x86_64/Emit.zig | 356 ++++++++++++++++++++++-------------- 2 files changed, 220 insertions(+), 138 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 2660a02ca3..65bd3b857c 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -2898,7 +2898,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void _ = try self.addInst(.{ .tag = tag, .ops = (Mir.Ops{ - .reg1 = reg, + .reg1 = reg.to8(), .flags = flags, }).encode(), .data = undefined, diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index e766d45a01..83268b5432 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -260,149 +260,60 @@ fn mirJmpCall(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); } -const CondType = enum { - /// greater than or equal - gte, - - /// greater than - gt, - - /// less than - lt, - - /// less than or equal - lte, - - /// above or equal - ae, - - /// above - a, - - /// below - b, - - /// below or equal - be, - - /// not equal - ne, - - /// equal - eq, - - fn fromTagAndFlags(tag: Mir.Inst.Tag, flags: u2) CondType { - return switch (tag) { - .cond_jmp_greater_less, - .cond_set_byte_greater_less, - => switch (flags) { - 0b00 => CondType.gte, - 0b01 => CondType.gt, - 0b10 => CondType.lt, - 0b11 => CondType.lte, - }, - .cond_jmp_above_below, - .cond_set_byte_above_below, - => switch (flags) { - 0b00 => CondType.ae, - 0b01 => CondType.a, - 0b10 => CondType.b, - 0b11 => CondType.be, - }, - .cond_jmp_eq_ne, - .cond_set_byte_eq_ne, - => switch (@truncate(u1, flags)) { - 0b0 => CondType.ne, - 0b1 => CondType.eq, - }, - else => unreachable, - }; - } -}; - -inline fn getCondOpCode(tag: Mir.Inst.Tag, cond: CondType) u8 { - switch (cond) { - .gte => return switch (tag) { - .cond_jmp_greater_less => 0x8d, - .cond_set_byte_greater_less => 0x9d, - else => unreachable, - }, - .gt => return switch (tag) { - .cond_jmp_greater_less => 0x8f, - .cond_set_byte_greater_less => 0x9f, - else => unreachable, - }, - .lt => return switch (tag) { - .cond_jmp_greater_less => 0x8c, - .cond_set_byte_greater_less => 0x9c, - else => unreachable, - }, - .lte => return switch (tag) { - .cond_jmp_greater_less => 0x8e, - .cond_set_byte_greater_less => 0x9e, - else => unreachable, - }, - .ae => return switch (tag) { - .cond_jmp_above_below => 0x83, - .cond_set_byte_above_below => 0x93, - else => unreachable, - }, - .a => return switch (tag) { - .cond_jmp_above_below => 0x87, - .cond_set_byte_greater_less => 0x97, - else => unreachable, - }, - .b => return switch (tag) { - .cond_jmp_above_below => 0x82, - .cond_set_byte_greater_less => 0x92, - else => unreachable, - }, - .be => return switch (tag) { - .cond_jmp_above_below => 0x86, - .cond_set_byte_greater_less => 0x96, - else => unreachable, - }, - .eq => return switch (tag) { - .cond_jmp_eq_ne => 0x84, - .cond_set_byte_eq_ne => 0x94, - else => unreachable, - }, - .ne => return switch (tag) { - .cond_jmp_eq_ne => 0x85, - .cond_set_byte_eq_ne => 0x95, - else => unreachable, - }, - } -} - -fn mirCondJmp(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirCondJmp(emit: *Emit, mir_tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const target = emit.mir.instructions.items(.data)[inst].inst; - const cond = CondType.fromTagAndFlags(tag, ops.flags); - const opc = getCondOpCode(tag, cond); + const tag = switch (mir_tag) { + .cond_jmp_greater_less => switch (ops.flags) { + 0b00 => Tag.jge, + 0b01 => Tag.jg, + 0b10 => Tag.jl, + 0b11 => Tag.jle, + }, + .cond_jmp_above_below => switch (ops.flags) { + 0b00 => Tag.jae, + 0b01 => Tag.ja, + 0b10 => Tag.jb, + 0b11 => Tag.jbe, + }, + .cond_jmp_eq_ne => switch (@truncate(u1, ops.flags)) { + 0b0 => Tag.jne, + 0b1 => Tag.je, + }, + else => unreachable, + }; const source = emit.code.items.len; - const encoder = try Encoder.init(emit.code, 6); - encoder.opcode_2byte(0x0f, opc); + try lowerToDEnc(tag, 0, emit.code); try emit.relocs.append(emit.bin_file.allocator, .{ .source = source, .target = target, - .offset = emit.code.items.len, + .offset = emit.code.items.len - 4, .length = 6, }); - encoder.imm32(0); } -fn mirCondSetByte(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { +fn mirCondSetByte(emit: *Emit, mir_tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); - const cond = CondType.fromTagAndFlags(tag, ops.flags); - const opc = getCondOpCode(tag, cond); - const encoder = try Encoder.init(emit.code, 4); - encoder.rex(.{ - .w = true, - .b = ops.reg1.isExtended(), - }); - encoder.opcode_2byte(0x0f, opc); - encoder.modRm_direct(0x0, ops.reg1.lowId()); + const tag = switch (mir_tag) { + .cond_set_byte_greater_less => switch (ops.flags) { + 0b00 => Tag.setge, + 0b01 => Tag.setg, + 0b10 => Tag.setl, + 0b11 => Tag.setle, + }, + .cond_set_byte_above_below => switch (ops.flags) { + 0b00 => Tag.setae, + 0b01 => Tag.seta, + 0b10 => Tag.setb, + 0b11 => Tag.setbe, + }, + .cond_set_byte_eq_ne => switch (@truncate(u1, ops.flags)) { + 0b0 => Tag.setne, + 0b1 => Tag.sete, + }, + else => unreachable, + }; + return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); } fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { @@ -472,6 +383,103 @@ const Tag = enum { syscall, ret_near, ret_far, + jo, + jno, + jb, + jbe, + jc, + jnae, + jnc, + jae, + je, + jz, + jne, + jnz, + jna, + jnb, + jnbe, + ja, + js, + jns, + jpe, + jp, + jpo, + jnp, + jnge, + jl, + jge, + jnl, + jle, + jng, + jg, + jnle, + seto, + setno, + setb, + setc, + setnae, + setnb, + setnc, + setae, + sete, + setz, + setne, + setnz, + setbe, + setna, + seta, + setnbe, + sets, + setns, + setp, + setpe, + setnp, + setop, + setl, + setnge, + setnl, + setge, + setle, + setng, + setnle, + setg, + + fn isSetCC(tag: Tag) bool { + return switch (tag) { + .seto, + .setno, + .setb, + .setc, + .setnae, + .setnb, + .setnc, + .setae, + .sete, + .setz, + .setne, + .setnz, + .setbe, + .setna, + .seta, + .setnbe, + .sets, + .setns, + .setp, + .setpe, + .setnp, + .setop, + .setl, + .setnge, + .setnl, + .setge, + .setle, + .setng, + .setnle, + .setg, + => true, + else => false, + }; + } }; const Encoding = enum { @@ -547,11 +555,43 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { .d => return switch (tag) { .jmp_near => OpCode.oneByte(0xe9), .call_near => OpCode.oneByte(0xe8), + .jo => if (is_one_byte) OpCode.oneByte(0x70) else OpCode.twoByte(0x0f, 0x80), + .jno => if (is_one_byte) OpCode.oneByte(0x71) else OpCode.twoByte(0x0f, 0x81), + .jb, .jc, .jnae => if (is_one_byte) OpCode.oneByte(0x72) else OpCode.twoByte(0x0f, 0x82), + .jnb, .jnc, .jae => if (is_one_byte) OpCode.oneByte(0x73) else OpCode.twoByte(0x0f, 0x83), + .je, .jz => if (is_one_byte) OpCode.oneByte(0x74) else OpCode.twoByte(0x0f, 0x84), + .jne, .jnz => if (is_one_byte) OpCode.oneByte(0x75) else OpCode.twoByte(0x0f, 0x85), + .jna, .jbe => if (is_one_byte) OpCode.oneByte(0x76) else OpCode.twoByte(0x0f, 0x86), + .jnbe, .ja => if (is_one_byte) OpCode.oneByte(0x77) else OpCode.twoByte(0x0f, 0x87), + .js => if (is_one_byte) OpCode.oneByte(0x78) else OpCode.twoByte(0x0f, 0x88), + .jns => if (is_one_byte) OpCode.oneByte(0x79) else OpCode.twoByte(0x0f, 0x89), + .jpe, .jp => if (is_one_byte) OpCode.oneByte(0x7a) else OpCode.twoByte(0x0f, 0x8a), + .jpo, .jnp => if (is_one_byte) OpCode.oneByte(0x7b) else OpCode.twoByte(0x0f, 0x8b), + .jnge, .jl => if (is_one_byte) OpCode.oneByte(0x7c) else OpCode.twoByte(0x0f, 0x8c), + .jge, .jnl => if (is_one_byte) OpCode.oneByte(0x7d) else OpCode.twoByte(0x0f, 0x8d), + .jle, .jng => if (is_one_byte) OpCode.oneByte(0x7e) else OpCode.twoByte(0x0f, 0x8e), + .jg, .jnle => if (is_one_byte) OpCode.oneByte(0x7f) else OpCode.twoByte(0x0f, 0x8f), else => null, }, .m => return switch (tag) { .jmp_near, .call_near, .push => OpCode.oneByte(0xff), .pop => OpCode.oneByte(0x8f), + .seto => OpCode.twoByte(0x0f, 0x90), + .setno => OpCode.twoByte(0x0f, 0x91), + .setb, .setc, .setnae => OpCode.twoByte(0x0f, 0x92), + .setnb, .setnc, .setae => OpCode.twoByte(0x0f, 0x93), + .sete, .setz => OpCode.twoByte(0x0f, 0x94), + .setne, .setnz => OpCode.twoByte(0x0f, 0x95), + .setbe, .setna => OpCode.twoByte(0x0f, 0x96), + .seta, .setnbe => OpCode.twoByte(0x0f, 0x97), + .sets => OpCode.twoByte(0x0f, 0x98), + .setns => OpCode.twoByte(0x0f, 0x99), + .setp, .setpe => OpCode.twoByte(0x0f, 0x9a), + .setnp, .setop => OpCode.twoByte(0x0f, 0x9b), + .setl, .setnge => OpCode.twoByte(0x0f, 0x9c), + .setnl, .setge => OpCode.twoByte(0x0f, 0x9d), + .setle, .setng => OpCode.twoByte(0x0f, 0x9e), + .setnle, .setg => OpCode.twoByte(0x0f, 0x9f), else => null, }, .o => return switch (tag) { @@ -628,6 +668,37 @@ inline fn getModRmExt(tag: Tag) ?u3 { .push => 0x6, .pop => 0x0, .@"test" => 0x0, + .seto, + .setno, + .setb, + .setc, + .setnae, + .setnb, + .setnc, + .setae, + .sete, + .setz, + .setne, + .setnz, + .setbe, + .setna, + .seta, + .setnbe, + .sets, + .setns, + .setp, + .setpe, + .setnp, + .setop, + .setl, + .setnge, + .setnl, + .setge, + .setle, + .setng, + .setnle, + .setg, + => 0x0, else => null, }; } @@ -718,7 +789,7 @@ fn lowerToOEnc(tag: Tag, reg: Register, code: *std.ArrayList(u8)) InnerError!voi fn lowerToDEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) InnerError!void { const opc = getOpCode(tag, .d, false).?; - const encoder = try Encoder.init(code, 5); + const encoder = try Encoder.init(code, 6); opc.encode(encoder); encoder.imm32(imm); } @@ -728,10 +799,13 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) const modrm_ext = getModRmExt(tag).?; switch (reg_or_mem) { .register => |reg| { - if (reg.size() != 64) return error.EmitFail; + // TODO clean this up! + if (reg.size() != 64) { + if (reg.size() != 8 and !tag.isSetCC()) return error.EmitFail; + } const encoder = try Encoder.init(code, 3); encoder.rex(.{ - .w = false, + .w = tag.isSetCC(), .b = reg.isExtended(), }); opc.encode(encoder); @@ -740,9 +814,12 @@ fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) .memory => |mem_op| { const encoder = try Encoder.init(code, 8); if (mem_op.reg) |reg| { - if (reg.size() != 64) return error.EmitFail; + // TODO clean this up! + if (reg.size() != 64) { + if (reg.size() != 8 and !tag.isSetCC()) return error.EmitFail; + } encoder.rex(.{ - .w = false, + .w = tag.isSetCC(), .b = reg.isExtended(), }); opc.encode(encoder); @@ -1172,6 +1249,7 @@ fn immOpSize(imm: i64) u8 { return 64; } +// TODO fn mirArithScaleSrc(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; @@ -1196,6 +1274,7 @@ fn mirArithScaleSrc(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void } } +// TODO fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; @@ -1243,6 +1322,7 @@ fn mirArithScaleDst(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void } } +// TODO fn mirArithScaleImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); const scale = ops.flags; @@ -1757,6 +1837,8 @@ test "lower M encoding" { try expectEqualHexStrings("\xFF\x25\x10\x00\x00\x00", code.emitted(), "jmp qword ptr [rip + 0x10]"); try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(null, 0x10), code.buffer()); try expectEqualHexStrings("\xFF\x24\x25\x10\x00\x00\x00", code.emitted(), "jmp qword ptr [ds:0x10]"); + try lowerToMEnc(.seta, RegisterOrMemory.reg(.r11b), code.buffer()); + try expectEqualHexStrings("\x49\x0F\x97\xC3", code.emitted(), "seta r11b"); } test "lower O encoding" { From dba5df64eacfbcca9727fe8596b186defb30a753 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Dec 2021 20:51:48 +0100 Subject: [PATCH 17/19] stage2: use lowerToRmEnc to lower two-operand imul Fix mismatched register sizes in codegen. --- src/arch/x86_64/CodeGen.zig | 4 ++-- src/arch/x86_64/Emit.zig | 13 +++---------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 65bd3b857c..08a926216c 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -1717,7 +1717,7 @@ fn genIMulOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: MCValue) ! _ = try self.addInst(.{ .tag = .imul_complex, .ops = (Mir.Ops{ - .reg1 = dst_reg, + .reg1 = registerAlias(dst_reg, @divExact(src_reg.size(), 8)), .reg2 = src_reg, }).encode(), .data = undefined, @@ -1766,7 +1766,7 @@ fn genIMulOpMir(self: *Self, dst_ty: Type, dst_mcv: MCValue, src_mcv: MCValue) ! _ = try self.addInst(.{ .tag = .imul_complex, .ops = (Mir.Ops{ - .reg1 = dst_reg, + .reg1 = registerAlias(dst_reg, @divExact(src_reg.size(), 8)), .reg2 = src_reg, }).encode(), .data = undefined, diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 83268b5432..0694a6ed2c 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -380,6 +380,7 @@ const Tag = enum { @"test", brk, nop, + imul, syscall, ret_near, ret_far, @@ -635,6 +636,7 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { .cmp => OpCode.oneByte(if (is_one_byte) 0x3a else 0x3b), .mov => OpCode.oneByte(if (is_one_byte) 0x8a else 0x8b), .lea => OpCode.oneByte(if (is_one_byte) 0x8c else 0x8d), + .imul => OpCode.twoByte(0x0f, 0xaf), else => null, }, .oi => return switch (tag) { @@ -1378,16 +1380,7 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { assert(tag == .imul_complex); const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); switch (ops.flags) { - 0b00 => { - const encoder = try Encoder.init(emit.code, 4); - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), - }); - encoder.opcode_2byte(0x0f, 0xaf); - encoder.modRm_direct(ops.reg1.lowId(), ops.reg2.lowId()); - }, + 0b00 => return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code), 0b10 => { const imm = emit.mir.instructions.items(.data)[inst].imm; const opc: u8 = if (imm <= math.maxInt(i8)) 0x6b else 0x69; From 35fe088e0e341d06d97f1ac0f799b875c71f242a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Thu, 23 Dec 2021 21:14:14 +0100 Subject: [PATCH 18/19] stage2: add lowering of RMI encoding Example includes imul with 3 operands such as imul r64, r/m64, imm32. --- src/arch/x86_64/Emit.zig | 123 ++++++++++++++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 16 deletions(-) diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig index 0694a6ed2c..e410e08ee3 100644 --- a/src/arch/x86_64/Emit.zig +++ b/src/arch/x86_64/Emit.zig @@ -516,6 +516,9 @@ const Encoding = enum { /// OP moffs, al/ax/eax/rax td, + + /// OP r64, r/m64, imm32 + rmi, }; const OpCode = union(enum) { @@ -651,6 +654,10 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { .mov => OpCode.oneByte(if (is_one_byte) 0xa2 else 0xa3), else => null, }, + .rmi => return switch (tag) { + .imul => OpCode.oneByte(if (is_one_byte) 0x6b else 0x69), + else => null, + }, } } @@ -1180,6 +1187,96 @@ fn lowerToMrEnc( } } +fn lowerToRmiEnc( + tag: Tag, + reg: Register, + reg_or_mem: RegisterOrMemory, + imm: i32, + code: *std.ArrayList(u8), +) InnerError!void { + const opc = getOpCode(tag, .rmi, reg.size() == 8).?; + switch (reg_or_mem) { + .register => |src_reg| { + if (reg.size() != src_reg.size()) return error.EmitFail; + const encoder = try Encoder.init(code, 7); + encoder.rex(.{ + .w = reg.size() == 64, + .r = reg.isExtended(), + .b = src_reg.isExtended(), + }); + opc.encode(encoder); + encoder.modRm_direct(reg.lowId(), src_reg.lowId()); + switch (reg.size()) { + 8 => { + const imm8 = try math.cast(i8, imm); + encoder.imm8(imm8); + }, + 16 => { + const imm16 = try math.cast(i16, imm); + encoder.imm16(imm16); + }, + 32, 64 => encoder.imm32(imm), + else => unreachable, + } + }, + .memory => |src_mem| { + const encoder = try Encoder.init(code, 13); + if (reg.size() == 16) { + encoder.opcode_1byte(0x66); + } + if (src_mem.reg) |src_reg| { + // TODO handle 32-bit base register - requires prefix 0x67 + // Intel Manual, Vol 1, chapter 3.6 and 3.6.1 + if (src_reg.size() != 64) return error.EmitFail; + encoder.rex(.{ + .w = reg.size() == 64, + .r = reg.isExtended(), + .b = src_reg.isExtended(), + }); + opc.encode(encoder); + if (src_reg.lowId() == 4) { + if (src_mem.disp == 0) { + encoder.modRm_SIBDisp0(reg.lowId()); + encoder.sib_base(src_reg.lowId()); + } else if (immOpSize(src_mem.disp) == 8) { + encoder.modRm_SIBDisp8(reg.lowId()); + encoder.sib_baseDisp8(src_reg.lowId()); + encoder.disp8(@intCast(i8, src_mem.disp)); + } else { + encoder.modRm_SIBDisp32(reg.lowId()); + encoder.sib_baseDisp32(src_reg.lowId()); + encoder.disp32(src_mem.disp); + } + } else { + if (src_mem.disp == 0) { + encoder.modRm_indirectDisp0(reg.lowId(), src_reg.lowId()); + } else if (immOpSize(src_mem.disp) == 8) { + encoder.modRm_indirectDisp8(reg.lowId(), src_reg.lowId()); + encoder.disp8(@intCast(i8, src_mem.disp)); + } else { + encoder.modRm_indirectDisp32(reg.lowId(), src_reg.lowId()); + encoder.disp32(src_mem.disp); + } + } + } else { + encoder.rex(.{ + .w = reg.size() == 64, + .r = reg.isExtended(), + }); + opc.encode(encoder); + if (src_mem.rip) { + encoder.modRm_RIPDisp32(reg.lowId()); + } else { + encoder.modRm_SIBDisp0(reg.lowId()); + encoder.sib_disp32(); + } + encoder.disp32(src_mem.disp); + } + encoder.imm32(imm); + }, + } +} + fn mirArith(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); switch (ops.flags) { @@ -1383,22 +1480,7 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { 0b00 => return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code), 0b10 => { const imm = emit.mir.instructions.items(.data)[inst].imm; - const opc: u8 = if (imm <= math.maxInt(i8)) 0x6b else 0x69; - const encoder = try Encoder.init(emit.code, 7); - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg1.isExtended(), - }); - encoder.opcode_1byte(opc); - encoder.modRm_direct(ops.reg1.lowId(), ops.reg2.lowId()); - if (imm <= math.maxInt(i8)) { - encoder.imm8(@intCast(i8, imm)); - } else if (imm <= math.maxInt(i16)) { - encoder.imm16(@intCast(i16, imm)); - } else { - encoder.imm32(imm); - } + return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), imm, emit.code); }, else => return emit.fail("TODO implement imul", .{}), } @@ -1842,3 +1924,12 @@ test "lower O encoding" { try lowerToOEnc(.push, .r12w, code.buffer()); try expectEqualHexStrings("\x66\x41\x54", code.emitted(), "push r12w"); } + +test "lower RMI encoding" { + var code = TestEmitCode.init(); + defer code.deinit(); + try lowerToRmiEnc(.imul, .rax, RegisterOrMemory.mem(.rbp, -8), 0x10, code.buffer()); + try expectEqualHexStrings("\x48\x69\x45\xF8\x10\x00\x00\x00", code.emitted(), "imul rax, [rbp - 8], 0x10"); + try lowerToRmiEnc(.imul, .r12, RegisterOrMemory.reg(.r12), 0x10, code.buffer()); + try expectEqualHexStrings("\x4D\x69\xE4\x10\x00\x00\x00", code.emitted(), "imul r12, r12, 0x10"); +} From 4c119866507b244dc0658b5cb43e20295f1888d7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 23 Dec 2021 16:39:28 -0800 Subject: [PATCH 19/19] MIR: remove unnecessary TODO comment it wouldn't save any bytes in the MIR, and we can just check the range of the value when lowering the MIR to machine code. --- src/arch/x86_64/Mir.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/src/arch/x86_64/Mir.zig b/src/arch/x86_64/Mir.zig index fbc4fd320b..5bd3c53004 100644 --- a/src/arch/x86_64/Mir.zig +++ b/src/arch/x86_64/Mir.zig @@ -293,7 +293,6 @@ pub const Inst = struct { /// Another instruction. inst: Index, /// A 32-bit immediate value. - /// TODO we should add support for 16- and 8-bit immediate values. imm: i32, /// An extern function. /// Index into the linker's string table.