zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit e7ac05e882fa4290af4a41e9cae63105bcacb283 (tree)
parent 2d9508780a3578ecddb4948eb459768ad5bf8720
Author: Jakub Konka <kubkon@jakubkonka.com>
Date:   Fri, 31 Dec 2021 11:18:23 +0100

stage2: rename Emit to Isel for x86_64

Diffstat:
MCMakeLists.txt | 2+-
Msrc/arch/x86_64/CodeGen.zig | 10+++++-----
Dsrc/arch/x86_64/Emit.zig | 2132-------------------------------------------------------------------------------
Asrc/arch/x86_64/Isel.zig | 2132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 2138 insertions(+), 2138 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -578,7 +578,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/arch/wasm/Emit.zig" "${CMAKE_SOURCE_DIR}/src/arch/wasm/Mir.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/CodeGen.zig" - "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Emit.zig" + "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Isel.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/Mir.zig" "${CMAKE_SOURCE_DIR}/src/arch/x86_64/bits.zig" "${CMAKE_SOURCE_DIR}/src/clang.zig" diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig @@ -14,10 +14,10 @@ const Allocator = mem.Allocator; const Compilation = @import("../../Compilation.zig"); const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput; const DW = std.dwarf; -const Emit = @import("Emit.zig"); const ErrorMsg = Module.ErrorMsg; const FnResult = @import("../../codegen.zig").FnResult; const GenerateSymbolError = @import("../../codegen.zig").GenerateSymbolError; +const Isel = @import("Isel.zig"); const Liveness = @import("../../Liveness.zig"); const Mir = @import("Mir.zig"); const Module = @import("../../Module.zig"); @@ -309,7 +309,7 @@ pub fn generate( }; defer mir.deinit(bin_file.allocator); - var emit = Emit{ + var isel = Isel{ .mir = mir, .bin_file = bin_file, .debug_output = debug_output, @@ -320,9 +320,9 @@ pub fn generate( .prev_di_line = module_fn.lbrace_line, .prev_di_column = module_fn.lbrace_column, }; - defer emit.deinit(); - emit.emitMir() catch |err| switch (err) { - error.EmitFail => return FnResult{ .fail = emit.err_msg.? }, + defer isel.deinit(); + isel.lowerMir() catch |err| switch (err) { + error.IselFail => return FnResult{ .fail = isel.err_msg.? }, else => |e| return e, }; diff --git a/src/arch/x86_64/Emit.zig b/src/arch/x86_64/Emit.zig @@ -1,2132 +0,0 @@ -//! This file contains the functionality for lowering x86_64 MIR into -//! machine code - -const Emit = @This(); - -const std = @import("std"); -const assert = std.debug.assert; -const bits = @import("bits.zig"); -const leb128 = std.leb; -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; -const ErrorMsg = Module.ErrorMsg; -const MCValue = @import("CodeGen.zig").MCValue; -const Mir = @import("Mir.zig"); -const Module = @import("../../Module.zig"); -const Instruction = bits.Instruction; -const Register = bits.Register; -const Type = @import("../../type.zig").Type; - -mir: Mir, -bin_file: *link.File, -debug_output: DebugInfoOutput, -target: *const std.Target, -err_msg: ?*ErrorMsg = null, -src_loc: Module.SrcLoc, -code: *std.ArrayList(u8), - -prev_di_line: u32, -prev_di_column: u32, -/// Relative to the beginning of `code`. -prev_di_pc: usize, - -code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .{}, -relocs: std.ArrayListUnmanaged(Reloc) = .{}, - -const InnerError = error{ - OutOfMemory, - Overflow, - EmitFail, -}; - -const Reloc = struct { - /// Offset of the instruction. - source: u64, - /// Target of the relocation. - target: Mir.Inst.Index, - /// Offset of the relocation within the instruction. - offset: u64, - /// Length of the instruction. - length: u5, -}; - -pub fn emitMir(emit: *Emit) InnerError!void { - const mir_tags = emit.mir.instructions.items(.tag); - - for (mir_tags) |tag, index| { - 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), - .mov => try emit.mirArith(.mov, inst), - - .adc_mem_imm => try emit.mirArithMemImm(.adc, inst), - .add_mem_imm => try emit.mirArithMemImm(.add, inst), - .sub_mem_imm => try emit.mirArithMemImm(.sub, inst), - .xor_mem_imm => try emit.mirArithMemImm(.xor, inst), - .and_mem_imm => try emit.mirArithMemImm(.@"and", inst), - .or_mem_imm => try emit.mirArithMemImm(.@"or", inst), - .sbb_mem_imm => try emit.mirArithMemImm(.sbb, inst), - .cmp_mem_imm => try emit.mirArithMemImm(.cmp, inst), - .mov_mem_imm => try emit.mirArithMemImm(.mov, 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 => 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 => 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), - - .lea => try emit.mirLea(inst), - - .imul_complex => try emit.mirIMulComplex(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), - - .cond_jmp_greater_less, - .cond_jmp_above_below, - .cond_jmp_eq_ne, - => try emit.mirCondJmp(tag, 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), - - .syscall => try emit.mirSyscall(), - - .@"test" => try emit.mirTest(inst), - - .brk => try emit.mirBrk(), - .nop => try emit.mirNop(), - - .call_extern => try emit.mirCallExtern(inst), - - .dbg_line => try emit.mirDbgLine(inst), - .dbg_prologue_end => try emit.mirDbgPrologueEnd(inst), - .dbg_epilogue_begin => try emit.mirDbgEpilogueBegin(inst), - .arg_dbg_info => try emit.mirArgDbgInfo(inst), - - .push_regs_from_callee_preserved_regs => try emit.mirPushPopRegsFromCalleePreservedRegs(.push, inst), - .pop_regs_from_callee_preserved_regs => try emit.mirPushPopRegsFromCalleePreservedRegs(.pop, inst), - - else => { - return emit.fail("Implement MIR->Isel lowering for x86_64 for pseudo-inst: {s}", .{tag}); - }, - } - } - - try emit.fixupRelocs(); -} - -pub fn deinit(emit: *Emit) void { - emit.relocs.deinit(emit.bin_file.allocator); - emit.code_offset_mapping.deinit(emit.bin_file.allocator); - emit.* = undefined; -} - -fn fail(emit: *Emit, comptime format: []const u8, args: anytype) InnerError { - @setCold(true); - assert(emit.err_msg == null); - emit.err_msg = try ErrorMsg.create(emit.bin_file.allocator, emit.src_loc, format, args); - return error.EmitFail; -} - -fn failWithLoweringError(emit: *Emit, err: LoweringError) InnerError { - return switch (err) { - error.RaxOperandExpected => emit.fail("Register.rax expected as destination operand", .{}), - error.OperandSizeMismatch => emit.fail("operand size mismatch", .{}), - else => |e| e, - }; -} - -fn fixupRelocs(emit: *Emit) InnerError!void { - // TODO this function currently assumes all relocs via JMP/CALL instructions are 32bit in size. - // This should be reversed like it is done in aarch64 MIR emit code: start with the smallest - // possible resolution, i.e., 8bit, and iteratively converge on the minimum required resolution - // until the entire decl is correctly emitted with all JMP/CALL instructions within range. - for (emit.relocs.items) |reloc| { - const offset = try math.cast(usize, reloc.offset); - const target = emit.code_offset_mapping.get(reloc.target) orelse - return emit.fail("JMP/CALL relocation target not found!", .{}); - const disp = @intCast(i32, @intCast(i64, target) - @intCast(i64, reloc.source + reloc.length)); - mem.writeIntLittle(i32, emit.code.items[offset..][0..4], disp); - } -} - -fn mirBrk(emit: *Emit) InnerError!void { - return lowerToZoEnc(.brk, emit.code) catch |err| emit.failWithLoweringError(err); -} - -fn mirNop(emit: *Emit) InnerError!void { - return lowerToZoEnc(.nop, emit.code) catch |err| emit.failWithLoweringError(err); -} - -fn mirSyscall(emit: *Emit) InnerError!void { - return lowerToZoEnc(.syscall, emit.code) catch |err| emit.failWithLoweringError(err); -} - -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 => { - // PUSH/POP reg - return lowerToOEnc(tag, ops.reg1, emit.code) catch |err| emit.failWithLoweringError(err); - }, - 0b01 => { - // PUSH/POP r/m64 - const imm = emit.mir.instructions.items(.data)[inst].imm; - const ptr_size: Memory.PtrSize = switch (immOpSize(imm)) { - 16 => .word_ptr, - else => .qword_ptr, - }; - return lowerToMEnc(tag, RegisterOrMemory.mem(ops.reg1, imm, ptr_size), emit.code) catch |err| - emit.failWithLoweringError(err); - }, - 0b10 => { - // PUSH imm32 - assert(tag == .push); - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.push, imm, emit.code) catch |err| - emit.failWithLoweringError(err); - }, - 0b11 => unreachable, - } -} -fn mirPushPopRegsFromCalleePreservedRegs(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { - const callee_preserved_regs = bits.callee_preserved_regs; - 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; - lowerToOEnc(.push, reg, emit.code) catch |err| - return emit.failWithLoweringError(err); - } - } else { - // pop in the reverse direction - var i = callee_preserved_regs.len; - while (i > 0) : (i -= 1) { - const reg = callee_preserved_regs[i - 1]; - if ((regs >> @intCast(u5, i - 1)) & 1 == 0) continue; - lowerToOEnc(.pop, reg, emit.code) catch |err| - return emit.failWithLoweringError(err); - } - } -} - -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 source = emit.code.items.len; - lowerToDEnc(tag, 0, emit.code) catch |err| - return emit.failWithLoweringError(err); - try emit.relocs.append(emit.bin_file.allocator, .{ - .source = source, - .target = target, - .offset = emit.code.items.len - 4, - .length = 5, - }); - return; - } - if (ops.reg1 == .none) { - // JMP/CALL [imm] - const imm = emit.mir.instructions.items(.data)[inst].imm; - const ptr_size: Memory.PtrSize = switch (immOpSize(imm)) { - 16 => .word_ptr, - else => .qword_ptr, - }; - return lowerToMEnc(tag, RegisterOrMemory.mem(null, imm, ptr_size), emit.code) catch |err| - emit.failWithLoweringError(err); - } - // JMP/CALL reg - return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code) catch |err| emit.failWithLoweringError(err); -} - -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 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; - lowerToDEnc(tag, 0, emit.code) catch |err| - return emit.failWithLoweringError(err); - try emit.relocs.append(emit.bin_file.allocator, .{ - .source = source, - .target = target, - .offset = emit.code.items.len - 4, - .length = 6, - }); -} - -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 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.to8()), emit.code) catch |err| - emit.failWithLoweringError(err); -} - -fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .@"test"); - const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); - switch (ops.flags) { - 0b00 => { - if (ops.reg2 == .none) { - // TEST r/m64, imm32 - // MI - const imm = emit.mir.instructions.items(.data)[inst].imm; - if (ops.reg1.to64() == .rax) { - // TEST rax, imm32 - // I - return lowerToIEnc(.@"test", imm, emit.code) catch |err| - emit.failWithLoweringError(err); - } - return lowerToMiEnc(.@"test", RegisterOrMemory.reg(ops.reg1), imm, emit.code) catch |err| - emit.failWithLoweringError(err); - } - // TEST r/m64, r64 - return emit.fail("TODO TEST r/m64, r64", .{}); - }, - else => return emit.fail("TODO more TEST alternatives", .{}), - } -} - -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]); - switch (ops.flags) { - 0b00 => { - // RETF imm16 - // I - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.ret_far, imm, emit.code) catch |err| emit.failWithLoweringError(err); - }, - 0b01 => { - return lowerToZoEnc(.ret_far, emit.code) catch |err| emit.failWithLoweringError(err); - }, - 0b10 => { - // RET imm16 - // I - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToIEnc(.ret_near, imm, emit.code) catch |err| emit.failWithLoweringError(err); - }, - 0b11 => { - return lowerToZoEnc(.ret_near, emit.code) catch |err| emit.failWithLoweringError(err); - }, - } -} - -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 => { - if (ops.reg2 == .none) { - // mov reg1, imm32 - // MI - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMiEnc(tag, RegisterOrMemory.reg(ops.reg1), imm, emit.code) catch |err| - emit.failWithLoweringError(err); - } - // mov reg1, reg2 - // RM - return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code) catch |err| - emit.failWithLoweringError(err); - }, - 0b01 => { - // mov reg1, [reg2 + imm32] - // RM - const imm = emit.mir.instructions.items(.data)[inst].imm; - const src_reg: ?Register = if (ops.reg2 == .none) null else ops.reg2; - return lowerToRmEnc( - tag, - ops.reg1, - RegisterOrMemory.mem(src_reg, imm, Memory.PtrSize.fromBits(ops.reg1.size())), - emit.code, - ) catch |err| emit.failWithLoweringError(err); - }, - 0b10 => { - if (ops.reg2 == .none) { - return emit.fail("TODO unused variant: mov reg1, none, 0b10", .{}); - } - // mov [reg1 + imm32], reg2 - // MR - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToMrEnc( - tag, - RegisterOrMemory.mem(ops.reg1, imm, Memory.PtrSize.fromBits(ops.reg2.size())), - ops.reg2, - emit.code, - ) catch |err| emit.failWithLoweringError(err); - }, - 0b11 => { - return emit.fail("TODO unused variant: mov reg1, reg2, 0b11", .{}); - }, - } -} - -fn mirArithMemImm(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { - const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); - assert(ops.reg2 == .none); - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - const ptr_size: Memory.PtrSize = switch (ops.flags) { - 0b00 => .byte_ptr, - 0b01 => .word_ptr, - 0b10 => .dword_ptr, - 0b11 => .qword_ptr, - }; - return lowerToMiEnc( - tag, - RegisterOrMemory.mem(ops.reg1, imm_pair.dest_off, ptr_size), - imm_pair.operand, - emit.code, - ) catch |err| emit.failWithLoweringError(err); -} - -inline fn setRexWRegister(reg: Register) bool { - if (reg.size() == 64) return true; - return switch (reg) { - .ah, .bh, .ch, .dh => true, - else => false, - }; -} - -inline fn immOpSize(imm: i64) u8 { - blk: { - _ = math.cast(i8, imm) catch break :blk; - return 8; - } - blk: { - _ = math.cast(i16, imm) catch break :blk; - return 16; - } - blk: { - _ = math.cast(i32, imm) catch break :blk; - return 32; - } - 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; - // OP reg1, [reg2 + scale*rcx + imm32] - 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(.{ - .w = ops.reg1.size() == 64, - .r = ops.reg1.isExtended(), - .b = ops.reg2.isExtended(), - }); - opc.encode(encoder); - if (imm <= math.maxInt(i8)) { - encoder.modRm_SIBDisp8(ops.reg1.lowId()); - encoder.sib_scaleIndexBaseDisp8(scale, Register.rcx.lowId(), ops.reg2.lowId()); - encoder.disp8(@intCast(i8, imm)); - } else { - encoder.modRm_SIBDisp32(ops.reg1.lowId()); - encoder.sib_scaleIndexBaseDisp32(scale, Register.rcx.lowId(), ops.reg2.lowId()); - encoder.disp32(imm); - } -} - -// 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; - const imm = emit.mir.instructions.items(.data)[inst].imm; - - if (ops.reg2 == .none) { - // OP [reg1 + scale*rax + 0], imm32 - const opc = getOpCode(tag, .mi, ops.reg1.size() == 8).?; - const modrm_ext = getModRmExt(tag).?; - const encoder = try Encoder.init(emit.code, 8); - encoder.rex(.{ - .w = ops.reg1.size() == 64, - .b = ops.reg1.isExtended(), - }); - opc.encode(encoder); - 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)); - } else if (imm <= math.maxInt(i16)) { - encoder.imm16(@intCast(i16, imm)); - } else { - encoder.imm32(imm); - } - return; - } - - // OP [reg1 + scale*rax + imm32], reg2 - 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(), - }); - opc.encode(encoder); - if (imm <= math.maxInt(i8)) { - encoder.modRm_SIBDisp8(ops.reg2.lowId()); - encoder.sib_scaleIndexBaseDisp8(scale, Register.rax.lowId(), ops.reg1.lowId()); - encoder.disp8(@intCast(i8, imm)); - } else { - encoder.modRm_SIBDisp32(ops.reg2.lowId()); - encoder.sib_scaleIndexBaseDisp32(scale, Register.rax.lowId(), ops.reg1.lowId()); - encoder.disp32(imm); - } -} - -// 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; - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm_pair = emit.mir.extraData(Mir.ImmPair, payload).data; - 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(), - }); - 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()); - encoder.disp8(@intCast(i8, imm_pair.dest_off)); - } else { - encoder.modRm_SIBDisp32(modrm_ext); - encoder.sib_scaleIndexBaseDisp32(scale, Register.rax.lowId(), ops.reg1.lowId()); - encoder.disp32(imm_pair.dest_off); - } - encoder.imm32(imm_pair.operand); -} - -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 - return lowerToOiEnc(.mov, ops.reg1, imm, emit.code) catch |err| emit.failWithLoweringError(err); - } - if (ops.reg1 == .none) { - // movabs moffs64, rax - // TD - return lowerToTdEnc(.mov, imm, ops.reg2, emit.code) catch |err| emit.failWithLoweringError(err); - } - // movabs rax, moffs64 - // FD - return lowerToFdEnc(.mov, ops.reg1, imm, emit.code) catch |err| emit.failWithLoweringError(err); -} - -fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .imul_complex); - const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); - switch (ops.flags) { - 0b00 => { - return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code) catch |err| - emit.failWithLoweringError(err); - }, - 0b10 => { - const imm = emit.mir.instructions.items(.data)[inst].imm; - return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), imm, emit.code) catch |err| - emit.failWithLoweringError(err); - }, - else => return emit.fail("TODO implement imul", .{}), - } -} - -fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .lea); - const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); - switch (ops.flags) { - 0b00 => { - // lea reg1, [reg2 + imm32] - // RM - const imm = emit.mir.instructions.items(.data)[inst].imm; - const src_reg: ?Register = if (ops.reg2 == .none) null else ops.reg2; - return lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.mem(src_reg, imm, Memory.PtrSize.fromBits(ops.reg1.size())), - emit.code, - ) catch |err| emit.failWithLoweringError(err); - }, - 0b01 => { - // lea reg1, [rip + imm32] - // RM - const start_offset = emit.code.items.len; - lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.rip(0, Memory.PtrSize.fromBits(ops.reg1.size())), - emit.code, - ) catch |err| return emit.failWithLoweringError(err); - const end_offset = emit.code.items.len; - // Backpatch the displacement - const payload = emit.mir.instructions.items(.data)[inst].payload; - const imm = emit.mir.extraData(Mir.Imm64, payload).data.decode(); - 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); - }, - 0b10 => { - // lea reg1, [rip + reloc] - // RM - lowerToRmEnc( - .lea, - ops.reg1, - RegisterOrMemory.rip(0, Memory.PtrSize.fromBits(ops.reg1.size())), - emit.code, - ) catch |err| return emit.failWithLoweringError(err); - const end_offset = emit.code.items.len; - const got_entry = emit.mir.instructions.items(.data)[inst].got_entry; - 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 - 4), - .target = .{ .local = got_entry }, - .addend = 0, - .subtractor = null, - .pcrel = true, - .length = 2, - .@"type" = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), - }); - } else { - return emit.fail( - "TODO implement lea reg, [rip + reloc] for linking backends different than MachO", - .{}, - ); - } - }, - 0b11 => return emit.fail("TODO unused variant lea reg1, reg2, 0b11", .{}), - } -} - -fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .call_extern); - const n_strx = emit.mir.instructions.items(.data)[inst].extern_fn; - const offset = blk: { - // callq - lowerToDEnc(.call_near, 0, emit.code) catch |err| - return emit.failWithLoweringError(err); - break :blk @intCast(u32, emit.code.items.len) - 4; - }; - if (emit.bin_file.cast(link.File.MachO)) |macho_file| { - // Add relocation to the decl. - try macho_file.active_decl.?.link.macho.relocs.append(emit.bin_file.allocator, .{ - .offset = offset, - .target = .{ .global = n_strx }, - .addend = 0, - .subtractor = null, - .pcrel = true, - .length = 2, - .@"type" = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), - }); - } else { - return emit.fail("TODO implement call_extern for linking backends different than MachO", .{}); - } -} - -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; - try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column); -} - -fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) InnerError!void { - const delta_line = @intCast(i32, line) - @intCast(i32, emit.prev_di_line); - const delta_pc: usize = emit.code.items.len - emit.prev_di_pc; - switch (emit.debug_output) { - .dwarf => |dbg_out| { - // TODO Look into using the DWARF special opcodes to compress this data. - // It lets you emit single-byte opcodes that add different numbers to - // both the PC and the line number at the same time. - try dbg_out.dbg_line.ensureUnusedCapacity(11); - dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_pc); - leb128.writeULEB128(dbg_out.dbg_line.writer(), delta_pc) catch unreachable; - if (delta_line != 0) { - dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_line); - leb128.writeILEB128(dbg_out.dbg_line.writer(), delta_line) catch unreachable; - } - dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.copy); - emit.prev_di_pc = emit.code.items.len; - emit.prev_di_line = line; - emit.prev_di_column = column; - emit.prev_di_pc = emit.code.items.len; - }, - .plan9 => |dbg_out| { - if (delta_pc <= 0) return; // only do this when the pc changes - // we have already checked the target in the linker to make sure it is compatable - const quant = @import("../../link/Plan9/aout.zig").getPCQuant(emit.target.cpu.arch) catch unreachable; - - // increasing the line number - try @import("../../link/Plan9.zig").changeLine(dbg_out.dbg_line, delta_line); - // increasing the pc - const d_pc_p9 = @intCast(i64, delta_pc) - quant; - if (d_pc_p9 > 0) { - // minus one because if its the last one, we want to leave space to change the line which is one quanta - try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant); - if (dbg_out.pcop_change_index.*) |pci| - dbg_out.dbg_line.items[pci] += 1; - dbg_out.pcop_change_index.* = @intCast(u32, dbg_out.dbg_line.items.len - 1); - } else if (d_pc_p9 == 0) { - // we don't need to do anything, because adding the quant does it for us - } else unreachable; - if (dbg_out.start_line.* == null) - dbg_out.start_line.* = emit.prev_di_line; - dbg_out.end_line.* = line; - // only do this if the pc changed - emit.prev_di_line = line; - emit.prev_di_column = column; - emit.prev_di_pc = emit.code.items.len; - }, - .none => {}, - } -} - -fn mirDbgPrologueEnd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .dbg_prologue_end); - switch (emit.debug_output) { - .dwarf => |dbg_out| { - try dbg_out.dbg_line.append(DW.LNS.set_prologue_end); - try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column); - }, - .plan9 => {}, - .none => {}, - } -} - -fn mirDbgEpilogueBegin(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { - const tag = emit.mir.instructions.items(.tag)[inst]; - assert(tag == .dbg_epilogue_begin); - switch (emit.debug_output) { - .dwarf => |dbg_out| { - try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin); - try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column); - }, - .plan9 => {}, - .none => {}, - } -} - -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 mcv = emit.mir.function.args[arg_dbg_info.arg_index]; - try emit.genArgDbgInfo(arg_dbg_info.air_inst, mcv); -} - -fn genArgDbgInfo(emit: *Emit, inst: Air.Inst.Index, mcv: MCValue) !void { - const ty_str = emit.mir.function.air.instructions.items(.data)[inst].ty_str; - const zir = &emit.mir.function.mod_fn.owner_decl.getFileScope().zir; - const name = zir.nullTerminatedString(ty_str.str); - const name_with_null = name.ptr[0 .. name.len + 1]; - const ty = emit.mir.function.air.getRefType(ty_str.ty); - - switch (mcv) { - .register => |reg| { - switch (emit.debug_output) { - .dwarf => |dbg_out| { - try dbg_out.dbg_info.ensureUnusedCapacity(3); - dbg_out.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); - dbg_out.dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc - 1, // ULEB128 dwarf expression length - reg.dwarfLocOp(), - }); - try dbg_out.dbg_info.ensureUnusedCapacity(5 + name_with_null.len); - try emit.addDbgInfoTypeReloc(ty); // DW.AT.type, DW.FORM.ref4 - dbg_out.dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string - }, - .plan9 => {}, - .none => {}, - } - }, - .stack_offset => { - switch (emit.debug_output) { - .dwarf => {}, - .plan9 => {}, - .none => {}, - } - }, - else => {}, - } -} - -/// Adds a Type to the .debug_info at the current position. The bytes will be populated later, -/// after codegen for this symbol is done. -fn addDbgInfoTypeReloc(emit: *Emit, ty: Type) !void { - switch (emit.debug_output) { - .dwarf => |dbg_out| { - assert(ty.hasCodeGenBits()); - const index = dbg_out.dbg_info.items.len; - try dbg_out.dbg_info.resize(index + 4); // DW.AT.type, DW.FORM.ref4 - - const gop = try dbg_out.dbg_info_type_relocs.getOrPut(emit.bin_file.allocator, ty); - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .off = undefined, - .relocs = .{}, - }; - } - try gop.value_ptr.relocs.append(emit.bin_file.allocator, @intCast(u32, index)); - }, - .plan9 => {}, - .none => {}, - } -} - -const Tag = enum { - adc, - add, - sub, - xor, - @"and", - @"or", - sbb, - cmp, - mov, - lea, - jmp_near, - call_near, - push, - pop, - @"test", - brk, - nop, - imul, - 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 { - /// OP - zo, - - /// OP rel32 - d, - - /// OP r/m64 - m, - - /// OP r64 - o, - - /// OP imm32 - i, - - /// OP r/m64, imm32 - mi, - - /// OP r/m64, r64 - mr, - - /// OP r64, r/m64 - rm, - - /// OP r64, imm64 - oi, - - /// OP al/ax/eax/rax, moffs - fd, - - /// OP moffs, al/ax/eax/rax - td, - - /// OP r64, r/m64, imm32 - rmi, -}; - -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 => 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 => 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) { - .push => OpCode.oneByte(0x50), - .pop => OpCode.oneByte(0x58), - else => null, - }, - .i => return switch (tag) { - .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 => 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 => 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 => 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), - .imul => OpCode.twoByte(0x0f, 0xaf), - else => null, - }, - .oi => return switch (tag) { - .mov => OpCode.oneByte(if (is_one_byte) 0xb0 else 0xb8), - else => null, - }, - .fd => return switch (tag) { - .mov => OpCode.oneByte(if (is_one_byte) 0xa0 else 0xa1), - else => null, - }, - .td => return switch (tag) { - .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, - }, - } -} - -inline fn getModRmExt(tag: Tag) ?u3 { - return switch (tag) { - .adc => 0x2, - .add => 0x0, - .sub => 0x5, - .xor => 0x6, - .@"and" => 0x4, - .@"or" => 0x1, - .sbb => 0x3, - .cmp => 0x7, - .mov => 0x0, - .jmp_near => 0x4, - .call_near => 0x2, - .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, - }; -} - -const ScaleIndexBase = struct { - scale: u2, - index_reg: ?Register, - base_reg: ?Register, -}; - -const Memory = struct { - reg: ?Register, - rip: bool = false, - disp: i32, - ptr_size: PtrSize, - sib: ?ScaleIndexBase = null, - - const PtrSize = enum { - byte_ptr, - word_ptr, - dword_ptr, - qword_ptr, - - fn fromBits(in_bits: u64) PtrSize { - return switch (in_bits) { - 8 => .byte_ptr, - 16 => .word_ptr, - 32 => .dword_ptr, - 64 => .qword_ptr, - else => unreachable, - }; - } - - /// Returns size in bits. - fn size(ptr_size: PtrSize) u64 { - return switch (ptr_size) { - .byte_ptr => 8, - .word_ptr => 16, - .dword_ptr => 32, - .qword_ptr => 64, - }; - } - }; -}; - -const RegisterOrMemory = union(enum) { - register: Register, - memory: Memory, - - fn reg(register: Register) RegisterOrMemory { - return .{ .register = register }; - } - - fn mem(register: ?Register, disp: i32, ptr_size: Memory.PtrSize) RegisterOrMemory { - return .{ - .memory = .{ - .reg = register, - .disp = disp, - .ptr_size = ptr_size, - }, - }; - } - - fn rip(disp: i32, ptr_size: Memory.PtrSize) RegisterOrMemory { - return .{ - .memory = .{ - .reg = null, - .rip = true, - .disp = disp, - .ptr_size = ptr_size, - }, - }; - } -}; - -const LoweringError = error{ - OutOfMemory, - Overflow, - OperandSizeMismatch, - RaxOperandExpected, -}; - -fn lowerToZoEnc(tag: Tag, code: *std.ArrayList(u8)) LoweringError!void { - const opc = getOpCode(tag, .zo, false).?; - const encoder = try Encoder.init(code, 1); - opc.encode(encoder); -} - -fn lowerToIEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) LoweringError!void { - if (tag == .ret_far or tag == .ret_near) { - const encoder = try Encoder.init(code, 3); - const opc = getOpCode(tag, .i, false).?; - opc.encode(encoder); - encoder.imm16(@intCast(i16, imm)); - return; - } - const opc = getOpCode(tag, .i, immOpSize(imm) == 8).?; - const encoder = try Encoder.init(code, 5); - if (immOpSize(imm) == 16) { - encoder.opcode_1byte(0x66); - } - opc.encode(encoder); - 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)) LoweringError!void { - if (reg.size() != 16 and reg.size() != 64) { - return error.OperandSizeMismatch; // TODO correct for push/pop, but is it universal? - } - const opc = getOpCode(tag, .o, false).?; - const encoder = try Encoder.init(code, 3); - if (reg.size() == 16) { - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = false, - .b = reg.isExtended(), - }); - opc.encodeWithReg(encoder, reg); -} - -fn lowerToDEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) LoweringError!void { - const opc = getOpCode(tag, .d, false).?; - const encoder = try Encoder.init(code, 6); - opc.encode(encoder); - encoder.imm32(imm); -} - -fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) LoweringError!void { - const opc = getOpCode(tag, .m, false).?; - const modrm_ext = getModRmExt(tag).?; - switch (reg_or_mem) { - .register => |reg| { - const op_size_mismatch = blk: { - if (tag.isSetCC() and reg.size() == 8) - break :blk false; - break :blk reg.size() != 64 and reg.size() != 16; - }; - if (op_size_mismatch) { - return error.OperandSizeMismatch; - } - const encoder = try Encoder.init(code, 4); - if (reg.size() == 16) { - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = switch (reg) { - .ah, .bh, .ch, .dh => true, - else => false, - }, - .b = reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(modrm_ext, reg.lowId()); - }, - .memory => |mem_op| { - if (mem_op.ptr_size != .qword_ptr and mem_op.ptr_size != .word_ptr) { - return error.OperandSizeMismatch; - } - const encoder = try Encoder.init(code, 8); - if (mem_op.ptr_size == .word_ptr) { - encoder.opcode_1byte(0x66); - } - if (mem_op.reg) |reg| { - if (reg.size() != 64) { - return error.OperandSizeMismatch; - } - encoder.rex(.{ - .w = false, - .b = reg.isExtended(), - }); - opc.encode(encoder); - 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 { - opc.encode(encoder); - 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)) LoweringError!void { - return lowerToTdFdEnc(tag, reg, moffs, code, true); -} - -fn lowerToFdEnc(tag: Tag, reg: Register, moffs: i64, code: *std.ArrayList(u8)) LoweringError!void { - return lowerToTdFdEnc(tag, reg, moffs, code, false); -} - -fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: i64, code: *std.ArrayList(u8), td: bool) LoweringError!void { - if (reg.lowId() != Register.rax.lowId()) { - return error.RaxOperandExpected; - } - if (reg.size() != immOpSize(moffs)) { - return error.OperandSizeMismatch; - } - 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); - } - encoder.rex(.{ - .w = setRexWRegister(reg), - }); - opc.encode(encoder); - 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: Tag, reg: Register, imm: i64, code: *std.ArrayList(u8)) LoweringError!void { - if (reg.size() != immOpSize(imm)) { - return error.OperandSizeMismatch; - } - const opc = getOpCode(tag, .oi, reg.size() == 8).?; - const encoder = try Encoder.init(code, 10); - if (reg.size() == 16) { - encoder.opcode_1byte(0x66); - } - encoder.rex(.{ - .w = setRexWRegister(reg), - .b = reg.isExtended(), - }); - opc.encodeWithReg(encoder, reg); - 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: Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.ArrayList(u8)) LoweringError!void { - const modrm_ext = getModRmExt(tag).?; - switch (reg_or_mem) { - .register => |dst_reg| { - 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 - // 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 = setRexWRegister(dst_reg), - .b = dst_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(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 opc = getOpCode(tag, .mi, dst_mem.ptr_size == .byte_ptr).?; - const encoder = try Encoder.init(code, 12); - if (dst_mem.ptr_size == .word_ptr) { - encoder.opcode_1byte(0x66); - } - if (dst_mem.reg) |dst_reg| { - if (dst_reg.size() != 64) { - return error.OperandSizeMismatch; - } - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr, - .b = dst_reg.isExtended(), - }); - opc.encode(encoder); - 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); - } - } - } else { - opc.encode(encoder); - if (dst_mem.rip) { - encoder.modRm_RIPDisp32(modrm_ext); - } else { - encoder.modRm_SIBDisp0(modrm_ext); - encoder.sib_disp32(); - } - encoder.disp32(dst_mem.disp); - } - switch (dst_mem.ptr_size) { - .byte_ptr => { - const imm8 = try math.cast(i8, imm); - encoder.imm8(imm8); - }, - .word_ptr => { - const imm16 = try math.cast(i16, imm); - encoder.imm16(imm16); - }, - .dword_ptr, .qword_ptr => { - encoder.imm32(imm); - }, - } - }, - } -} - -fn lowerToRmEnc( - tag: Tag, - reg: Register, - reg_or_mem: RegisterOrMemory, - code: *std.ArrayList(u8), -) LoweringError!void { - const opc = getOpCode(tag, .rm, reg.size() == 8).?; - switch (reg_or_mem) { - .register => |src_reg| { - if (reg.size() != src_reg.size()) { - return error.OperandSizeMismatch; - } - const encoder = try Encoder.init(code, 3); - encoder.rex(.{ - .w = setRexWRegister(reg) or setRexWRegister(src_reg), - .r = reg.isExtended(), - .b = src_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(reg.lowId(), src_reg.lowId()); - }, - .memory => |src_mem| { - if (reg.size() != src_mem.ptr_size.size()) { - return error.OperandSizeMismatch; - } - 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.OperandSizeMismatch; - } - encoder.rex(.{ - .w = setRexWRegister(reg), - .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 = setRexWRegister(reg), - .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); - } - }, - } -} - -fn lowerToMrEnc( - tag: Tag, - reg_or_mem: RegisterOrMemory, - reg: Register, - code: *std.ArrayList(u8), -) LoweringError!void { - const opc = getOpCode(tag, .mr, reg.size() == 8).?; - switch (reg_or_mem) { - .register => |dst_reg| { - if (dst_reg.size() != reg.size()) { - return error.OperandSizeMismatch; - } - const encoder = try Encoder.init(code, 3); - encoder.rex(.{ - .w = setRexWRegister(dst_reg) or setRexWRegister(reg), - .r = reg.isExtended(), - .b = dst_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(reg.lowId(), dst_reg.lowId()); - }, - .memory => |dst_mem| { - if (dst_mem.ptr_size.size() != reg.size()) { - return error.OperandSizeMismatch; - } - 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.OperandSizeMismatch; - } - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg), - .r = reg.isExtended(), - .b = dst_reg.isExtended(), - }); - opc.encode(encoder); - 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); - } - } - } else { - encoder.rex(.{ - .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg), - .r = reg.isExtended(), - }); - opc.encode(encoder); - if (dst_mem.rip) { - encoder.modRm_RIPDisp32(reg.lowId()); - } else { - encoder.modRm_SIBDisp0(reg.lowId()); - encoder.sib_disp32(); - } - encoder.disp32(dst_mem.disp); - } - }, - } -} - -fn lowerToRmiEnc( - tag: Tag, - reg: Register, - reg_or_mem: RegisterOrMemory, - imm: i32, - code: *std.ArrayList(u8), -) LoweringError!void { - if (reg.size() == 8) { - return error.OperandSizeMismatch; - } - const opc = getOpCode(tag, .rmi, false).?; - const encoder = try Encoder.init(code, 13); - if (reg.size() == 16) { - encoder.opcode_1byte(0x66); - } - switch (reg_or_mem) { - .register => |src_reg| { - if (reg.size() != src_reg.size()) { - return error.OperandSizeMismatch; - } - encoder.rex(.{ - .w = setRexWRegister(reg) or setRexWRegister(src_reg), - .r = reg.isExtended(), - .b = src_reg.isExtended(), - }); - opc.encode(encoder); - encoder.modRm_direct(reg.lowId(), src_reg.lowId()); - }, - .memory => |src_mem| { - 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.OperandSizeMismatch; - } - if (src_mem.ptr_size == .byte_ptr) { - return error.OperandSizeMismatch; - } - encoder.rex(.{ - .w = setRexWRegister(reg), - .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 = setRexWRegister(reg), - .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); - } - }, - } - switch (reg.size()) { - // TODO 8bit immediate - 8 => unreachable, - 16 => { - const imm16 = try math.cast(i16, imm); - encoder.imm16(imm16); - }, - 32, 64 => encoder.imm32(imm), - else => unreachable, - } -} - -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; -} - -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, .dword_ptr), 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, .dword_ptr), 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, .dword_ptr), 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, .dword_ptr), 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, .dword_ptr), 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", - ); - try lowerToMiEnc(.mov, RegisterOrMemory.rip(0x10, .qword_ptr), 0x10, code.buffer()); - try expectEqualHexStrings( - "\xC7\x05\x10\x00\x00\x00\x10\x00\x00\x00", - code.emitted(), - "mov qword ptr [rip + 0x10], 0x10", - ); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.rbp, -8, .qword_ptr), 0x10, code.buffer()); - try expectEqualHexStrings( - "\x48\xc7\x45\xf8\x10\x00\x00\x00", - code.emitted(), - "mov qword ptr [rbp - 8], 0x10", - ); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.rbp, -2, .word_ptr), 0x10, code.buffer()); - try expectEqualHexStrings("\x66\xC7\x45\xFE\x10\x00", code.emitted(), "mov word ptr [rbp - 2], 0x10"); - try lowerToMiEnc(.mov, RegisterOrMemory.mem(.rbp, -1, .byte_ptr), 0x10, code.buffer()); - try expectEqualHexStrings("\xC6\x45\xFF\x10", code.emitted(), "mov byte ptr [rbp - 1], 0x10"); -} - -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, .qword_ptr), code.buffer()); - try expectEqualHexStrings("\x49\x8b\x03", code.emitted(), "mov rax, qword ptr [r11 + 0]"); - try lowerToRmEnc(.add, .r11, RegisterOrMemory.mem(null, 0x10000000, .qword_ptr), 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, .byte_ptr), 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, .qword_ptr), 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, .qword_ptr), 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, .qword_ptr), code.buffer()); - try expectEqualHexStrings("\x48\x8B\x45\xFC", code.emitted(), "mov rax, qword ptr [rbp - 4]"); - try lowerToRmEnc(.lea, .rax, RegisterOrMemory.rip(0x10, .qword_ptr), code.buffer()); - try expectEqualHexStrings("\x48\x8D\x05\x10\x00\x00\x00", code.emitted(), "lea rax, [rip + 0x10]"); -} - -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, .qword_ptr), .r11, code.buffer()); - try expectEqualHexStrings("\x4c\x89\x5d\xfc", code.emitted(), "mov qword ptr [rbp - 4], r11"); - try lowerToMrEnc(.add, RegisterOrMemory.mem(null, 0x10000000, .byte_ptr), .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, .dword_ptr), .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, .qword_ptr), .r12, code.buffer()); - try expectEqualHexStrings( - "\x4D\x29\xA3\x00\x00\x00\x10", - code.emitted(), - "sub qword ptr [r11 + 0x10000000], r12", - ); - try lowerToMrEnc(.mov, RegisterOrMemory.rip(0x10, .qword_ptr), .r12, code.buffer()); - try expectEqualHexStrings("\x4C\x89\x25\x10\x00\x00\x00", code.emitted(), "mov qword ptr [rip + 0x10], 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, .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"); -} - -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.reg(.r12w), code.buffer()); - try expectEqualHexStrings("\x66\x41\xFF\xE4", code.emitted(), "jmp r12w"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0, .qword_ptr), code.buffer()); - try expectEqualHexStrings("\x41\xFF\x24\x24", code.emitted(), "jmp qword ptr [r12]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0, .word_ptr), code.buffer()); - try expectEqualHexStrings("\x66\x41\xFF\x24\x24", code.emitted(), "jmp word ptr [r12]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0x10, .qword_ptr), code.buffer()); - try expectEqualHexStrings("\x41\xFF\x64\x24\x10", code.emitted(), "jmp qword ptr [r12 + 0x10]"); - try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0x1000, .qword_ptr), 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, .qword_ptr), 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, .qword_ptr), 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("\x41\x0F\x97\xC3", code.emitted(), "seta r11b"); -} - -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"); -} - -test "lower RMI encoding" { - var code = TestEmitCode.init(); - defer code.deinit(); - try lowerToRmiEnc(.imul, .rax, RegisterOrMemory.mem(.rbp, -8, .qword_ptr), 0x10, code.buffer()); - try expectEqualHexStrings( - "\x48\x69\x45\xF8\x10\x00\x00\x00", - code.emitted(), - "imul rax, qword ptr [rbp - 8], 0x10", - ); - try lowerToRmiEnc(.imul, .eax, RegisterOrMemory.mem(.rbp, -4, .dword_ptr), 0x10, code.buffer()); - try expectEqualHexStrings("\x69\x45\xFC\x10\x00\x00\x00", code.emitted(), "imul eax, dword ptr [rbp - 4], 0x10"); - try lowerToRmiEnc(.imul, .ax, RegisterOrMemory.mem(.rbp, -2, .word_ptr), 0x10, code.buffer()); - try expectEqualHexStrings("\x66\x69\x45\xFE\x10\x00", code.emitted(), "imul ax, word ptr [rbp - 2], 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"); - try lowerToRmiEnc(.imul, .r12w, RegisterOrMemory.reg(.r12w), 0x10, code.buffer()); - try expectEqualHexStrings("\x66\x45\x69\xE4\x10\x00", code.emitted(), "imul r12w, r12w, 0x10"); -} diff --git a/src/arch/x86_64/Isel.zig b/src/arch/x86_64/Isel.zig @@ -0,0 +1,2132 @@ +//! This file contains the functionality for lowering x86_64 MIR into +//! machine code + +const Isel = @This(); + +const std = @import("std"); +const assert = std.debug.assert; +const bits = @import("bits.zig"); +const leb128 = std.leb; +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; +const ErrorMsg = Module.ErrorMsg; +const MCValue = @import("CodeGen.zig").MCValue; +const Mir = @import("Mir.zig"); +const Module = @import("../../Module.zig"); +const Instruction = bits.Instruction; +const Register = bits.Register; +const Type = @import("../../type.zig").Type; + +mir: Mir, +bin_file: *link.File, +debug_output: DebugInfoOutput, +target: *const std.Target, +err_msg: ?*ErrorMsg = null, +src_loc: Module.SrcLoc, +code: *std.ArrayList(u8), + +prev_di_line: u32, +prev_di_column: u32, +/// Relative to the beginning of `code`. +prev_di_pc: usize, + +code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .{}, +relocs: std.ArrayListUnmanaged(Reloc) = .{}, + +const InnerError = error{ + OutOfMemory, + Overflow, + IselFail, +}; + +const Reloc = struct { + /// Offset of the instruction. + source: u64, + /// Target of the relocation. + target: Mir.Inst.Index, + /// Offset of the relocation within the instruction. + offset: u64, + /// Length of the instruction. + length: u5, +}; + +pub fn lowerMir(isel: *Isel) InnerError!void { + const mir_tags = isel.mir.instructions.items(.tag); + + for (mir_tags) |tag, index| { + const inst = @intCast(u32, index); + try isel.code_offset_mapping.putNoClobber(isel.bin_file.allocator, inst, isel.code.items.len); + switch (tag) { + .adc => try isel.mirArith(.adc, inst), + .add => try isel.mirArith(.add, inst), + .sub => try isel.mirArith(.sub, inst), + .xor => try isel.mirArith(.xor, inst), + .@"and" => try isel.mirArith(.@"and", inst), + .@"or" => try isel.mirArith(.@"or", inst), + .sbb => try isel.mirArith(.sbb, inst), + .cmp => try isel.mirArith(.cmp, inst), + .mov => try isel.mirArith(.mov, inst), + + .adc_mem_imm => try isel.mirArithMemImm(.adc, inst), + .add_mem_imm => try isel.mirArithMemImm(.add, inst), + .sub_mem_imm => try isel.mirArithMemImm(.sub, inst), + .xor_mem_imm => try isel.mirArithMemImm(.xor, inst), + .and_mem_imm => try isel.mirArithMemImm(.@"and", inst), + .or_mem_imm => try isel.mirArithMemImm(.@"or", inst), + .sbb_mem_imm => try isel.mirArithMemImm(.sbb, inst), + .cmp_mem_imm => try isel.mirArithMemImm(.cmp, inst), + .mov_mem_imm => try isel.mirArithMemImm(.mov, inst), + + .adc_scale_src => try isel.mirArithScaleSrc(.adc, inst), + .add_scale_src => try isel.mirArithScaleSrc(.add, inst), + .sub_scale_src => try isel.mirArithScaleSrc(.sub, inst), + .xor_scale_src => try isel.mirArithScaleSrc(.xor, inst), + .and_scale_src => try isel.mirArithScaleSrc(.@"and", inst), + .or_scale_src => try isel.mirArithScaleSrc(.@"or", inst), + .sbb_scale_src => try isel.mirArithScaleSrc(.sbb, inst), + .cmp_scale_src => try isel.mirArithScaleSrc(.cmp, inst), + .mov_scale_src => try isel.mirArithScaleSrc(.mov, inst), + + .adc_scale_dst => try isel.mirArithScaleDst(.adc, inst), + .add_scale_dst => try isel.mirArithScaleDst(.add, inst), + .sub_scale_dst => try isel.mirArithScaleDst(.sub, inst), + .xor_scale_dst => try isel.mirArithScaleDst(.xor, inst), + .and_scale_dst => try isel.mirArithScaleDst(.@"and", inst), + .or_scale_dst => try isel.mirArithScaleDst(.@"or", inst), + .sbb_scale_dst => try isel.mirArithScaleDst(.sbb, inst), + .cmp_scale_dst => try isel.mirArithScaleDst(.cmp, inst), + .mov_scale_dst => try isel.mirArithScaleDst(.mov, inst), + + .adc_scale_imm => try isel.mirArithScaleImm(.adc, inst), + .add_scale_imm => try isel.mirArithScaleImm(.add, inst), + .sub_scale_imm => try isel.mirArithScaleImm(.sub, inst), + .xor_scale_imm => try isel.mirArithScaleImm(.xor, inst), + .and_scale_imm => try isel.mirArithScaleImm(.@"and", inst), + .or_scale_imm => try isel.mirArithScaleImm(.@"or", inst), + .sbb_scale_imm => try isel.mirArithScaleImm(.sbb, inst), + .cmp_scale_imm => try isel.mirArithScaleImm(.cmp, inst), + .mov_scale_imm => try isel.mirArithScaleImm(.mov, inst), + + .movabs => try isel.mirMovabs(inst), + + .lea => try isel.mirLea(inst), + + .imul_complex => try isel.mirIMulComplex(inst), + + .push => try isel.mirPushPop(.push, inst), + .pop => try isel.mirPushPop(.pop, inst), + + .jmp => try isel.mirJmpCall(.jmp_near, inst), + .call => try isel.mirJmpCall(.call_near, inst), + + .cond_jmp_greater_less, + .cond_jmp_above_below, + .cond_jmp_eq_ne, + => try isel.mirCondJmp(tag, inst), + + .cond_set_byte_greater_less, + .cond_set_byte_above_below, + .cond_set_byte_eq_ne, + => try isel.mirCondSetByte(tag, inst), + + .ret => try isel.mirRet(inst), + + .syscall => try isel.mirSyscall(), + + .@"test" => try isel.mirTest(inst), + + .brk => try isel.mirBrk(), + .nop => try isel.mirNop(), + + .call_extern => try isel.mirCallExtern(inst), + + .dbg_line => try isel.mirDbgLine(inst), + .dbg_prologue_end => try isel.mirDbgPrologueEnd(inst), + .dbg_epilogue_begin => try isel.mirDbgEpilogueBegin(inst), + .arg_dbg_info => try isel.mirArgDbgInfo(inst), + + .push_regs_from_callee_preserved_regs => try isel.mirPushPopRegsFromCalleePreservedRegs(.push, inst), + .pop_regs_from_callee_preserved_regs => try isel.mirPushPopRegsFromCalleePreservedRegs(.pop, inst), + + else => { + return isel.fail("Implement MIR->Isel lowering for x86_64 for pseudo-inst: {s}", .{tag}); + }, + } + } + + try isel.fixupRelocs(); +} + +pub fn deinit(isel: *Isel) void { + isel.relocs.deinit(isel.bin_file.allocator); + isel.code_offset_mapping.deinit(isel.bin_file.allocator); + isel.* = undefined; +} + +fn fail(isel: *Isel, comptime format: []const u8, args: anytype) InnerError { + @setCold(true); + assert(isel.err_msg == null); + isel.err_msg = try ErrorMsg.create(isel.bin_file.allocator, isel.src_loc, format, args); + return error.IselFail; +} + +fn failWithLoweringError(isel: *Isel, err: LoweringError) InnerError { + return switch (err) { + error.RaxOperandExpected => isel.fail("Register.rax expected as destination operand", .{}), + error.OperandSizeMismatch => isel.fail("operand size mismatch", .{}), + else => |e| e, + }; +} + +fn fixupRelocs(isel: *Isel) InnerError!void { + // TODO this function currently assumes all relocs via JMP/CALL instructions are 32bit in size. + // This should be reversed like it is done in aarch64 MIR emit code: start with the smallest + // possible resolution, i.e., 8bit, and iteratively converge on the minimum required resolution + // until the entire decl is correctly emitted with all JMP/CALL instructions within range. + for (isel.relocs.items) |reloc| { + const offset = try math.cast(usize, reloc.offset); + const target = isel.code_offset_mapping.get(reloc.target) orelse + return isel.fail("JMP/CALL relocation target not found!", .{}); + const disp = @intCast(i32, @intCast(i64, target) - @intCast(i64, reloc.source + reloc.length)); + mem.writeIntLittle(i32, isel.code.items[offset..][0..4], disp); + } +} + +fn mirBrk(isel: *Isel) InnerError!void { + return lowerToZoEnc(.brk, isel.code) catch |err| isel.failWithLoweringError(err); +} + +fn mirNop(isel: *Isel) InnerError!void { + return lowerToZoEnc(.nop, isel.code) catch |err| isel.failWithLoweringError(err); +} + +fn mirSyscall(isel: *Isel) InnerError!void { + return lowerToZoEnc(.syscall, isel.code) catch |err| isel.failWithLoweringError(err); +} + +fn mirPushPop(isel: *Isel, tag: Tag, inst: Mir.Inst.Index) InnerError!void { + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + switch (ops.flags) { + 0b00 => { + // PUSH/POP reg + return lowerToOEnc(tag, ops.reg1, isel.code) catch |err| isel.failWithLoweringError(err); + }, + 0b01 => { + // PUSH/POP r/m64 + const imm = isel.mir.instructions.items(.data)[inst].imm; + const ptr_size: Memory.PtrSize = switch (immOpSize(imm)) { + 16 => .word_ptr, + else => .qword_ptr, + }; + return lowerToMEnc(tag, RegisterOrMemory.mem(ops.reg1, imm, ptr_size), isel.code) catch |err| + isel.failWithLoweringError(err); + }, + 0b10 => { + // PUSH imm32 + assert(tag == .push); + const imm = isel.mir.instructions.items(.data)[inst].imm; + return lowerToIEnc(.push, imm, isel.code) catch |err| + isel.failWithLoweringError(err); + }, + 0b11 => unreachable, + } +} +fn mirPushPopRegsFromCalleePreservedRegs(isel: *Isel, tag: Tag, inst: Mir.Inst.Index) InnerError!void { + const callee_preserved_regs = bits.callee_preserved_regs; + const regs = isel.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; + lowerToOEnc(.push, reg, isel.code) catch |err| + return isel.failWithLoweringError(err); + } + } else { + // pop in the reverse direction + var i = callee_preserved_regs.len; + while (i > 0) : (i -= 1) { + const reg = callee_preserved_regs[i - 1]; + if ((regs >> @intCast(u5, i - 1)) & 1 == 0) continue; + lowerToOEnc(.pop, reg, isel.code) catch |err| + return isel.failWithLoweringError(err); + } + } +} + +fn mirJmpCall(isel: *Isel, tag: Tag, inst: Mir.Inst.Index) InnerError!void { + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + const flag = @truncate(u1, ops.flags); + if (flag == 0) { + const target = isel.mir.instructions.items(.data)[inst].inst; + const source = isel.code.items.len; + lowerToDEnc(tag, 0, isel.code) catch |err| + return isel.failWithLoweringError(err); + try isel.relocs.append(isel.bin_file.allocator, .{ + .source = source, + .target = target, + .offset = isel.code.items.len - 4, + .length = 5, + }); + return; + } + if (ops.reg1 == .none) { + // JMP/CALL [imm] + const imm = isel.mir.instructions.items(.data)[inst].imm; + const ptr_size: Memory.PtrSize = switch (immOpSize(imm)) { + 16 => .word_ptr, + else => .qword_ptr, + }; + return lowerToMEnc(tag, RegisterOrMemory.mem(null, imm, ptr_size), isel.code) catch |err| + isel.failWithLoweringError(err); + } + // JMP/CALL reg + return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), isel.code) catch |err| isel.failWithLoweringError(err); +} + +fn mirCondJmp(isel: *Isel, mir_tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + const target = isel.mir.instructions.items(.data)[inst].inst; + 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 = isel.code.items.len; + lowerToDEnc(tag, 0, isel.code) catch |err| + return isel.failWithLoweringError(err); + try isel.relocs.append(isel.bin_file.allocator, .{ + .source = source, + .target = target, + .offset = isel.code.items.len - 4, + .length = 6, + }); +} + +fn mirCondSetByte(isel: *Isel, mir_tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void { + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + 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.to8()), isel.code) catch |err| + isel.failWithLoweringError(err); +} + +fn mirTest(isel: *Isel, inst: Mir.Inst.Index) InnerError!void { + const tag = isel.mir.instructions.items(.tag)[inst]; + assert(tag == .@"test"); + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + switch (ops.flags) { + 0b00 => { + if (ops.reg2 == .none) { + // TEST r/m64, imm32 + // MI + const imm = isel.mir.instructions.items(.data)[inst].imm; + if (ops.reg1.to64() == .rax) { + // TEST rax, imm32 + // I + return lowerToIEnc(.@"test", imm, isel.code) catch |err| + isel.failWithLoweringError(err); + } + return lowerToMiEnc(.@"test", RegisterOrMemory.reg(ops.reg1), imm, isel.code) catch |err| + isel.failWithLoweringError(err); + } + // TEST r/m64, r64 + return isel.fail("TODO TEST r/m64, r64", .{}); + }, + else => return isel.fail("TODO more TEST alternatives", .{}), + } +} + +fn mirRet(isel: *Isel, inst: Mir.Inst.Index) InnerError!void { + const tag = isel.mir.instructions.items(.tag)[inst]; + assert(tag == .ret); + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + switch (ops.flags) { + 0b00 => { + // RETF imm16 + // I + const imm = isel.mir.instructions.items(.data)[inst].imm; + return lowerToIEnc(.ret_far, imm, isel.code) catch |err| isel.failWithLoweringError(err); + }, + 0b01 => { + return lowerToZoEnc(.ret_far, isel.code) catch |err| isel.failWithLoweringError(err); + }, + 0b10 => { + // RET imm16 + // I + const imm = isel.mir.instructions.items(.data)[inst].imm; + return lowerToIEnc(.ret_near, imm, isel.code) catch |err| isel.failWithLoweringError(err); + }, + 0b11 => { + return lowerToZoEnc(.ret_near, isel.code) catch |err| isel.failWithLoweringError(err); + }, + } +} + +fn mirArith(isel: *Isel, tag: Tag, inst: Mir.Inst.Index) InnerError!void { + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + switch (ops.flags) { + 0b00 => { + if (ops.reg2 == .none) { + // mov reg1, imm32 + // MI + const imm = isel.mir.instructions.items(.data)[inst].imm; + return lowerToMiEnc(tag, RegisterOrMemory.reg(ops.reg1), imm, isel.code) catch |err| + isel.failWithLoweringError(err); + } + // mov reg1, reg2 + // RM + return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), isel.code) catch |err| + isel.failWithLoweringError(err); + }, + 0b01 => { + // mov reg1, [reg2 + imm32] + // RM + const imm = isel.mir.instructions.items(.data)[inst].imm; + const src_reg: ?Register = if (ops.reg2 == .none) null else ops.reg2; + return lowerToRmEnc( + tag, + ops.reg1, + RegisterOrMemory.mem(src_reg, imm, Memory.PtrSize.fromBits(ops.reg1.size())), + isel.code, + ) catch |err| isel.failWithLoweringError(err); + }, + 0b10 => { + if (ops.reg2 == .none) { + return isel.fail("TODO unused variant: mov reg1, none, 0b10", .{}); + } + // mov [reg1 + imm32], reg2 + // MR + const imm = isel.mir.instructions.items(.data)[inst].imm; + return lowerToMrEnc( + tag, + RegisterOrMemory.mem(ops.reg1, imm, Memory.PtrSize.fromBits(ops.reg2.size())), + ops.reg2, + isel.code, + ) catch |err| isel.failWithLoweringError(err); + }, + 0b11 => { + return isel.fail("TODO unused variant: mov reg1, reg2, 0b11", .{}); + }, + } +} + +fn mirArithMemImm(isel: *Isel, tag: Tag, inst: Mir.Inst.Index) InnerError!void { + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + assert(ops.reg2 == .none); + const payload = isel.mir.instructions.items(.data)[inst].payload; + const imm_pair = isel.mir.extraData(Mir.ImmPair, payload).data; + const ptr_size: Memory.PtrSize = switch (ops.flags) { + 0b00 => .byte_ptr, + 0b01 => .word_ptr, + 0b10 => .dword_ptr, + 0b11 => .qword_ptr, + }; + return lowerToMiEnc( + tag, + RegisterOrMemory.mem(ops.reg1, imm_pair.dest_off, ptr_size), + imm_pair.operand, + isel.code, + ) catch |err| isel.failWithLoweringError(err); +} + +inline fn setRexWRegister(reg: Register) bool { + if (reg.size() == 64) return true; + return switch (reg) { + .ah, .bh, .ch, .dh => true, + else => false, + }; +} + +inline fn immOpSize(imm: i64) u8 { + blk: { + _ = math.cast(i8, imm) catch break :blk; + return 8; + } + blk: { + _ = math.cast(i16, imm) catch break :blk; + return 16; + } + blk: { + _ = math.cast(i32, imm) catch break :blk; + return 32; + } + return 64; +} + +// TODO +fn mirArithScaleSrc(isel: *Isel, tag: Tag, inst: Mir.Inst.Index) InnerError!void { + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + const scale = ops.flags; + // OP reg1, [reg2 + scale*rcx + imm32] + const opc = getOpCode(tag, .rm, ops.reg1.size() == 8).?; + const imm = isel.mir.instructions.items(.data)[inst].imm; + const encoder = try Encoder.init(isel.code, 8); + encoder.rex(.{ + .w = ops.reg1.size() == 64, + .r = ops.reg1.isExtended(), + .b = ops.reg2.isExtended(), + }); + opc.encode(encoder); + if (imm <= math.maxInt(i8)) { + encoder.modRm_SIBDisp8(ops.reg1.lowId()); + encoder.sib_scaleIndexBaseDisp8(scale, Register.rcx.lowId(), ops.reg2.lowId()); + encoder.disp8(@intCast(i8, imm)); + } else { + encoder.modRm_SIBDisp32(ops.reg1.lowId()); + encoder.sib_scaleIndexBaseDisp32(scale, Register.rcx.lowId(), ops.reg2.lowId()); + encoder.disp32(imm); + } +} + +// TODO +fn mirArithScaleDst(isel: *Isel, tag: Tag, inst: Mir.Inst.Index) InnerError!void { + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + const scale = ops.flags; + const imm = isel.mir.instructions.items(.data)[inst].imm; + + if (ops.reg2 == .none) { + // OP [reg1 + scale*rax + 0], imm32 + const opc = getOpCode(tag, .mi, ops.reg1.size() == 8).?; + const modrm_ext = getModRmExt(tag).?; + const encoder = try Encoder.init(isel.code, 8); + encoder.rex(.{ + .w = ops.reg1.size() == 64, + .b = ops.reg1.isExtended(), + }); + opc.encode(encoder); + 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)); + } else if (imm <= math.maxInt(i16)) { + encoder.imm16(@intCast(i16, imm)); + } else { + encoder.imm32(imm); + } + return; + } + + // OP [reg1 + scale*rax + imm32], reg2 + const opc = getOpCode(tag, .mr, ops.reg1.size() == 8).?; + const encoder = try Encoder.init(isel.code, 8); + encoder.rex(.{ + .w = ops.reg1.size() == 64, + .r = ops.reg2.isExtended(), + .b = ops.reg1.isExtended(), + }); + opc.encode(encoder); + if (imm <= math.maxInt(i8)) { + encoder.modRm_SIBDisp8(ops.reg2.lowId()); + encoder.sib_scaleIndexBaseDisp8(scale, Register.rax.lowId(), ops.reg1.lowId()); + encoder.disp8(@intCast(i8, imm)); + } else { + encoder.modRm_SIBDisp32(ops.reg2.lowId()); + encoder.sib_scaleIndexBaseDisp32(scale, Register.rax.lowId(), ops.reg1.lowId()); + encoder.disp32(imm); + } +} + +// TODO +fn mirArithScaleImm(isel: *Isel, tag: Tag, inst: Mir.Inst.Index) InnerError!void { + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + const scale = ops.flags; + const payload = isel.mir.instructions.items(.data)[inst].payload; + const imm_pair = isel.mir.extraData(Mir.ImmPair, payload).data; + const opc = getOpCode(tag, .mi, ops.reg1.size() == 8).?; + const modrm_ext = getModRmExt(tag).?; + const encoder = try Encoder.init(isel.code, 2); + encoder.rex(.{ + .w = ops.reg1.size() == 64, + .b = ops.reg1.isExtended(), + }); + 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()); + encoder.disp8(@intCast(i8, imm_pair.dest_off)); + } else { + encoder.modRm_SIBDisp32(modrm_ext); + encoder.sib_scaleIndexBaseDisp32(scale, Register.rax.lowId(), ops.reg1.lowId()); + encoder.disp32(imm_pair.dest_off); + } + encoder.imm32(imm_pair.operand); +} + +fn mirMovabs(isel: *Isel, inst: Mir.Inst.Index) InnerError!void { + const tag = isel.mir.instructions.items(.tag)[inst]; + assert(tag == .movabs); + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + const imm: i64 = if (ops.reg1.size() == 64) blk: { + const payload = isel.mir.instructions.items(.data)[inst].payload; + const imm = isel.mir.extraData(Mir.Imm64, payload).data; + break :blk @bitCast(i64, imm.decode()); + } else isel.mir.instructions.items(.data)[inst].imm; + if (ops.flags == 0b00) { + // movabs reg, imm64 + // OI + return lowerToOiEnc(.mov, ops.reg1, imm, isel.code) catch |err| isel.failWithLoweringError(err); + } + if (ops.reg1 == .none) { + // movabs moffs64, rax + // TD + return lowerToTdEnc(.mov, imm, ops.reg2, isel.code) catch |err| isel.failWithLoweringError(err); + } + // movabs rax, moffs64 + // FD + return lowerToFdEnc(.mov, ops.reg1, imm, isel.code) catch |err| isel.failWithLoweringError(err); +} + +fn mirIMulComplex(isel: *Isel, inst: Mir.Inst.Index) InnerError!void { + const tag = isel.mir.instructions.items(.tag)[inst]; + assert(tag == .imul_complex); + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + switch (ops.flags) { + 0b00 => { + return lowerToRmEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), isel.code) catch |err| + isel.failWithLoweringError(err); + }, + 0b10 => { + const imm = isel.mir.instructions.items(.data)[inst].imm; + return lowerToRmiEnc(.imul, ops.reg1, RegisterOrMemory.reg(ops.reg2), imm, isel.code) catch |err| + isel.failWithLoweringError(err); + }, + else => return isel.fail("TODO implement imul", .{}), + } +} + +fn mirLea(isel: *Isel, inst: Mir.Inst.Index) InnerError!void { + const tag = isel.mir.instructions.items(.tag)[inst]; + assert(tag == .lea); + const ops = Mir.Ops.decode(isel.mir.instructions.items(.ops)[inst]); + switch (ops.flags) { + 0b00 => { + // lea reg1, [reg2 + imm32] + // RM + const imm = isel.mir.instructions.items(.data)[inst].imm; + const src_reg: ?Register = if (ops.reg2 == .none) null else ops.reg2; + return lowerToRmEnc( + .lea, + ops.reg1, + RegisterOrMemory.mem(src_reg, imm, Memory.PtrSize.fromBits(ops.reg1.size())), + isel.code, + ) catch |err| isel.failWithLoweringError(err); + }, + 0b01 => { + // lea reg1, [rip + imm32] + // RM + const start_offset = isel.code.items.len; + lowerToRmEnc( + .lea, + ops.reg1, + RegisterOrMemory.rip(0, Memory.PtrSize.fromBits(ops.reg1.size())), + isel.code, + ) catch |err| return isel.failWithLoweringError(err); + const end_offset = isel.code.items.len; + // Backpatch the displacement + const payload = isel.mir.instructions.items(.data)[inst].payload; + const imm = isel.mir.extraData(Mir.Imm64, payload).data.decode(); + const disp = @intCast(i32, @intCast(i64, imm) - @intCast(i64, end_offset - start_offset)); + mem.writeIntLittle(i32, isel.code.items[end_offset - 4 ..][0..4], disp); + }, + 0b10 => { + // lea reg1, [rip + reloc] + // RM + lowerToRmEnc( + .lea, + ops.reg1, + RegisterOrMemory.rip(0, Memory.PtrSize.fromBits(ops.reg1.size())), + isel.code, + ) catch |err| return isel.failWithLoweringError(err); + const end_offset = isel.code.items.len; + const got_entry = isel.mir.instructions.items(.data)[inst].got_entry; + if (isel.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(isel.bin_file.allocator, .{ + .offset = @intCast(u32, end_offset - 4), + .target = .{ .local = got_entry }, + .addend = 0, + .subtractor = null, + .pcrel = true, + .length = 2, + .@"type" = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT), + }); + } else { + return isel.fail( + "TODO implement lea reg, [rip + reloc] for linking backends different than MachO", + .{}, + ); + } + }, + 0b11 => return isel.fail("TODO unused variant lea reg1, reg2, 0b11", .{}), + } +} + +fn mirCallExtern(isel: *Isel, inst: Mir.Inst.Index) InnerError!void { + const tag = isel.mir.instructions.items(.tag)[inst]; + assert(tag == .call_extern); + const n_strx = isel.mir.instructions.items(.data)[inst].extern_fn; + const offset = blk: { + // callq + lowerToDEnc(.call_near, 0, isel.code) catch |err| + return isel.failWithLoweringError(err); + break :blk @intCast(u32, isel.code.items.len) - 4; + }; + if (isel.bin_file.cast(link.File.MachO)) |macho_file| { + // Add relocation to the decl. + try macho_file.active_decl.?.link.macho.relocs.append(isel.bin_file.allocator, .{ + .offset = offset, + .target = .{ .global = n_strx }, + .addend = 0, + .subtractor = null, + .pcrel = true, + .length = 2, + .@"type" = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH), + }); + } else { + return isel.fail("TODO implement call_extern for linking backends different than MachO", .{}); + } +} + +fn mirDbgLine(isel: *Isel, inst: Mir.Inst.Index) InnerError!void { + const tag = isel.mir.instructions.items(.tag)[inst]; + assert(tag == .dbg_line); + const payload = isel.mir.instructions.items(.data)[inst].payload; + const dbg_line_column = isel.mir.extraData(Mir.DbgLineColumn, payload).data; + try isel.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column); +} + +fn dbgAdvancePCAndLine(isel: *Isel, line: u32, column: u32) InnerError!void { + const delta_line = @intCast(i32, line) - @intCast(i32, isel.prev_di_line); + const delta_pc: usize = isel.code.items.len - isel.prev_di_pc; + switch (isel.debug_output) { + .dwarf => |dbg_out| { + // TODO Look into using the DWARF special opcodes to compress this data. + // It lets you emit single-byte opcodes that add different numbers to + // both the PC and the line number at the same time. + try dbg_out.dbg_line.ensureUnusedCapacity(11); + dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_pc); + leb128.writeULEB128(dbg_out.dbg_line.writer(), delta_pc) catch unreachable; + if (delta_line != 0) { + dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.advance_line); + leb128.writeILEB128(dbg_out.dbg_line.writer(), delta_line) catch unreachable; + } + dbg_out.dbg_line.appendAssumeCapacity(DW.LNS.copy); + isel.prev_di_pc = isel.code.items.len; + isel.prev_di_line = line; + isel.prev_di_column = column; + isel.prev_di_pc = isel.code.items.len; + }, + .plan9 => |dbg_out| { + if (delta_pc <= 0) return; // only do this when the pc changes + // we have already checked the target in the linker to make sure it is compatable + const quant = @import("../../link/Plan9/aout.zig").getPCQuant(isel.target.cpu.arch) catch unreachable; + + // increasing the line number + try @import("../../link/Plan9.zig").changeLine(dbg_out.dbg_line, delta_line); + // increasing the pc + const d_pc_p9 = @intCast(i64, delta_pc) - quant; + if (d_pc_p9 > 0) { + // minus one because if its the last one, we want to leave space to change the line which is one quanta + try dbg_out.dbg_line.append(@intCast(u8, @divExact(d_pc_p9, quant) + 128) - quant); + if (dbg_out.pcop_change_index.*) |pci| + dbg_out.dbg_line.items[pci] += 1; + dbg_out.pcop_change_index.* = @intCast(u32, dbg_out.dbg_line.items.len - 1); + } else if (d_pc_p9 == 0) { + // we don't need to do anything, because adding the quant does it for us + } else unreachable; + if (dbg_out.start_line.* == null) + dbg_out.start_line.* = isel.prev_di_line; + dbg_out.end_line.* = line; + // only do this if the pc changed + isel.prev_di_line = line; + isel.prev_di_column = column; + isel.prev_di_pc = isel.code.items.len; + }, + .none => {}, + } +} + +fn mirDbgPrologueEnd(isel: *Isel, inst: Mir.Inst.Index) InnerError!void { + const tag = isel.mir.instructions.items(.tag)[inst]; + assert(tag == .dbg_prologue_end); + switch (isel.debug_output) { + .dwarf => |dbg_out| { + try dbg_out.dbg_line.append(DW.LNS.set_prologue_end); + try isel.dbgAdvancePCAndLine(isel.prev_di_line, isel.prev_di_column); + }, + .plan9 => {}, + .none => {}, + } +} + +fn mirDbgEpilogueBegin(isel: *Isel, inst: Mir.Inst.Index) InnerError!void { + const tag = isel.mir.instructions.items(.tag)[inst]; + assert(tag == .dbg_epilogue_begin); + switch (isel.debug_output) { + .dwarf => |dbg_out| { + try dbg_out.dbg_line.append(DW.LNS.set_epilogue_begin); + try isel.dbgAdvancePCAndLine(isel.prev_di_line, isel.prev_di_column); + }, + .plan9 => {}, + .none => {}, + } +} + +fn mirArgDbgInfo(isel: *Isel, inst: Mir.Inst.Index) InnerError!void { + const tag = isel.mir.instructions.items(.tag)[inst]; + assert(tag == .arg_dbg_info); + const payload = isel.mir.instructions.items(.data)[inst].payload; + const arg_dbg_info = isel.mir.extraData(Mir.ArgDbgInfo, payload).data; + const mcv = isel.mir.function.args[arg_dbg_info.arg_index]; + try isel.genArgDbgInfo(arg_dbg_info.air_inst, mcv); +} + +fn genArgDbgInfo(isel: *Isel, inst: Air.Inst.Index, mcv: MCValue) !void { + const ty_str = isel.mir.function.air.instructions.items(.data)[inst].ty_str; + const zir = &isel.mir.function.mod_fn.owner_decl.getFileScope().zir; + const name = zir.nullTerminatedString(ty_str.str); + const name_with_null = name.ptr[0 .. name.len + 1]; + const ty = isel.mir.function.air.getRefType(ty_str.ty); + + switch (mcv) { + .register => |reg| { + switch (isel.debug_output) { + .dwarf => |dbg_out| { + try dbg_out.dbg_info.ensureUnusedCapacity(3); + dbg_out.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); + dbg_out.dbg_info.appendSliceAssumeCapacity(&[2]u8{ // DW.AT.location, DW.FORM.exprloc + 1, // ULEB128 dwarf expression length + reg.dwarfLocOp(), + }); + try dbg_out.dbg_info.ensureUnusedCapacity(5 + name_with_null.len); + try isel.addDbgInfoTypeReloc(ty); // DW.AT.type, DW.FORM.ref4 + dbg_out.dbg_info.appendSliceAssumeCapacity(name_with_null); // DW.AT.name, DW.FORM.string + }, + .plan9 => {}, + .none => {}, + } + }, + .stack_offset => { + switch (isel.debug_output) { + .dwarf => {}, + .plan9 => {}, + .none => {}, + } + }, + else => {}, + } +} + +/// Adds a Type to the .debug_info at the current position. The bytes will be populated later, +/// after codegen for this symbol is done. +fn addDbgInfoTypeReloc(isel: *Isel, ty: Type) !void { + switch (isel.debug_output) { + .dwarf => |dbg_out| { + assert(ty.hasCodeGenBits()); + const index = dbg_out.dbg_info.items.len; + try dbg_out.dbg_info.resize(index + 4); // DW.AT.type, DW.FORM.ref4 + + const gop = try dbg_out.dbg_info_type_relocs.getOrPut(isel.bin_file.allocator, ty); + if (!gop.found_existing) { + gop.value_ptr.* = .{ + .off = undefined, + .relocs = .{}, + }; + } + try gop.value_ptr.relocs.append(isel.bin_file.allocator, @intCast(u32, index)); + }, + .plan9 => {}, + .none => {}, + } +} + +const Tag = enum { + adc, + add, + sub, + xor, + @"and", + @"or", + sbb, + cmp, + mov, + lea, + jmp_near, + call_near, + push, + pop, + @"test", + brk, + nop, + imul, + 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 { + /// OP + zo, + + /// OP rel32 + d, + + /// OP r/m64 + m, + + /// OP r64 + o, + + /// OP imm32 + i, + + /// OP r/m64, imm32 + mi, + + /// OP r/m64, r64 + mr, + + /// OP r64, r/m64 + rm, + + /// OP r64, imm64 + oi, + + /// OP al/ax/eax/rax, moffs + fd, + + /// OP moffs, al/ax/eax/rax + td, + + /// OP r64, r/m64, imm32 + rmi, +}; + +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 => 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 => 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) { + .push => OpCode.oneByte(0x50), + .pop => OpCode.oneByte(0x58), + else => null, + }, + .i => return switch (tag) { + .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 => 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 => 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 => 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), + .imul => OpCode.twoByte(0x0f, 0xaf), + else => null, + }, + .oi => return switch (tag) { + .mov => OpCode.oneByte(if (is_one_byte) 0xb0 else 0xb8), + else => null, + }, + .fd => return switch (tag) { + .mov => OpCode.oneByte(if (is_one_byte) 0xa0 else 0xa1), + else => null, + }, + .td => return switch (tag) { + .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, + }, + } +} + +inline fn getModRmExt(tag: Tag) ?u3 { + return switch (tag) { + .adc => 0x2, + .add => 0x0, + .sub => 0x5, + .xor => 0x6, + .@"and" => 0x4, + .@"or" => 0x1, + .sbb => 0x3, + .cmp => 0x7, + .mov => 0x0, + .jmp_near => 0x4, + .call_near => 0x2, + .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, + }; +} + +const ScaleIndexBase = struct { + scale: u2, + index_reg: ?Register, + base_reg: ?Register, +}; + +const Memory = struct { + reg: ?Register, + rip: bool = false, + disp: i32, + ptr_size: PtrSize, + sib: ?ScaleIndexBase = null, + + const PtrSize = enum { + byte_ptr, + word_ptr, + dword_ptr, + qword_ptr, + + fn fromBits(in_bits: u64) PtrSize { + return switch (in_bits) { + 8 => .byte_ptr, + 16 => .word_ptr, + 32 => .dword_ptr, + 64 => .qword_ptr, + else => unreachable, + }; + } + + /// Returns size in bits. + fn size(ptr_size: PtrSize) u64 { + return switch (ptr_size) { + .byte_ptr => 8, + .word_ptr => 16, + .dword_ptr => 32, + .qword_ptr => 64, + }; + } + }; +}; + +const RegisterOrMemory = union(enum) { + register: Register, + memory: Memory, + + fn reg(register: Register) RegisterOrMemory { + return .{ .register = register }; + } + + fn mem(register: ?Register, disp: i32, ptr_size: Memory.PtrSize) RegisterOrMemory { + return .{ + .memory = .{ + .reg = register, + .disp = disp, + .ptr_size = ptr_size, + }, + }; + } + + fn rip(disp: i32, ptr_size: Memory.PtrSize) RegisterOrMemory { + return .{ + .memory = .{ + .reg = null, + .rip = true, + .disp = disp, + .ptr_size = ptr_size, + }, + }; + } +}; + +const LoweringError = error{ + OutOfMemory, + Overflow, + OperandSizeMismatch, + RaxOperandExpected, +}; + +fn lowerToZoEnc(tag: Tag, code: *std.ArrayList(u8)) LoweringError!void { + const opc = getOpCode(tag, .zo, false).?; + const encoder = try Encoder.init(code, 1); + opc.encode(encoder); +} + +fn lowerToIEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) LoweringError!void { + if (tag == .ret_far or tag == .ret_near) { + const encoder = try Encoder.init(code, 3); + const opc = getOpCode(tag, .i, false).?; + opc.encode(encoder); + encoder.imm16(@intCast(i16, imm)); + return; + } + const opc = getOpCode(tag, .i, immOpSize(imm) == 8).?; + const encoder = try Encoder.init(code, 5); + if (immOpSize(imm) == 16) { + encoder.opcode_1byte(0x66); + } + opc.encode(encoder); + 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)) LoweringError!void { + if (reg.size() != 16 and reg.size() != 64) { + return error.OperandSizeMismatch; // TODO correct for push/pop, but is it universal? + } + const opc = getOpCode(tag, .o, false).?; + const encoder = try Encoder.init(code, 3); + if (reg.size() == 16) { + encoder.opcode_1byte(0x66); + } + encoder.rex(.{ + .w = false, + .b = reg.isExtended(), + }); + opc.encodeWithReg(encoder, reg); +} + +fn lowerToDEnc(tag: Tag, imm: i32, code: *std.ArrayList(u8)) LoweringError!void { + const opc = getOpCode(tag, .d, false).?; + const encoder = try Encoder.init(code, 6); + opc.encode(encoder); + encoder.imm32(imm); +} + +fn lowerToMEnc(tag: Tag, reg_or_mem: RegisterOrMemory, code: *std.ArrayList(u8)) LoweringError!void { + const opc = getOpCode(tag, .m, false).?; + const modrm_ext = getModRmExt(tag).?; + switch (reg_or_mem) { + .register => |reg| { + const op_size_mismatch = blk: { + if (tag.isSetCC() and reg.size() == 8) + break :blk false; + break :blk reg.size() != 64 and reg.size() != 16; + }; + if (op_size_mismatch) { + return error.OperandSizeMismatch; + } + const encoder = try Encoder.init(code, 4); + if (reg.size() == 16) { + encoder.opcode_1byte(0x66); + } + encoder.rex(.{ + .w = switch (reg) { + .ah, .bh, .ch, .dh => true, + else => false, + }, + .b = reg.isExtended(), + }); + opc.encode(encoder); + encoder.modRm_direct(modrm_ext, reg.lowId()); + }, + .memory => |mem_op| { + if (mem_op.ptr_size != .qword_ptr and mem_op.ptr_size != .word_ptr) { + return error.OperandSizeMismatch; + } + const encoder = try Encoder.init(code, 8); + if (mem_op.ptr_size == .word_ptr) { + encoder.opcode_1byte(0x66); + } + if (mem_op.reg) |reg| { + if (reg.size() != 64) { + return error.OperandSizeMismatch; + } + encoder.rex(.{ + .w = false, + .b = reg.isExtended(), + }); + opc.encode(encoder); + 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 { + opc.encode(encoder); + 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)) LoweringError!void { + return lowerToTdFdEnc(tag, reg, moffs, code, true); +} + +fn lowerToFdEnc(tag: Tag, reg: Register, moffs: i64, code: *std.ArrayList(u8)) LoweringError!void { + return lowerToTdFdEnc(tag, reg, moffs, code, false); +} + +fn lowerToTdFdEnc(tag: Tag, reg: Register, moffs: i64, code: *std.ArrayList(u8), td: bool) LoweringError!void { + if (reg.lowId() != Register.rax.lowId()) { + return error.RaxOperandExpected; + } + if (reg.size() != immOpSize(moffs)) { + return error.OperandSizeMismatch; + } + 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); + } + encoder.rex(.{ + .w = setRexWRegister(reg), + }); + opc.encode(encoder); + 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: Tag, reg: Register, imm: i64, code: *std.ArrayList(u8)) LoweringError!void { + if (reg.size() != immOpSize(imm)) { + return error.OperandSizeMismatch; + } + const opc = getOpCode(tag, .oi, reg.size() == 8).?; + const encoder = try Encoder.init(code, 10); + if (reg.size() == 16) { + encoder.opcode_1byte(0x66); + } + encoder.rex(.{ + .w = setRexWRegister(reg), + .b = reg.isExtended(), + }); + opc.encodeWithReg(encoder, reg); + 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: Tag, reg_or_mem: RegisterOrMemory, imm: i32, code: *std.ArrayList(u8)) LoweringError!void { + const modrm_ext = getModRmExt(tag).?; + switch (reg_or_mem) { + .register => |dst_reg| { + 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 + // 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 = setRexWRegister(dst_reg), + .b = dst_reg.isExtended(), + }); + opc.encode(encoder); + encoder.modRm_direct(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 opc = getOpCode(tag, .mi, dst_mem.ptr_size == .byte_ptr).?; + const encoder = try Encoder.init(code, 12); + if (dst_mem.ptr_size == .word_ptr) { + encoder.opcode_1byte(0x66); + } + if (dst_mem.reg) |dst_reg| { + if (dst_reg.size() != 64) { + return error.OperandSizeMismatch; + } + encoder.rex(.{ + .w = dst_mem.ptr_size == .qword_ptr, + .b = dst_reg.isExtended(), + }); + opc.encode(encoder); + 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); + } + } + } else { + opc.encode(encoder); + if (dst_mem.rip) { + encoder.modRm_RIPDisp32(modrm_ext); + } else { + encoder.modRm_SIBDisp0(modrm_ext); + encoder.sib_disp32(); + } + encoder.disp32(dst_mem.disp); + } + switch (dst_mem.ptr_size) { + .byte_ptr => { + const imm8 = try math.cast(i8, imm); + encoder.imm8(imm8); + }, + .word_ptr => { + const imm16 = try math.cast(i16, imm); + encoder.imm16(imm16); + }, + .dword_ptr, .qword_ptr => { + encoder.imm32(imm); + }, + } + }, + } +} + +fn lowerToRmEnc( + tag: Tag, + reg: Register, + reg_or_mem: RegisterOrMemory, + code: *std.ArrayList(u8), +) LoweringError!void { + const opc = getOpCode(tag, .rm, reg.size() == 8).?; + switch (reg_or_mem) { + .register => |src_reg| { + if (reg.size() != src_reg.size()) { + return error.OperandSizeMismatch; + } + const encoder = try Encoder.init(code, 3); + encoder.rex(.{ + .w = setRexWRegister(reg) or setRexWRegister(src_reg), + .r = reg.isExtended(), + .b = src_reg.isExtended(), + }); + opc.encode(encoder); + encoder.modRm_direct(reg.lowId(), src_reg.lowId()); + }, + .memory => |src_mem| { + if (reg.size() != src_mem.ptr_size.size()) { + return error.OperandSizeMismatch; + } + 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.OperandSizeMismatch; + } + encoder.rex(.{ + .w = setRexWRegister(reg), + .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 = setRexWRegister(reg), + .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); + } + }, + } +} + +fn lowerToMrEnc( + tag: Tag, + reg_or_mem: RegisterOrMemory, + reg: Register, + code: *std.ArrayList(u8), +) LoweringError!void { + const opc = getOpCode(tag, .mr, reg.size() == 8).?; + switch (reg_or_mem) { + .register => |dst_reg| { + if (dst_reg.size() != reg.size()) { + return error.OperandSizeMismatch; + } + const encoder = try Encoder.init(code, 3); + encoder.rex(.{ + .w = setRexWRegister(dst_reg) or setRexWRegister(reg), + .r = reg.isExtended(), + .b = dst_reg.isExtended(), + }); + opc.encode(encoder); + encoder.modRm_direct(reg.lowId(), dst_reg.lowId()); + }, + .memory => |dst_mem| { + if (dst_mem.ptr_size.size() != reg.size()) { + return error.OperandSizeMismatch; + } + 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.OperandSizeMismatch; + } + encoder.rex(.{ + .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg), + .r = reg.isExtended(), + .b = dst_reg.isExtended(), + }); + opc.encode(encoder); + 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); + } + } + } else { + encoder.rex(.{ + .w = dst_mem.ptr_size == .qword_ptr or setRexWRegister(reg), + .r = reg.isExtended(), + }); + opc.encode(encoder); + if (dst_mem.rip) { + encoder.modRm_RIPDisp32(reg.lowId()); + } else { + encoder.modRm_SIBDisp0(reg.lowId()); + encoder.sib_disp32(); + } + encoder.disp32(dst_mem.disp); + } + }, + } +} + +fn lowerToRmiEnc( + tag: Tag, + reg: Register, + reg_or_mem: RegisterOrMemory, + imm: i32, + code: *std.ArrayList(u8), +) LoweringError!void { + if (reg.size() == 8) { + return error.OperandSizeMismatch; + } + const opc = getOpCode(tag, .rmi, false).?; + const encoder = try Encoder.init(code, 13); + if (reg.size() == 16) { + encoder.opcode_1byte(0x66); + } + switch (reg_or_mem) { + .register => |src_reg| { + if (reg.size() != src_reg.size()) { + return error.OperandSizeMismatch; + } + encoder.rex(.{ + .w = setRexWRegister(reg) or setRexWRegister(src_reg), + .r = reg.isExtended(), + .b = src_reg.isExtended(), + }); + opc.encode(encoder); + encoder.modRm_direct(reg.lowId(), src_reg.lowId()); + }, + .memory => |src_mem| { + 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.OperandSizeMismatch; + } + if (src_mem.ptr_size == .byte_ptr) { + return error.OperandSizeMismatch; + } + encoder.rex(.{ + .w = setRexWRegister(reg), + .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 = setRexWRegister(reg), + .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); + } + }, + } + switch (reg.size()) { + // TODO 8bit immediate + 8 => unreachable, + 16 => { + const imm16 = try math.cast(i16, imm); + encoder.imm16(imm16); + }, + 32, 64 => encoder.imm32(imm), + else => unreachable, + } +} + +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; +} + +const TestIsel = struct { + code_buffer: std.ArrayList(u8), + next: usize = 0, + + fn init() TestIsel { + return .{ + .code_buffer = std.ArrayList(u8).init(testing.allocator), + }; + } + + fn deinit(isel: *TestIsel) void { + isel.code_buffer.deinit(); + isel.next = undefined; + } + + fn code(isel: *TestIsel) *std.ArrayList(u8) { + isel.next = isel.code_buffer.items.len; + return &isel.code_buffer; + } + + fn lowered(isel: TestIsel) []const u8 { + return isel.code_buffer.items[isel.next..]; + } +}; + +test "lower MI encoding" { + var isel = TestIsel.init(); + defer isel.deinit(); + try lowerToMiEnc(.mov, RegisterOrMemory.reg(.rax), 0x10, isel.code()); + try expectEqualHexStrings("\x48\xc7\xc0\x10\x00\x00\x00", isel.lowered(), "mov rax, 0x10"); + try lowerToMiEnc(.mov, RegisterOrMemory.mem(.r11, 0, .dword_ptr), 0x10, isel.code()); + try expectEqualHexStrings("\x41\xc7\x03\x10\x00\x00\x00", isel.lowered(), "mov dword ptr [r11 + 0], 0x10"); + try lowerToMiEnc(.add, RegisterOrMemory.mem(.rdx, -8, .dword_ptr), 0x10, isel.code()); + try expectEqualHexStrings("\x81\x42\xF8\x10\x00\x00\x00", isel.lowered(), "add dword ptr [rdx - 8], 0x10"); + try lowerToMiEnc(.sub, RegisterOrMemory.mem(.r11, 0x10000000, .dword_ptr), 0x10, isel.code()); + try expectEqualHexStrings( + "\x41\x81\xab\x00\x00\x00\x10\x10\x00\x00\x00", + isel.lowered(), + "sub dword ptr [r11 + 0x10000000], 0x10", + ); + try lowerToMiEnc(.@"and", RegisterOrMemory.mem(null, 0x10000000, .dword_ptr), 0x10, isel.code()); + try expectEqualHexStrings( + "\x81\x24\x25\x00\x00\x00\x10\x10\x00\x00\x00", + isel.lowered(), + "and dword ptr [ds:0x10000000], 0x10", + ); + try lowerToMiEnc(.@"and", RegisterOrMemory.mem(.r12, 0x10000000, .dword_ptr), 0x10, isel.code()); + try expectEqualHexStrings( + "\x41\x81\xA4\x24\x00\x00\x00\x10\x10\x00\x00\x00", + isel.lowered(), + "and dword ptr [r12 + 0x10000000], 0x10", + ); + try lowerToMiEnc(.mov, RegisterOrMemory.rip(0x10, .qword_ptr), 0x10, isel.code()); + try expectEqualHexStrings( + "\xC7\x05\x10\x00\x00\x00\x10\x00\x00\x00", + isel.lowered(), + "mov qword ptr [rip + 0x10], 0x10", + ); + try lowerToMiEnc(.mov, RegisterOrMemory.mem(.rbp, -8, .qword_ptr), 0x10, isel.code()); + try expectEqualHexStrings( + "\x48\xc7\x45\xf8\x10\x00\x00\x00", + isel.lowered(), + "mov qword ptr [rbp - 8], 0x10", + ); + try lowerToMiEnc(.mov, RegisterOrMemory.mem(.rbp, -2, .word_ptr), 0x10, isel.code()); + try expectEqualHexStrings("\x66\xC7\x45\xFE\x10\x00", isel.lowered(), "mov word ptr [rbp - 2], 0x10"); + try lowerToMiEnc(.mov, RegisterOrMemory.mem(.rbp, -1, .byte_ptr), 0x10, isel.code()); + try expectEqualHexStrings("\xC6\x45\xFF\x10", isel.lowered(), "mov byte ptr [rbp - 1], 0x10"); +} + +test "lower RM encoding" { + var isel = TestIsel.init(); + defer isel.deinit(); + try lowerToRmEnc(.mov, .rax, RegisterOrMemory.reg(.rbx), isel.code()); + try expectEqualHexStrings("\x48\x8b\xc3", isel.lowered(), "mov rax, rbx"); + try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.r11, 0, .qword_ptr), isel.code()); + try expectEqualHexStrings("\x49\x8b\x03", isel.lowered(), "mov rax, qword ptr [r11 + 0]"); + try lowerToRmEnc(.add, .r11, RegisterOrMemory.mem(null, 0x10000000, .qword_ptr), isel.code()); + try expectEqualHexStrings( + "\x4C\x03\x1C\x25\x00\x00\x00\x10", + isel.lowered(), + "add r11, qword ptr [ds:0x10000000]", + ); + try lowerToRmEnc(.add, .r12b, RegisterOrMemory.mem(null, 0x10000000, .byte_ptr), isel.code()); + try expectEqualHexStrings( + "\x44\x02\x24\x25\x00\x00\x00\x10", + isel.lowered(), + "add r11b, byte ptr [ds:0x10000000]", + ); + try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.r13, 0x10000000, .qword_ptr), isel.code()); + try expectEqualHexStrings( + "\x4D\x2B\x9D\x00\x00\x00\x10", + isel.lowered(), + "sub r11, qword ptr [r13 + 0x10000000]", + ); + try lowerToRmEnc(.sub, .r11, RegisterOrMemory.mem(.r12, 0x10000000, .qword_ptr), isel.code()); + try expectEqualHexStrings( + "\x4D\x2B\x9C\x24\x00\x00\x00\x10", + isel.lowered(), + "sub r11, qword ptr [r12 + 0x10000000]", + ); + try lowerToRmEnc(.mov, .rax, RegisterOrMemory.mem(.rbp, -4, .qword_ptr), isel.code()); + try expectEqualHexStrings("\x48\x8B\x45\xFC", isel.lowered(), "mov rax, qword ptr [rbp - 4]"); + try lowerToRmEnc(.lea, .rax, RegisterOrMemory.rip(0x10, .qword_ptr), isel.code()); + try expectEqualHexStrings("\x48\x8D\x05\x10\x00\x00\x00", isel.lowered(), "lea rax, [rip + 0x10]"); +} + +test "lower MR encoding" { + var isel = TestIsel.init(); + defer isel.deinit(); + try lowerToMrEnc(.mov, RegisterOrMemory.reg(.rax), .rbx, isel.code()); + try expectEqualHexStrings("\x48\x89\xd8", isel.lowered(), "mov rax, rbx"); + try lowerToMrEnc(.mov, RegisterOrMemory.mem(.rbp, -4, .qword_ptr), .r11, isel.code()); + try expectEqualHexStrings("\x4c\x89\x5d\xfc", isel.lowered(), "mov qword ptr [rbp - 4], r11"); + try lowerToMrEnc(.add, RegisterOrMemory.mem(null, 0x10000000, .byte_ptr), .r12b, isel.code()); + try expectEqualHexStrings( + "\x44\x00\x24\x25\x00\x00\x00\x10", + isel.lowered(), + "add byte ptr [ds:0x10000000], r12b", + ); + try lowerToMrEnc(.add, RegisterOrMemory.mem(null, 0x10000000, .dword_ptr), .r12d, isel.code()); + try expectEqualHexStrings( + "\x44\x01\x24\x25\x00\x00\x00\x10", + isel.lowered(), + "add dword ptr [ds:0x10000000], r12d", + ); + try lowerToMrEnc(.sub, RegisterOrMemory.mem(.r11, 0x10000000, .qword_ptr), .r12, isel.code()); + try expectEqualHexStrings( + "\x4D\x29\xA3\x00\x00\x00\x10", + isel.lowered(), + "sub qword ptr [r11 + 0x10000000], r12", + ); + try lowerToMrEnc(.mov, RegisterOrMemory.rip(0x10, .qword_ptr), .r12, isel.code()); + try expectEqualHexStrings("\x4C\x89\x25\x10\x00\x00\x00", isel.lowered(), "mov qword ptr [rip + 0x10], r12"); +} + +test "lower OI encoding" { + var isel = TestIsel.init(); + defer isel.deinit(); + try lowerToOiEnc(.mov, .rax, 0x1000000000000000, isel.code()); + try expectEqualHexStrings( + "\x48\xB8\x00\x00\x00\x00\x00\x00\x00\x10", + isel.lowered(), + "movabs rax, 0x1000000000000000", + ); + try lowerToOiEnc(.mov, .r11, 0x1000000000000000, isel.code()); + try expectEqualHexStrings( + "\x49\xBB\x00\x00\x00\x00\x00\x00\x00\x10", + isel.lowered(), + "movabs r11, 0x1000000000000000", + ); + try lowerToOiEnc(.mov, .r11d, 0x10000000, isel.code()); + try expectEqualHexStrings("\x41\xBB\x00\x00\x00\x10", isel.lowered(), "mov r11d, 0x10000000"); + try lowerToOiEnc(.mov, .r11w, 0x1000, isel.code()); + try expectEqualHexStrings("\x66\x41\xBB\x00\x10", isel.lowered(), "mov r11w, 0x1000"); + try lowerToOiEnc(.mov, .r11b, 0x10, isel.code()); + try expectEqualHexStrings("\x41\xB3\x10", isel.lowered(), "mov r11b, 0x10"); +} + +test "lower FD/TD encoding" { + var isel = TestIsel.init(); + defer isel.deinit(); + try lowerToFdEnc(.mov, .rax, 0x1000000000000000, isel.code()); + try expectEqualHexStrings( + "\x48\xa1\x00\x00\x00\x00\x00\x00\x00\x10", + isel.lowered(), + "mov rax, ds:0x1000000000000000", + ); + try lowerToFdEnc(.mov, .eax, 0x10000000, isel.code()); + try expectEqualHexStrings("\xa1\x00\x00\x00\x10", isel.lowered(), "mov eax, ds:0x10000000"); + try lowerToFdEnc(.mov, .ax, 0x1000, isel.code()); + try expectEqualHexStrings("\x66\xa1\x00\x10", isel.lowered(), "mov ax, ds:0x1000"); + try lowerToFdEnc(.mov, .al, 0x10, isel.code()); + try expectEqualHexStrings("\xa0\x10", isel.lowered(), "mov al, ds:0x10"); +} + +test "lower M encoding" { + var isel = TestIsel.init(); + defer isel.deinit(); + try lowerToMEnc(.jmp_near, RegisterOrMemory.reg(.r12), isel.code()); + try expectEqualHexStrings("\x41\xFF\xE4", isel.lowered(), "jmp r12"); + try lowerToMEnc(.jmp_near, RegisterOrMemory.reg(.r12w), isel.code()); + try expectEqualHexStrings("\x66\x41\xFF\xE4", isel.lowered(), "jmp r12w"); + try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0, .qword_ptr), isel.code()); + try expectEqualHexStrings("\x41\xFF\x24\x24", isel.lowered(), "jmp qword ptr [r12]"); + try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0, .word_ptr), isel.code()); + try expectEqualHexStrings("\x66\x41\xFF\x24\x24", isel.lowered(), "jmp word ptr [r12]"); + try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0x10, .qword_ptr), isel.code()); + try expectEqualHexStrings("\x41\xFF\x64\x24\x10", isel.lowered(), "jmp qword ptr [r12 + 0x10]"); + try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(.r12, 0x1000, .qword_ptr), isel.code()); + try expectEqualHexStrings( + "\x41\xFF\xA4\x24\x00\x10\x00\x00", + isel.lowered(), + "jmp qword ptr [r12 + 0x1000]", + ); + try lowerToMEnc(.jmp_near, RegisterOrMemory.rip(0x10, .qword_ptr), isel.code()); + try expectEqualHexStrings("\xFF\x25\x10\x00\x00\x00", isel.lowered(), "jmp qword ptr [rip + 0x10]"); + try lowerToMEnc(.jmp_near, RegisterOrMemory.mem(null, 0x10, .qword_ptr), isel.code()); + try expectEqualHexStrings("\xFF\x24\x25\x10\x00\x00\x00", isel.lowered(), "jmp qword ptr [ds:0x10]"); + try lowerToMEnc(.seta, RegisterOrMemory.reg(.r11b), isel.code()); + try expectEqualHexStrings("\x41\x0F\x97\xC3", isel.lowered(), "seta r11b"); +} + +test "lower O encoding" { + var isel = TestIsel.init(); + defer isel.deinit(); + try lowerToOEnc(.pop, .r12, isel.code()); + try expectEqualHexStrings("\x41\x5c", isel.lowered(), "pop r12"); + try lowerToOEnc(.push, .r12w, isel.code()); + try expectEqualHexStrings("\x66\x41\x54", isel.lowered(), "push r12w"); +} + +test "lower RMI encoding" { + var isel = TestIsel.init(); + defer isel.deinit(); + try lowerToRmiEnc(.imul, .rax, RegisterOrMemory.mem(.rbp, -8, .qword_ptr), 0x10, isel.code()); + try expectEqualHexStrings( + "\x48\x69\x45\xF8\x10\x00\x00\x00", + isel.lowered(), + "imul rax, qword ptr [rbp - 8], 0x10", + ); + try lowerToRmiEnc(.imul, .eax, RegisterOrMemory.mem(.rbp, -4, .dword_ptr), 0x10, isel.code()); + try expectEqualHexStrings("\x69\x45\xFC\x10\x00\x00\x00", isel.lowered(), "imul eax, dword ptr [rbp - 4], 0x10"); + try lowerToRmiEnc(.imul, .ax, RegisterOrMemory.mem(.rbp, -2, .word_ptr), 0x10, isel.code()); + try expectEqualHexStrings("\x66\x69\x45\xFE\x10\x00", isel.lowered(), "imul ax, word ptr [rbp - 2], 0x10"); + try lowerToRmiEnc(.imul, .r12, RegisterOrMemory.reg(.r12), 0x10, isel.code()); + try expectEqualHexStrings("\x4D\x69\xE4\x10\x00\x00\x00", isel.lowered(), "imul r12, r12, 0x10"); + try lowerToRmiEnc(.imul, .r12w, RegisterOrMemory.reg(.r12w), 0x10, isel.code()); + try expectEqualHexStrings("\x66\x45\x69\xE4\x10\x00", isel.lowered(), "imul r12w, r12w, 0x10"); +}