x86_64: factor out lowering from emitting

This commit is contained in:
Jacob Young 2023-03-26 21:33:56 -04:00
parent 0e5e001278
commit abb37a7cb8
8 changed files with 2102 additions and 2062 deletions

View File

@ -341,19 +341,22 @@ pub fn generate(
defer mir.deinit(bin_file.allocator);
var emit = Emit{
.mir = mir,
.lower = .{
.allocator = bin_file.allocator,
.mir = mir,
.target = &bin_file.options.target,
.src_loc = src_loc,
},
.bin_file = bin_file,
.debug_output = debug_output,
.target = &bin_file.options.target,
.src_loc = src_loc,
.code = code,
.prev_di_pc = 0,
.prev_di_line = module_fn.lbrace_line,
.prev_di_column = module_fn.lbrace_column,
};
defer emit.deinit();
emit.lowerMir() catch |err| switch (err) {
error.EmitFail => return Result{ .fail = emit.err_msg.? },
emit.emitMir() catch |err| switch (err) {
error.LowerFail, error.EmitFail => return Result{ .fail = emit.lower.err_msg.? },
error.InvalidInstruction, error.CannotEncode => |e| {
const msg = switch (e) {
error.InvalidInstruction => "CodeGen failed to find a viable instruction.",
@ -7070,16 +7073,10 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
if (reg.to64() == .rax) {
// If this is RAX, we can use a direct load.
// Otherwise, we need to load the address, then indirectly load the value.
var moffs: Mir.MemoryMoffs = .{
.seg = @enumToInt(Register.ds),
.msb = undefined,
.lsb = undefined,
};
moffs.encodeOffset(x);
_ = try self.addInst(.{
.tag = .mov_moffs,
.ops = .rax_moffs,
.data = .{ .payload = try self.addExtra(moffs) },
.data = .{ .payload = try self.addExtra(Mir.MemoryMoffs.encode(.ds, x)) },
});
} else {
// Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue.

View File

@ -1,40 +1,8 @@
//! This file contains the functionality for lowering x86_64 MIR into
//! machine code
//! This file contains the functionality for emitting x86_64 MIR as machine code
const Emit = @This();
const std = @import("std");
const assert = std.debug.assert;
const bits = @import("bits.zig");
const abi = @import("abi.zig");
const encoder = @import("encoder.zig");
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 CodeGen = @import("CodeGen.zig");
const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
const Encoder = bits.Encoder;
const ErrorMsg = Module.ErrorMsg;
const Immediate = bits.Immediate;
const Instruction = encoder.Instruction;
const MCValue = @import("CodeGen.zig").MCValue;
const Memory = bits.Memory;
const Mir = @import("Mir.zig");
const Module = @import("../../Module.zig");
const Register = bits.Register;
const Type = @import("../../type.zig").Type;
mir: Mir,
lower: Lower,
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,
@ -45,12 +13,163 @@ prev_di_pc: usize,
code_offset_mapping: std.AutoHashMapUnmanaged(Mir.Inst.Index, usize) = .{},
relocs: std.ArrayListUnmanaged(Reloc) = .{},
const InnerError = error{
OutOfMemory,
EmitFail,
InvalidInstruction,
CannotEncode,
};
pub const Error = Lower.Error || error{EmitFail};
pub fn emitMir(emit: *Emit) Error!void {
for (0..emit.lower.mir.instructions.len) |i| {
const index = @intCast(Mir.Inst.Index, i);
const inst = emit.lower.mir.instructions.get(index);
const start_offset = @intCast(u32, emit.code.items.len);
try emit.code_offset_mapping.putNoClobber(emit.lower.allocator, index, start_offset);
for (try emit.lower.lowerMir(inst)) |lower_inst| try lower_inst.encode(emit.code.writer());
const end_offset = @intCast(u32, emit.code.items.len);
switch (inst.tag) {
else => {},
.jmp_reloc => try emit.relocs.append(emit.lower.allocator, .{
.source = start_offset,
.target = inst.data.inst,
.offset = end_offset - 4,
.length = 5,
}),
.call_extern => if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
// Add relocation to the decl.
const atom_index = macho_file.getAtomIndexForSymbol(
.{ .sym_index = inst.data.relocation.atom_index, .file = null },
).?;
const target = macho_file.getGlobalByIndex(inst.data.relocation.sym_index);
try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
.type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH),
.target = target,
.offset = end_offset - 4,
.addend = 0,
.pcrel = true,
.length = 2,
});
} else if (emit.bin_file.cast(link.File.Coff)) |coff_file| {
// Add relocation to the decl.
const atom_index = coff_file.getAtomIndexForSymbol(
.{ .sym_index = inst.data.relocation.atom_index, .file = null },
).?;
const target = coff_file.getGlobalByIndex(inst.data.relocation.sym_index);
try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{
.type = .direct,
.target = target,
.offset = end_offset - 4,
.addend = 0,
.pcrel = true,
.length = 2,
});
} else return emit.fail("TODO implement {} for {}", .{ inst.tag, emit.bin_file.tag }),
.lea_linker => if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
const metadata =
emit.lower.mir.extraData(Mir.LeaRegisterReloc, inst.data.payload).data;
const reloc_type = switch (inst.ops) {
.got_reloc => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT),
.direct_reloc => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED),
else => unreachable,
};
const atom_index = macho_file.getAtomIndexForSymbol(.{
.sym_index = metadata.atom_index,
.file = null,
}).?;
try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
.type = reloc_type,
.target = .{ .sym_index = metadata.sym_index, .file = null },
.offset = @intCast(u32, end_offset - 4),
.addend = 0,
.pcrel = true,
.length = 2,
});
} else if (emit.bin_file.cast(link.File.Coff)) |coff_file| {
const metadata =
emit.lower.mir.extraData(Mir.LeaRegisterReloc, inst.data.payload).data;
const atom_index = coff_file.getAtomIndexForSymbol(.{
.sym_index = metadata.atom_index,
.file = null,
}).?;
try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{
.type = switch (inst.ops) {
.got_reloc => .got,
.direct_reloc => .direct,
.import_reloc => .import,
else => unreachable,
},
.target = switch (inst.ops) {
.got_reloc,
.direct_reloc,
=> .{ .sym_index = metadata.sym_index, .file = null },
.import_reloc => coff_file.getGlobalByIndex(metadata.sym_index),
else => unreachable,
},
.offset = @intCast(u32, end_offset - 4),
.addend = 0,
.pcrel = true,
.length = 2,
});
} else return emit.fail("TODO implement {} for {}", .{ inst.tag, emit.bin_file.tag }),
.jcc => try emit.relocs.append(emit.lower.allocator, .{
.source = start_offset,
.target = inst.data.inst_cc.inst,
.offset = end_offset - 4,
.length = 6,
}),
.dbg_line => {
const dbg_line_column =
emit.lower.mir.extraData(Mir.DbgLineColumn, inst.data.payload).data;
try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column);
},
.dbg_prologue_end => {
switch (emit.debug_output) {
.dwarf => |dw| {
try dw.setPrologueEnd();
log.debug("mirDbgPrologueEnd (line={d}, col={d})", .{
emit.prev_di_line, emit.prev_di_column,
});
try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
},
.plan9 => {},
.none => {},
}
},
.dbg_epilogue_begin => {
switch (emit.debug_output) {
.dwarf => |dw| {
try dw.setEpilogueBegin();
log.debug("mirDbgEpilogueBegin (line={d}, col={d})", .{
emit.prev_di_line, emit.prev_di_column,
});
try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
},
.plan9 => {},
.none => {},
}
},
}
}
try emit.fixupRelocs();
}
pub fn deinit(emit: *Emit) void {
emit.relocs.deinit(emit.lower.allocator);
emit.code_offset_mapping.deinit(emit.lower.allocator);
emit.* = undefined;
}
fn fail(emit: *Emit, comptime format: []const u8, args: anytype) Error {
return switch (emit.lower.fail(format, args)) {
error.LowerFail => error.EmitFail,
else => |e| e,
};
}
const Reloc = struct {
/// Offset of the instruction.
@ -63,148 +182,7 @@ const Reloc = struct {
length: u5,
};
pub fn lowerMir(emit: *Emit) InnerError!void {
const mir_tags = emit.mir.instructions.items(.tag);
for (mir_tags, 0..) |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,
.add,
.@"and",
.bsf,
.bsr,
.bswap,
.bt,
.btc,
.btr,
.bts,
.call,
.cbw,
.cwde,
.cdqe,
.cwd,
.cdq,
.cqo,
.cmp,
.cmpxchg,
.div,
.fisttp,
.fld,
.idiv,
.imul,
.int3,
.jmp,
.lea,
.lfence,
.lzcnt,
.mfence,
.mov,
.movbe,
.movzx,
.mul,
.neg,
.nop,
.not,
.@"or",
.pop,
.popcnt,
.push,
.rcl,
.rcr,
.ret,
.rol,
.ror,
.sal,
.sar,
.sbb,
.sfence,
.shl,
.shld,
.shr,
.shrd,
.sub,
.syscall,
.@"test",
.tzcnt,
.ud2,
.xadd,
.xchg,
.xor,
.addss,
.cmpss,
.divss,
.maxss,
.minss,
.movss,
.mulss,
.roundss,
.subss,
.ucomiss,
.addsd,
.cmpsd,
.divsd,
.maxsd,
.minsd,
.movsd,
.mulsd,
.roundsd,
.subsd,
.ucomisd,
=> try emit.mirEncodeGeneric(tag, inst),
.cmps,
.lods,
.movs,
.scas,
.stos,
=> try emit.mirString(tag, inst),
.cmpxchgb => try emit.mirCmpxchgBytes(inst),
.jmp_reloc => try emit.mirJmpReloc(inst),
.call_extern => try emit.mirCallExtern(inst),
.lea_linker => try emit.mirLeaLinker(inst),
.mov_moffs => try emit.mirMovMoffs(inst),
.movsx => try emit.mirMovsx(inst),
.cmovcc => try emit.mirCmovcc(inst),
.setcc => try emit.mirSetcc(inst),
.jcc => try emit.mirJcc(inst),
.dbg_line => try emit.mirDbgLine(inst),
.dbg_prologue_end => try emit.mirDbgPrologueEnd(inst),
.dbg_epilogue_begin => try emit.mirDbgEpilogueBegin(inst),
.push_regs => try emit.mirPushPopRegisterList(.push, inst),
.pop_regs => try emit.mirPushPopRegisterList(.pop, inst),
.dead => {},
}
}
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 fixupRelocs(emit: *Emit) InnerError!void {
fn fixupRelocs(emit: *Emit) Error!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
@ -217,532 +195,7 @@ fn fixupRelocs(emit: *Emit) InnerError!void {
}
}
fn encode(emit: *Emit, mnemonic: Instruction.Mnemonic, ops: Instruction.Init) InnerError!void {
const inst = try Instruction.new(mnemonic, ops);
return inst.encode(emit.code.writer());
}
fn mirEncodeGeneric(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void {
const mnemonic = inline for (@typeInfo(Instruction.Mnemonic).Enum.fields) |field| {
if (mem.eql(u8, field.name, @tagName(tag))) break @field(Instruction.Mnemonic, field.name);
} else unreachable;
const ops = emit.mir.instructions.items(.ops)[inst];
const data = emit.mir.instructions.items(.data)[inst];
const prefix: Instruction.Prefix = switch (ops) {
.lock_m_sib,
.lock_m_rip,
.lock_mi_sib_u,
.lock_mi_rip_u,
.lock_mi_sib_s,
.lock_mi_rip_s,
.lock_mr_sib,
.lock_mr_rip,
.lock_moffs_rax,
=> .lock,
else => .none,
};
var op1: Instruction.Operand = .none;
var op2: Instruction.Operand = .none;
var op3: Instruction.Operand = .none;
var op4: Instruction.Operand = .none;
switch (ops) {
.none => {},
.i_s => op1 = .{ .imm = Immediate.s(@bitCast(i32, data.i)) },
.i_u => op1 = .{ .imm = Immediate.u(data.i) },
.r => op1 = .{ .reg = data.r },
.rr => {
op1 = .{ .reg = data.rr.r1 };
op2 = .{ .reg = data.rr.r2 };
},
.rrr => {
op1 = .{ .reg = data.rrr.r1 };
op2 = .{ .reg = data.rrr.r2 };
op3 = .{ .reg = data.rrr.r3 };
},
.ri_s, .ri_u => {
const imm = switch (ops) {
.ri_s => Immediate.s(@bitCast(i32, data.ri.i)),
.ri_u => Immediate.u(data.ri.i),
else => unreachable,
};
op1 = .{ .reg = data.ri.r };
op2 = .{ .imm = imm };
},
.ri64 => {
const imm64 = emit.mir.extraData(Mir.Imm64, data.rx.payload).data;
op1 = .{ .reg = data.rx.r };
op2 = .{ .imm = Immediate.u(Mir.Imm64.decode(imm64)) };
},
.rri_s, .rri_u => {
const imm = switch (ops) {
.rri_s => Immediate.s(@bitCast(i32, data.rri.i)),
.rri_u => Immediate.u(data.rri.i),
else => unreachable,
};
op1 = .{ .reg = data.rri.r1 };
op2 = .{ .reg = data.rri.r2 };
op3 = .{ .imm = imm };
},
.m_sib, .lock_m_sib => {
const msib = emit.mir.extraData(Mir.MemorySib, data.payload).data;
op1 = .{ .mem = Mir.MemorySib.decode(msib) };
},
.m_rip, .lock_m_rip => {
const mrip = emit.mir.extraData(Mir.MemoryRip, data.payload).data;
op1 = .{ .mem = Mir.MemoryRip.decode(mrip) };
},
.mi_sib_s, .mi_sib_u, .lock_mi_sib_s, .lock_mi_sib_u => {
const msib = emit.mir.extraData(Mir.MemorySib, data.ix.payload).data;
const imm = switch (ops) {
.mi_sib_s, .lock_mi_sib_s => Immediate.s(@bitCast(i32, data.ix.i)),
.mi_sib_u, .lock_mi_sib_u => Immediate.u(data.ix.i),
else => unreachable,
};
op1 = .{ .mem = Mir.MemorySib.decode(msib) };
op2 = .{ .imm = imm };
},
.mi_rip_u, .mi_rip_s, .lock_mi_rip_u, .lock_mi_rip_s => {
const mrip = emit.mir.extraData(Mir.MemoryRip, data.ix.payload).data;
const imm = switch (ops) {
.mi_rip_s, .lock_mi_rip_s => Immediate.s(@bitCast(i32, data.ix.i)),
.mi_rip_u, .lock_mi_rip_u => Immediate.u(data.ix.i),
else => unreachable,
};
op1 = .{ .mem = Mir.MemoryRip.decode(mrip) };
op2 = .{ .imm = imm };
},
.rm_sib, .mr_sib, .lock_mr_sib => {
const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data;
const op_r = .{ .reg = data.rx.r };
const op_m = .{ .mem = Mir.MemorySib.decode(msib) };
switch (ops) {
.rm_sib => {
op1 = op_r;
op2 = op_m;
},
.mr_sib, .lock_mr_sib => {
op1 = op_m;
op2 = op_r;
},
else => unreachable,
}
},
.rm_rip, .mr_rip, .lock_mr_rip => {
const mrip = emit.mir.extraData(Mir.MemoryRip, data.rx.payload).data;
const op_r = .{ .reg = data.rx.r };
const op_m = .{ .mem = Mir.MemoryRip.decode(mrip) };
switch (ops) {
.rm_rip => {
op1 = op_r;
op2 = op_m;
},
.mr_rip, .lock_mr_rip => {
op1 = op_m;
op2 = op_r;
},
else => unreachable,
}
},
.mrr_sib => {
const msib = emit.mir.extraData(Mir.MemorySib, data.rrx.payload).data;
op1 = .{ .mem = Mir.MemorySib.decode(msib) };
op2 = .{ .reg = data.rrx.r1 };
op2 = .{ .reg = data.rrx.r2 };
},
.mrr_rip => {
const mrip = emit.mir.extraData(Mir.MemoryRip, data.rrx.payload).data;
op1 = .{ .mem = Mir.MemoryRip.decode(mrip) };
op2 = .{ .reg = data.rrx.r1 };
op2 = .{ .reg = data.rrx.r2 };
},
.mri_sib => {
const msib = emit.mir.extraData(Mir.MemorySib, data.rix.payload).data;
op1 = .{ .mem = Mir.MemorySib.decode(msib) };
op2 = .{ .reg = data.rix.r };
op3 = .{ .imm = Immediate.u(data.rix.i) };
},
.mri_rip => {
const mrip = emit.mir.extraData(Mir.MemoryRip, data.rix.payload).data;
op1 = .{ .mem = Mir.MemoryRip.decode(mrip) };
op2 = .{ .reg = data.rix.r };
op3 = .{ .imm = Immediate.u(data.rix.i) };
},
else => return emit.fail("TODO handle generic encoding: {s}, {s}", .{
@tagName(mnemonic),
@tagName(ops),
}),
}
return emit.encode(mnemonic, .{
.prefix = prefix,
.op1 = op1,
.op2 = op2,
.op3 = op3,
.op4 = op4,
});
}
fn mirString(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void {
const ops = emit.mir.instructions.items(.ops)[inst];
switch (ops) {
.string => {
const data = emit.mir.instructions.items(.data)[inst].string;
const mnemonic = switch (tag) {
inline .cmps, .lods, .movs, .scas, .stos => |comptime_tag| switch (data.width) {
inline else => |comptime_width| @field(
Instruction.Mnemonic,
@tagName(comptime_tag) ++ @tagName(comptime_width),
),
},
else => unreachable,
};
return emit.encode(mnemonic, .{ .prefix = switch (data.repeat) {
inline else => |comptime_repeat| @field(Instruction.Prefix, @tagName(comptime_repeat)),
} });
},
else => unreachable,
}
}
fn mirCmpxchgBytes(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const ops = emit.mir.instructions.items(.ops)[inst];
const data = emit.mir.instructions.items(.data)[inst];
var op1: Instruction.Operand = .none;
switch (ops) {
.m_sib, .lock_m_sib => {
const sib = emit.mir.extraData(Mir.MemorySib, data.payload).data;
op1 = .{ .mem = Mir.MemorySib.decode(sib) };
},
.m_rip, .lock_m_rip => {
const rip = emit.mir.extraData(Mir.MemoryRip, data.payload).data;
op1 = .{ .mem = Mir.MemoryRip.decode(rip) };
},
else => unreachable,
}
const mnemonic: Instruction.Mnemonic = switch (op1.mem.bitSize()) {
64 => .cmpxchg8b,
128 => .cmpxchg16b,
else => unreachable,
};
return emit.encode(mnemonic, .{
.prefix = switch (ops) {
.m_sib, .m_rip => .none,
.lock_m_sib, .lock_m_rip => .lock,
else => unreachable,
},
.op1 = op1,
});
}
fn mirMovMoffs(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const ops = emit.mir.instructions.items(.ops)[inst];
const payload = emit.mir.instructions.items(.data)[inst].payload;
const moffs = emit.mir.extraData(Mir.MemoryMoffs, payload).data;
const seg = @intToEnum(Register, moffs.seg);
const offset = moffs.decodeOffset();
switch (ops) {
.rax_moffs => {
try emit.encode(.mov, .{
.op1 = .{ .reg = .rax },
.op2 = .{ .mem = Memory.moffs(seg, offset) },
});
},
.moffs_rax, .lock_moffs_rax => {
try emit.encode(.mov, .{
.prefix = switch (ops) {
.moffs_rax => .none,
.lock_moffs_rax => .lock,
else => unreachable,
},
.op1 = .{ .mem = Memory.moffs(seg, offset) },
.op2 = .{ .reg = .rax },
});
},
else => unreachable,
}
}
fn mirMovsx(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const ops = emit.mir.instructions.items(.ops)[inst];
const data = emit.mir.instructions.items(.data)[inst];
var op1: Instruction.Operand = .none;
var op2: Instruction.Operand = .none;
switch (ops) {
.rr => {
op1 = .{ .reg = data.rr.r1 };
op2 = .{ .reg = data.rr.r2 };
},
.rm_sib => {
const msib = emit.mir.extraData(Mir.MemorySib, data.rx.payload).data;
op1 = .{ .reg = data.rx.r };
op2 = .{ .mem = Mir.MemorySib.decode(msib) };
},
.rm_rip => {
const mrip = emit.mir.extraData(Mir.MemoryRip, data.rx.payload).data;
op1 = .{ .reg = data.rx.r };
op2 = .{ .mem = Mir.MemoryRip.decode(mrip) };
},
else => unreachable, // TODO
}
const mnemonic: Instruction.Mnemonic = switch (op1.bitSize()) {
32, 64 => if (op2.bitSize() == 32) .movsxd else .movsx,
else => .movsx,
};
return emit.encode(mnemonic, .{
.op1 = op1,
.op2 = op2,
});
}
fn mnemonicFromConditionCode(comptime basename: []const u8, cc: bits.Condition) Instruction.Mnemonic {
return switch (cc) {
inline else => |comptime_cc| @field(Instruction.Mnemonic, basename ++ @tagName(comptime_cc)),
};
}
fn mirCmovcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const ops = emit.mir.instructions.items(.ops)[inst];
switch (ops) {
.rr_cc => {
const data = emit.mir.instructions.items(.data)[inst].rr_cc;
const mnemonic = mnemonicFromConditionCode("cmov", data.cc);
return emit.encode(mnemonic, .{
.op1 = .{ .reg = data.r1 },
.op2 = .{ .reg = data.r2 },
});
},
.rm_sib_cc => {
const data = emit.mir.instructions.items(.data)[inst].rx_cc;
const extra = emit.mir.extraData(Mir.MemorySib, data.payload).data;
const mnemonic = mnemonicFromConditionCode("cmov", data.cc);
return emit.encode(mnemonic, .{
.op1 = .{ .reg = data.r },
.op2 = .{ .mem = Mir.MemorySib.decode(extra) },
});
},
.rm_rip_cc => {
const data = emit.mir.instructions.items(.data)[inst].rx_cc;
const extra = emit.mir.extraData(Mir.MemoryRip, data.payload).data;
const mnemonic = mnemonicFromConditionCode("cmov", data.cc);
return emit.encode(mnemonic, .{
.op1 = .{ .reg = data.r },
.op2 = .{ .mem = Mir.MemoryRip.decode(extra) },
});
},
else => unreachable,
}
}
fn mirSetcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const ops = emit.mir.instructions.items(.ops)[inst];
switch (ops) {
.r_cc => {
const data = emit.mir.instructions.items(.data)[inst].r_cc;
const mnemonic = mnemonicFromConditionCode("set", data.cc);
return emit.encode(mnemonic, .{
.op1 = .{ .reg = data.r },
});
},
.m_sib_cc => {
const data = emit.mir.instructions.items(.data)[inst].x_cc;
const extra = emit.mir.extraData(Mir.MemorySib, data.payload).data;
const mnemonic = mnemonicFromConditionCode("set", data.cc);
return emit.encode(mnemonic, .{
.op1 = .{ .mem = Mir.MemorySib.decode(extra) },
});
},
.m_rip_cc => {
const data = emit.mir.instructions.items(.data)[inst].x_cc;
const extra = emit.mir.extraData(Mir.MemoryRip, data.payload).data;
const mnemonic = mnemonicFromConditionCode("set", data.cc);
return emit.encode(mnemonic, .{
.op1 = .{ .mem = Mir.MemoryRip.decode(extra) },
});
},
else => unreachable, // TODO
}
}
fn mirJcc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const ops = emit.mir.instructions.items(.ops)[inst];
switch (ops) {
.inst_cc => {
const data = emit.mir.instructions.items(.data)[inst].inst_cc;
const mnemonic = mnemonicFromConditionCode("j", data.cc);
const source = emit.code.items.len;
try emit.encode(mnemonic, .{
.op1 = .{ .imm = Immediate.s(0) },
});
try emit.relocs.append(emit.bin_file.allocator, .{
.source = source,
.target = data.inst,
.offset = emit.code.items.len - 4,
.length = 6,
});
},
else => unreachable, // TODO
}
}
fn mirJmpReloc(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const target = emit.mir.instructions.items(.data)[inst].inst;
const source = emit.code.items.len;
try emit.encode(.jmp, .{
.op1 = .{ .imm = Immediate.s(0) },
});
try emit.relocs.append(emit.bin_file.allocator, .{
.source = source,
.target = target,
.offset = emit.code.items.len - 4,
.length = 5,
});
}
fn mirCallExtern(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const relocation = emit.mir.instructions.items(.data)[inst].relocation;
const offset = blk: {
try emit.encode(.call, .{
.op1 = .{ .imm = Immediate.s(0) },
});
break :blk @intCast(u32, emit.code.items.len) - 4;
};
if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
// Add relocation to the decl.
const atom_index = macho_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?;
const target = macho_file.getGlobalByIndex(relocation.sym_index);
try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
.type = @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_BRANCH),
.target = target,
.offset = offset,
.addend = 0,
.pcrel = true,
.length = 2,
});
} else if (emit.bin_file.cast(link.File.Coff)) |coff_file| {
// Add relocation to the decl.
const atom_index = coff_file.getAtomIndexForSymbol(.{ .sym_index = relocation.atom_index, .file = null }).?;
const target = coff_file.getGlobalByIndex(relocation.sym_index);
try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{
.type = .direct,
.target = target,
.offset = offset,
.addend = 0,
.pcrel = true,
.length = 2,
});
} else {
return emit.fail("TODO implement call_extern for linking backends different than MachO and COFF", .{});
}
}
fn mirPushPopRegisterList(emit: *Emit, tag: Mir.Inst.Tag, inst: Mir.Inst.Index) InnerError!void {
const payload = emit.mir.instructions.items(.data)[inst].payload;
const save_reg_list = emit.mir.extraData(Mir.SaveRegisterList, payload).data;
const base = @intToEnum(Register, save_reg_list.base_reg);
var disp: i32 = -@intCast(i32, save_reg_list.stack_end);
const reg_list = Mir.RegisterList.fromInt(save_reg_list.register_list);
const callee_preserved_regs = abi.getCalleePreservedRegs(emit.target.*);
for (callee_preserved_regs) |reg| {
if (reg_list.isSet(callee_preserved_regs, reg)) {
const op1: Instruction.Operand = .{ .mem = Memory.sib(.qword, .{
.base = base,
.disp = disp,
}) };
const op2: Instruction.Operand = .{ .reg = reg };
switch (tag) {
.push => try emit.encode(.mov, .{
.op1 = op1,
.op2 = op2,
}),
.pop => try emit.encode(.mov, .{
.op1 = op2,
.op2 = op1,
}),
else => unreachable,
}
disp += 8;
}
}
}
fn mirLeaLinker(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const ops = emit.mir.instructions.items(.ops)[inst];
const payload = emit.mir.instructions.items(.data)[inst].payload;
const metadata = emit.mir.extraData(Mir.LeaRegisterReloc, payload).data;
const reg = @intToEnum(Register, metadata.reg);
try emit.encode(.lea, .{
.op1 = .{ .reg = reg },
.op2 = .{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(reg.bitSize()), 0) },
});
const end_offset = emit.code.items.len;
if (emit.bin_file.cast(link.File.MachO)) |macho_file| {
const reloc_type = switch (ops) {
.got_reloc => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_GOT),
.direct_reloc => @enumToInt(std.macho.reloc_type_x86_64.X86_64_RELOC_SIGNED),
else => unreachable,
};
const atom_index = macho_file.getAtomIndexForSymbol(.{
.sym_index = metadata.atom_index,
.file = null,
}).?;
try link.File.MachO.Atom.addRelocation(macho_file, atom_index, .{
.type = reloc_type,
.target = .{ .sym_index = metadata.sym_index, .file = null },
.offset = @intCast(u32, end_offset - 4),
.addend = 0,
.pcrel = true,
.length = 2,
});
} else if (emit.bin_file.cast(link.File.Coff)) |coff_file| {
const atom_index = coff_file.getAtomIndexForSymbol(.{
.sym_index = metadata.atom_index,
.file = null,
}).?;
try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{
.type = switch (ops) {
.got_reloc => .got,
.direct_reloc => .direct,
.import_reloc => .import,
else => unreachable,
},
.target = switch (ops) {
.got_reloc, .direct_reloc => .{ .sym_index = metadata.sym_index, .file = null },
.import_reloc => coff_file.getGlobalByIndex(metadata.sym_index),
else => unreachable,
},
.offset = @intCast(u32, end_offset - 4),
.addend = 0,
.pcrel = true,
.length = 2,
});
} else {
return emit.fail("TODO implement lea reg, [rip + reloc] for linking backends different than MachO", .{});
}
}
fn mirDbgLine(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
const payload = emit.mir.instructions.items(.data)[inst].payload;
const dbg_line_column = emit.mir.extraData(Mir.DbgLineColumn, payload).data;
log.debug("mirDbgLine", .{});
try emit.dbgAdvancePCAndLine(dbg_line_column.line, dbg_line_column.column);
}
fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) InnerError!void {
fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) Error!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;
log.debug(" (advance pc={d} and line={d})", .{ delta_line, delta_pc });
@ -756,7 +209,7 @@ fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) InnerError!void {
.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;
const quant = @import("../../link/Plan9/aout.zig").getPCQuant(emit.lower.target.cpu.arch) catch unreachable;
// increasing the line number
try @import("../../link/Plan9.zig").changeLine(dbg_out.dbg_line, delta_line);
@ -792,34 +245,12 @@ fn dbgAdvancePCAndLine(emit: *Emit, line: u32, column: u32) InnerError!void {
}
}
fn mirDbgPrologueEnd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
_ = inst;
switch (emit.debug_output) {
.dwarf => |dw| {
try dw.setPrologueEnd();
log.debug("mirDbgPrologueEnd (line={d}, col={d})", .{
emit.prev_di_line,
emit.prev_di_column,
});
try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
},
.plan9 => {},
.none => {},
}
}
const link = @import("../../link.zig");
const log = std.log.scoped(.emit);
const mem = std.mem;
const std = @import("std");
fn mirDbgEpilogueBegin(emit: *Emit, inst: Mir.Inst.Index) InnerError!void {
_ = inst;
switch (emit.debug_output) {
.dwarf => |dw| {
try dw.setEpilogueBegin();
log.debug("mirDbgEpilogueBegin (line={d}, col={d})", .{
emit.prev_di_line,
emit.prev_di_column,
});
try emit.dbgAdvancePCAndLine(emit.prev_di_line, emit.prev_di_column);
},
.plan9 => {},
.none => {},
}
}
const DebugInfoOutput = @import("../../codegen.zig").DebugInfoOutput;
const Emit = @This();
const Lower = @import("Lower.zig");
const Mir = @import("Mir.zig");

View File

@ -7,30 +7,32 @@ const math = std.math;
const bits = @import("bits.zig");
const encoder = @import("encoder.zig");
const Instruction = encoder.Instruction;
const Operand = Instruction.Operand;
const Prefix = Instruction.Prefix;
const Register = bits.Register;
const Rex = encoder.Rex;
const LegacyPrefixes = encoder.LegacyPrefixes;
const table = @import("encodings.zig").table;
mnemonic: Mnemonic,
op_en: OpEn,
op1: Op,
op2: Op,
op3: Op,
op4: Op,
opc_len: u3,
opc: [7]u8,
modrm_ext: u3,
mode: Mode,
data: Data,
pub fn findByMnemonic(mnemonic: Mnemonic, args: Instruction.Init) !?Encoding {
const input_op1 = Op.fromOperand(args.op1);
const input_op2 = Op.fromOperand(args.op2);
const input_op3 = Op.fromOperand(args.op3);
const input_op4 = Op.fromOperand(args.op4);
const Data = struct {
op_en: OpEn,
ops: [4]Op,
opc_len: u3,
opc: [7]u8,
modrm_ext: u3,
mode: Mode,
};
pub fn findByMnemonic(
prefix: Instruction.Prefix,
mnemonic: Mnemonic,
ops: []const Instruction.Operand,
) !?Encoding {
var input_ops = [1]Op{.none} ** 4;
for (input_ops[0..ops.len], ops) |*input_op, op| input_op.* = Op.fromOperand(op);
const ops = &[_]Instruction.Operand{ args.op1, args.op2, args.op3, args.op4 };
const rex_required = for (ops) |op| switch (op) {
.reg => |r| switch (r) {
.spl, .bpl, .sil, .dil => break true,
@ -60,88 +62,29 @@ pub fn findByMnemonic(mnemonic: Mnemonic, args: Instruction.Init) !?Encoding {
if ((rex_required or rex_extended) and rex_invalid) return error.CannotEncode;
// TODO work out what is the maximum number of variants we can actually find in one swoop.
var candidates: [10]Encoding = undefined;
var count: usize = 0;
for (table) |entry| {
var enc = Encoding{
.mnemonic = entry[0],
.op_en = entry[1],
.op1 = entry[2],
.op2 = entry[3],
.op3 = entry[4],
.op4 = entry[5],
.opc_len = @intCast(u3, entry[6].len),
.opc = undefined,
.modrm_ext = entry[7],
.mode = entry[8],
};
std.mem.copy(u8, &enc.opc, entry[6]);
if (enc.mnemonic == mnemonic and
input_op1.isSubset(enc.op1, enc.mode) and
input_op2.isSubset(enc.op2, enc.mode) and
input_op3.isSubset(enc.op3, enc.mode) and
input_op4.isSubset(enc.op4, enc.mode))
{
if (rex_required) {
switch (enc.mode) {
.rex, .long => {
candidates[count] = enc;
count += 1;
},
else => {},
}
} else {
if (enc.mode != .rex) {
candidates[count] = enc;
count += 1;
}
}
var shortest_enc: ?Encoding = null;
var shortest_len: ?usize = null;
next: for (mnemonic_to_encodings_map[@enumToInt(mnemonic)]) |data| {
switch (data.mode) {
.rex => if (!rex_required) continue,
.long => {},
else => if (rex_required) continue,
}
for (input_ops, data.ops) |input_op, data_op|
if (!input_op.isSubset(data_op, data.mode)) continue :next;
const enc = Encoding{ .mnemonic = mnemonic, .data = data };
if (shortest_enc) |previous_shortest_enc| {
const len = estimateInstructionLength(prefix, enc, ops);
const previous_shortest_len = shortest_len orelse
estimateInstructionLength(prefix, previous_shortest_enc, ops);
if (len < previous_shortest_len) {
shortest_enc = enc;
shortest_len = len;
} else shortest_len = previous_shortest_len;
} else shortest_enc = enc;
}
if (count == 0) return null;
if (count == 1) return candidates[0];
const EncodingLength = struct {
fn estimate(encoding: Encoding, params: Instruction.Init) usize {
var inst = Instruction{
.op1 = params.op1,
.op2 = params.op2,
.op3 = params.op3,
.op4 = params.op4,
.prefix = params.prefix,
.encoding = encoding,
};
var cwriter = std.io.countingWriter(std.io.null_writer);
inst.encode(cwriter.writer()) catch unreachable; // Not allowed to fail here unless OOM.
return @intCast(usize, cwriter.bytes_written);
}
};
var shortest_encoding: ?struct {
index: usize,
len: usize,
} = null;
var i: usize = 0;
while (i < count) : (i += 1) {
const candidate = candidates[i];
switch (candidate.mode) {
.long, .rex => if (rex_invalid) return error.CannotEncode,
else => {},
}
const len = EncodingLength.estimate(candidate, args);
const current = shortest_encoding orelse {
shortest_encoding = .{ .index = i, .len = len };
continue;
};
if (len < current.len) {
shortest_encoding = .{ .index = i, .len = len };
}
}
return candidates[shortest_encoding.?.index];
return shortest_enc;
}
/// Returns first matching encoding by opcode.
@ -149,57 +92,45 @@ pub fn findByOpcode(opc: []const u8, prefixes: struct {
legacy: LegacyPrefixes,
rex: Rex,
}, modrm_ext: ?u3) ?Encoding {
for (table) |entry| {
const enc = Encoding{
.mnemonic = entry[0],
.op_en = entry[1],
.op1 = entry[2],
.op2 = entry[3],
.op3 = entry[4],
.op4 = entry[5],
.opc_len = entry[6],
.opc = .{ entry[7], entry[8], entry[9] },
.modrm_ext = entry[10],
.mode = entry[11],
};
const match = match: {
if (modrm_ext) |ext| {
break :match ext == enc.modrm_ext and std.mem.eql(u8, enc.opcode(), opc);
for (mnemonic_to_encodings_map, 0..) |encs, mnemonic_int| for (encs) |data| {
const enc = Encoding{ .mnemonic = @intToEnum(Mnemonic, mnemonic_int), .data = data };
if (modrm_ext) |ext| if (ext != data.modrm_ext) continue;
if (!std.mem.eql(u8, opc, enc.opcode())) continue;
if (prefixes.rex.w) {
switch (data.mode) {
.short, .fpu, .sse, .sse2, .sse4_1, .none => continue,
.long, .rex => {},
}
break :match std.mem.eql(u8, enc.opcode(), opc);
};
if (match) {
if (prefixes.rex.w) {
switch (enc.mode) {
.fpu, .sse, .sse2, .sse4_1, .none => {},
.long, .rex => return enc,
}
} else if (prefixes.rex.present and !prefixes.rex.isSet()) {
if (enc.mode == .rex) return enc;
} else if (prefixes.legacy.prefix_66) {
switch (enc.operandBitSize()) {
16 => return enc,
} else if (prefixes.rex.present and !prefixes.rex.isSet()) {
switch (data.mode) {
.rex => {},
else => continue,
}
} else if (prefixes.legacy.prefix_66) {
switch (enc.operandBitSize()) {
16 => {},
else => continue,
}
} else {
switch (data.mode) {
.none => switch (enc.operandBitSize()) {
16 => continue,
else => {},
}
} else {
if (enc.mode == .none) {
switch (enc.operandBitSize()) {
16 => {},
else => return enc,
}
}
},
else => continue,
}
}
}
return enc;
};
return null;
}
pub fn opcode(encoding: *const Encoding) []const u8 {
return encoding.opc[0..encoding.opc_len];
return encoding.data.opc[0..encoding.data.opc_len];
}
pub fn mandatoryPrefix(encoding: *const Encoding) ?u8 {
const prefix = encoding.opc[0];
const prefix = encoding.data.opc[0];
return switch (prefix) {
0x66, 0xf2, 0xf3 => prefix,
else => null,
@ -207,27 +138,27 @@ pub fn mandatoryPrefix(encoding: *const Encoding) ?u8 {
}
pub fn modRmExt(encoding: Encoding) u3 {
return switch (encoding.op_en) {
.m, .mi, .m1, .mc => encoding.modrm_ext,
return switch (encoding.data.op_en) {
.m, .mi, .m1, .mc => encoding.data.modrm_ext,
else => unreachable,
};
}
pub fn operandBitSize(encoding: Encoding) u64 {
switch (encoding.mode) {
switch (encoding.data.mode) {
.short => return 16,
.long => return 64,
else => {},
}
const bit_size: u64 = switch (encoding.op_en) {
.np => switch (encoding.op1) {
const bit_size: u64 = switch (encoding.data.op_en) {
.np => switch (encoding.data.ops[0]) {
.o16 => 16,
.o32 => 32,
.o64 => 64,
else => 32,
},
.td => encoding.op2.bitSize(),
else => encoding.op1.bitSize(),
.td => encoding.data.ops[1].bitSize(),
else => encoding.data.ops[0].bitSize(),
};
return bit_size;
}
@ -240,7 +171,7 @@ pub fn format(
) !void {
_ = options;
_ = fmt;
switch (encoding.mode) {
switch (encoding.data.mode) {
.long => try writer.writeAll("REX.W + "),
else => {},
}
@ -249,10 +180,10 @@ pub fn format(
try writer.print("{x:0>2} ", .{byte});
}
switch (encoding.op_en) {
switch (encoding.data.op_en) {
.np, .fd, .td, .i, .zi, .d => {},
.o, .oi => {
const tag = switch (encoding.op1) {
const tag = switch (encoding.data.ops[0]) {
.r8 => "rb",
.r16 => "rw",
.r32 => "rd",
@ -265,12 +196,12 @@ pub fn format(
.mr, .rm, .rmi, .mri, .mrc => try writer.writeAll("/r "),
}
switch (encoding.op_en) {
switch (encoding.data.op_en) {
.i, .d, .zi, .oi, .mi, .rmi, .mri => {
const op = switch (encoding.op_en) {
.i, .d => encoding.op1,
.zi, .oi, .mi => encoding.op2,
.rmi, .mri => encoding.op3,
const op = switch (encoding.data.op_en) {
.i, .d => encoding.data.ops[0],
.zi, .oi, .mi => encoding.data.ops[1],
.rmi, .mri => encoding.data.ops[2],
else => unreachable,
};
const tag = switch (op) {
@ -290,13 +221,12 @@ pub fn format(
try writer.print("{s} ", .{@tagName(encoding.mnemonic)});
const ops = &[_]Op{ encoding.op1, encoding.op2, encoding.op3, encoding.op4 };
for (ops) |op| switch (op) {
for (encoding.data.ops) |op| switch (op) {
.none, .o16, .o32, .o64 => break,
else => try writer.print("{s} ", .{@tagName(op)}),
};
const op_en = switch (encoding.op_en) {
const op_en = switch (encoding.data.op_en) {
.zi => .i,
else => |op_en| op_en,
};
@ -604,3 +534,53 @@ pub const Mode = enum {
sse2,
sse4_1,
};
fn estimateInstructionLength(prefix: Prefix, encoding: Encoding, ops: []const Operand) usize {
var inst = Instruction{
.prefix = prefix,
.encoding = encoding,
.ops = [1]Operand{.none} ** 4,
};
std.mem.copy(Operand, &inst.ops, ops);
var cwriter = std.io.countingWriter(std.io.null_writer);
inst.encode(cwriter.writer()) catch unreachable; // Not allowed to fail here unless OOM.
return @intCast(usize, cwriter.bytes_written);
}
const mnemonic_to_encodings_map = init: {
@setEvalBranchQuota(100_000);
const encodings = @import("encodings.zig");
var entries = encodings.table;
std.sort.sort(encodings.Entry, &entries, {}, struct {
fn lessThan(_: void, lhs: encodings.Entry, rhs: encodings.Entry) bool {
return @enumToInt(lhs[0]) < @enumToInt(rhs[0]);
}
}.lessThan);
var data_storage: [entries.len]Data = undefined;
var mnemonic_map: [@typeInfo(Mnemonic).Enum.fields.len][]const Data = undefined;
var mnemonic_int = 0;
var mnemonic_start = 0;
for (&data_storage, entries, 0..) |*data, entry, data_index| {
data.* = .{
.op_en = entry[1],
.ops = undefined,
.opc_len = entry[3].len,
.opc = undefined,
.modrm_ext = entry[4],
.mode = entry[5],
};
std.mem.copy(Op, &data.ops, entry[2]);
std.mem.copy(u8, &data.opc, entry[3]);
while (mnemonic_int < @enumToInt(entry[0])) : (mnemonic_int += 1) {
mnemonic_map[mnemonic_int] = data_storage[mnemonic_start..data_index];
mnemonic_start = data_index;
}
}
while (mnemonic_int < mnemonic_map.len) : (mnemonic_int += 1) {
mnemonic_map[mnemonic_int] = data_storage[mnemonic_start..];
mnemonic_start = data_storage.len;
}
break :init mnemonic_map;
};

465
src/arch/x86_64/Lower.zig Normal file
View File

@ -0,0 +1,465 @@
//! This file contains the functionality for lowering x86_64 MIR to Instructions
allocator: Allocator,
mir: Mir,
target: *const std.Target,
err_msg: ?*ErrorMsg = null,
src_loc: Module.SrcLoc,
result: [
std.mem.max(usize, &.{
abi.Win64.callee_preserved_regs.len,
abi.SysV.callee_preserved_regs.len,
})
]Instruction = undefined,
result_len: usize = undefined,
pub const Error = error{
OutOfMemory,
LowerFail,
InvalidInstruction,
CannotEncode,
};
/// The returned slice is overwritten by the next call to lowerMir.
pub fn lowerMir(lower: *Lower, inst: Mir.Inst) Error![]const Instruction {
lower.result = undefined;
errdefer lower.result = undefined;
lower.result_len = 0;
defer lower.result_len = undefined;
switch (inst.tag) {
.adc,
.add,
.@"and",
.bsf,
.bsr,
.bswap,
.bt,
.btc,
.btr,
.bts,
.call,
.cbw,
.cwde,
.cdqe,
.cwd,
.cdq,
.cqo,
.cmp,
.cmpxchg,
.div,
.fisttp,
.fld,
.idiv,
.imul,
.int3,
.jmp,
.lea,
.lfence,
.lzcnt,
.mfence,
.mov,
.movbe,
.movzx,
.mul,
.neg,
.nop,
.not,
.@"or",
.pop,
.popcnt,
.push,
.rcl,
.rcr,
.ret,
.rol,
.ror,
.sal,
.sar,
.sbb,
.sfence,
.shl,
.shld,
.shr,
.shrd,
.sub,
.syscall,
.@"test",
.tzcnt,
.ud2,
.xadd,
.xchg,
.xor,
.addss,
.cmpss,
.divss,
.maxss,
.minss,
.movss,
.mulss,
.roundss,
.subss,
.ucomiss,
.addsd,
.cmpsd,
.divsd,
.maxsd,
.minsd,
.movsd,
.mulsd,
.roundsd,
.subsd,
.ucomisd,
=> try lower.mirGeneric(inst),
.cmps,
.lods,
.movs,
.scas,
.stos,
=> try lower.mirString(inst),
.cmpxchgb => try lower.mirCmpxchgBytes(inst),
.jmp_reloc => try lower.emit(.none, .jmp, &.{.{ .imm = Immediate.s(0) }}),
.call_extern => try lower.emit(.none, .call, &.{.{ .imm = Immediate.s(0) }}),
.lea_linker => try lower.mirLeaLinker(inst),
.mov_moffs => try lower.mirMovMoffs(inst),
.movsx => try lower.mirMovsx(inst),
.cmovcc => try lower.mirCmovcc(inst),
.setcc => try lower.mirSetcc(inst),
.jcc => try lower.emit(.none, mnem_cc(.j, inst.data.inst_cc.cc), &.{.{ .imm = Immediate.s(0) }}),
.push_regs, .pop_regs => try lower.mirPushPopRegisterList(inst),
.dbg_line,
.dbg_prologue_end,
.dbg_epilogue_begin,
.dead,
=> {},
}
return lower.result[0..lower.result_len];
}
pub fn fail(lower: *Lower, comptime format: []const u8, args: anytype) Error {
@setCold(true);
assert(lower.err_msg == null);
lower.err_msg = try ErrorMsg.create(lower.allocator, lower.src_loc, format, args);
return error.LowerFail;
}
fn mnem_cc(comptime base: @Type(.EnumLiteral), cc: bits.Condition) Mnemonic {
return switch (cc) {
inline else => |c| @field(Mnemonic, @tagName(base) ++ @tagName(c)),
};
}
fn imm(lower: Lower, ops: Mir.Inst.Ops, i: u32) Immediate {
return switch (ops) {
.rri_s,
.ri_s,
.i_s,
.mi_sib_s,
.mi_rip_s,
.lock_mi_sib_s,
.lock_mi_rip_s,
=> Immediate.s(@bitCast(i32, i)),
.rri_u,
.ri_u,
.i_u,
.mi_sib_u,
.mi_rip_u,
.lock_mi_sib_u,
.lock_mi_rip_u,
.mri_sib,
.mri_rip,
=> Immediate.u(i),
.ri64 => Immediate.u(lower.mir.extraData(Mir.Imm64, i).data.decode()),
else => unreachable,
};
}
fn mem(lower: Lower, ops: Mir.Inst.Ops, payload: u32) Memory {
return switch (ops) {
.rm_sib,
.rm_sib_cc,
.m_sib,
.m_sib_cc,
.mi_sib_u,
.mi_sib_s,
.mr_sib,
.mrr_sib,
.mri_sib,
.lock_m_sib,
.lock_mi_sib_u,
.lock_mi_sib_s,
.lock_mr_sib,
=> lower.mir.extraData(Mir.MemorySib, payload).data.decode(),
.rm_rip,
.rm_rip_cc,
.m_rip,
.m_rip_cc,
.mi_rip_u,
.mi_rip_s,
.mr_rip,
.mrr_rip,
.mri_rip,
.lock_m_rip,
.lock_mi_rip_u,
.lock_mi_rip_s,
.lock_mr_rip,
=> lower.mir.extraData(Mir.MemoryRip, payload).data.decode(),
.rax_moffs,
.moffs_rax,
.lock_moffs_rax,
=> lower.mir.extraData(Mir.MemoryMoffs, payload).data.decode(),
else => unreachable,
};
}
fn emit(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operand) Error!void {
lower.result[lower.result_len] = try Instruction.new(prefix, mnemonic, ops);
lower.result_len += 1;
}
fn mirGeneric(lower: *Lower, inst: Mir.Inst) Error!void {
try lower.emit(switch (inst.ops) {
else => .none,
.lock_m_sib,
.lock_m_rip,
.lock_mi_sib_u,
.lock_mi_rip_u,
.lock_mi_sib_s,
.lock_mi_rip_s,
.lock_mr_sib,
.lock_mr_rip,
.lock_moffs_rax,
=> .lock,
}, switch (inst.tag) {
inline else => |tag| if (@hasField(Mnemonic, @tagName(tag)))
@field(Mnemonic, @tagName(tag))
else
unreachable,
}, switch (inst.ops) {
.none => &.{},
.i_s, .i_u => &.{
.{ .imm = lower.imm(inst.ops, inst.data.i) },
},
.r => &.{
.{ .reg = inst.data.r },
},
.rr => &.{
.{ .reg = inst.data.rr.r1 },
.{ .reg = inst.data.rr.r2 },
},
.rrr => &.{
.{ .reg = inst.data.rrr.r1 },
.{ .reg = inst.data.rrr.r2 },
.{ .reg = inst.data.rrr.r3 },
},
.ri_s, .ri_u => &.{
.{ .reg = inst.data.ri.r },
.{ .imm = lower.imm(inst.ops, inst.data.ri.i) },
},
.ri64 => &.{
.{ .reg = inst.data.rx.r },
.{ .imm = lower.imm(inst.ops, inst.data.rx.payload) },
},
.rri_s, .rri_u => &.{
.{ .reg = inst.data.rri.r1 },
.{ .reg = inst.data.rri.r2 },
.{ .imm = lower.imm(inst.ops, inst.data.rri.i) },
},
.m_sib, .lock_m_sib, .m_rip, .lock_m_rip => &.{
.{ .mem = lower.mem(inst.ops, inst.data.payload) },
},
.mi_sib_s,
.lock_mi_sib_s,
.mi_sib_u,
.lock_mi_sib_u,
.mi_rip_u,
.lock_mi_rip_u,
.mi_rip_s,
.lock_mi_rip_s,
=> &.{
.{ .mem = lower.mem(inst.ops, inst.data.ix.payload) },
.{ .imm = lower.imm(inst.ops, inst.data.ix.i) },
},
.rm_sib, .rm_rip => &.{
.{ .reg = inst.data.rx.r },
.{ .mem = lower.mem(inst.ops, inst.data.rx.payload) },
},
.mr_sib, .lock_mr_sib, .mr_rip, .lock_mr_rip => &.{
.{ .mem = lower.mem(inst.ops, inst.data.rx.payload) },
.{ .reg = inst.data.rx.r },
},
.mrr_sib, .mrr_rip => &.{
.{ .mem = lower.mem(inst.ops, inst.data.rrx.payload) },
.{ .reg = inst.data.rrx.r1 },
.{ .reg = inst.data.rrx.r2 },
},
.mri_sib, .mri_rip => &.{
.{ .mem = lower.mem(inst.ops, inst.data.rix.payload) },
.{ .reg = inst.data.rix.r },
.{ .imm = lower.imm(inst.ops, inst.data.rix.i) },
},
else => return lower.fail("TODO lower {s} {s}", .{ @tagName(inst.tag), @tagName(inst.ops) }),
});
}
fn mirString(lower: *Lower, inst: Mir.Inst) Error!void {
switch (inst.ops) {
.string => try lower.emit(switch (inst.data.string.repeat) {
inline else => |repeat| @field(Prefix, @tagName(repeat)),
}, switch (inst.tag) {
inline .cmps, .lods, .movs, .scas, .stos => |tag| switch (inst.data.string.width) {
inline else => |width| @field(Mnemonic, @tagName(tag) ++ @tagName(width)),
},
else => unreachable,
}, &.{}),
else => return lower.fail("TODO lower {s} {s}", .{ @tagName(inst.tag), @tagName(inst.ops) }),
}
}
fn mirCmpxchgBytes(lower: *Lower, inst: Mir.Inst) Error!void {
const ops: [1]Operand = switch (inst.ops) {
.m_sib, .lock_m_sib, .m_rip, .lock_m_rip => .{
.{ .mem = lower.mem(inst.ops, inst.data.payload) },
},
else => return lower.fail("TODO lower {s} {s}", .{ @tagName(inst.tag), @tagName(inst.ops) }),
};
try lower.emit(switch (inst.ops) {
.m_sib, .m_rip => .none,
.lock_m_sib, .lock_m_rip => .lock,
else => unreachable,
}, switch (@divExact(ops[0].bitSize(), 8)) {
8 => .cmpxchg8b,
16 => .cmpxchg16b,
else => return lower.fail("invalid operand for {s}", .{@tagName(inst.tag)}),
}, &ops);
}
fn mirMovMoffs(lower: *Lower, inst: Mir.Inst) Error!void {
try lower.emit(switch (inst.ops) {
.rax_moffs, .moffs_rax => .none,
.lock_moffs_rax => .lock,
else => return lower.fail("TODO lower {s} {s}", .{ @tagName(inst.tag), @tagName(inst.ops) }),
}, .mov, switch (inst.ops) {
.rax_moffs => &.{
.{ .reg = .rax },
.{ .mem = lower.mem(inst.ops, inst.data.payload) },
},
.moffs_rax, .lock_moffs_rax => &.{
.{ .mem = lower.mem(inst.ops, inst.data.payload) },
.{ .reg = .rax },
},
else => unreachable,
});
}
fn mirMovsx(lower: *Lower, inst: Mir.Inst) Error!void {
const ops: [2]Operand = switch (inst.ops) {
.rr => .{
.{ .reg = inst.data.rr.r1 },
.{ .reg = inst.data.rr.r2 },
},
.rm_sib, .rm_rip => .{
.{ .reg = inst.data.rx.r },
.{ .mem = lower.mem(inst.ops, inst.data.rx.payload) },
},
else => return lower.fail("TODO lower {s} {s}", .{ @tagName(inst.tag), @tagName(inst.ops) }),
};
try lower.emit(.none, switch (ops[0].bitSize()) {
32, 64 => switch (ops[1].bitSize()) {
32 => .movsxd,
else => .movsx,
},
else => .movsx,
}, &ops);
}
fn mirCmovcc(lower: *Lower, inst: Mir.Inst) Error!void {
switch (inst.ops) {
.rr_cc => try lower.emit(.none, mnem_cc(.cmov, inst.data.rr_cc.cc), &.{
.{ .reg = inst.data.rr_cc.r1 },
.{ .reg = inst.data.rr_cc.r2 },
}),
.rm_sib_cc, .rm_rip_cc => try lower.emit(.none, mnem_cc(.cmov, inst.data.rx_cc.cc), &.{
.{ .reg = inst.data.rx_cc.r },
.{ .mem = lower.mem(inst.ops, inst.data.rx_cc.payload) },
}),
else => return lower.fail("TODO lower {s} {s}", .{ @tagName(inst.tag), @tagName(inst.ops) }),
}
}
fn mirSetcc(lower: *Lower, inst: Mir.Inst) Error!void {
switch (inst.ops) {
.r_cc => try lower.emit(.none, mnem_cc(.set, inst.data.r_cc.cc), &.{
.{ .reg = inst.data.r_cc.r },
}),
.m_sib_cc, .m_rip_cc => try lower.emit(.none, mnem_cc(.set, inst.data.x_cc.cc), &.{
.{ .mem = lower.mem(inst.ops, inst.data.x_cc.payload) },
}),
else => return lower.fail("TODO lower {s} {s}", .{ @tagName(inst.tag), @tagName(inst.ops) }),
}
}
fn mirPushPopRegisterList(lower: *Lower, inst: Mir.Inst) Error!void {
const save_reg_list = lower.mir.extraData(Mir.SaveRegisterList, inst.data.payload).data;
const base = @intToEnum(Register, save_reg_list.base_reg);
var disp: i32 = -@intCast(i32, save_reg_list.stack_end);
const reg_list = Mir.RegisterList.fromInt(save_reg_list.register_list);
const callee_preserved_regs = abi.getCalleePreservedRegs(lower.target.*);
for (callee_preserved_regs) |callee_preserved_reg| {
if (!reg_list.isSet(callee_preserved_regs, callee_preserved_reg)) continue;
const reg_op = Operand{ .reg = callee_preserved_reg };
const mem_op = Operand{ .mem = Memory.sib(.qword, .{ .base = base, .disp = disp }) };
try lower.emit(.none, .mov, switch (inst.tag) {
.push_regs => &.{ mem_op, reg_op },
.pop_regs => &.{ reg_op, mem_op },
else => unreachable,
});
disp += 8;
}
}
fn mirLeaLinker(lower: *Lower, inst: Mir.Inst) Error!void {
const metadata = lower.mir.extraData(Mir.LeaRegisterReloc, inst.data.payload).data;
const reg = @intToEnum(Register, metadata.reg);
try lower.emit(.none, .lea, &.{
.{ .reg = reg },
.{ .mem = Memory.rip(Memory.PtrSize.fromBitSize(reg.bitSize()), 0) },
});
}
const abi = @import("abi.zig");
const assert = std.debug.assert;
const bits = @import("bits.zig");
const encoder = @import("encoder.zig");
const std = @import("std");
const Air = @import("../../Air.zig");
const Allocator = std.mem.Allocator;
const ErrorMsg = Module.ErrorMsg;
const Immediate = bits.Immediate;
const Instruction = encoder.Instruction;
const Lower = @This();
const Memory = bits.Memory;
const Mir = @import("Mir.zig");
const Mnemonic = Instruction.Mnemonic;
const Module = @import("../../Module.zig");
const Operand = Instruction.Operand;
const Prefix = Instruction.Prefix;
const Register = bits.Register;

View File

@ -655,16 +655,19 @@ pub const MemoryMoffs = struct {
msb: u32,
lsb: u32,
pub fn encodeOffset(moffs: *MemoryMoffs, v: u64) void {
moffs.msb = @truncate(u32, v >> 32);
moffs.lsb = @truncate(u32, v);
pub fn encode(seg: Register, offset: u64) MemoryMoffs {
return .{
.seg = @enumToInt(seg),
.msb = @truncate(u32, offset >> 32),
.lsb = @truncate(u32, offset >> 0),
};
}
pub fn decodeOffset(moffs: *const MemoryMoffs) u64 {
var res: u64 = 0;
res |= (@intCast(u64, moffs.msb) << 32);
res |= @intCast(u64, moffs.lsb);
return res;
pub fn decode(moffs: MemoryMoffs) Memory {
return .{ .moffs = .{
.seg = @intToEnum(Register, moffs.seg),
.offset = @as(u64, moffs.msb) << 32 | @as(u64, moffs.lsb) << 0,
} };
}
};

View File

@ -515,7 +515,7 @@ pub const Memory = union(enum) {
return switch (mem) {
.rip => |r| r.ptr_size.bitSize(),
.sib => |s| s.ptr_size.bitSize(),
.moffs => unreachable,
.moffs => 64,
};
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff