zig/src/codegen/x86_64/Emit.zig
2025-10-10 22:47:47 -07:00

1026 lines
53 KiB
Zig

//! This file contains the functionality for emitting x86_64 MIR as machine code
lower: Lower,
bin_file: *link.File,
pt: Zcu.PerThread,
pic: bool,
atom_index: u32,
debug_output: link.File.DebugInfoOutput,
w: *std.Io.Writer,
prev_di_loc: Loc,
/// Relative to the beginning of `code`.
prev_di_pc: usize,
code_offset_mapping: std.ArrayListUnmanaged(u32),
relocs: std.ArrayListUnmanaged(Reloc),
table_relocs: std.ArrayListUnmanaged(TableReloc),
pub const Error = Lower.Error || error{
EmitFail,
NotFile,
} || std.posix.MMapError || std.posix.MRemapError || link.File.UpdateDebugInfoError;
pub fn emitMir(emit: *Emit) Error!void {
const comp = emit.bin_file.comp;
const gpa = comp.gpa;
try emit.code_offset_mapping.resize(gpa, emit.lower.mir.instructions.len);
emit.relocs.clearRetainingCapacity();
emit.table_relocs.clearRetainingCapacity();
var local_index: usize = 0;
for (0..emit.lower.mir.instructions.len) |mir_i| {
const mir_index: Mir.Inst.Index = @intCast(mir_i);
emit.code_offset_mapping.items[mir_index] = @intCast(emit.w.end);
const lowered = try emit.lower.lowerMir(mir_index);
var lowered_relocs = lowered.relocs;
lowered_inst: for (lowered.insts, 0..) |lowered_inst, lowered_index| {
if (lowered_inst.prefix == .directive) {
const start_offset: u32 = @intCast(emit.w.end);
switch (emit.debug_output) {
.dwarf => |dwarf| switch (lowered_inst.encoding.mnemonic) {
.@".cfi_def_cfa" => try dwarf.genDebugFrame(start_offset, .{ .def_cfa = .{
.reg = lowered_inst.ops[0].reg.dwarfNum(),
.off = lowered_inst.ops[1].imm.signed,
} }),
.@".cfi_def_cfa_register" => try dwarf.genDebugFrame(start_offset, .{
.def_cfa_register = lowered_inst.ops[0].reg.dwarfNum(),
}),
.@".cfi_def_cfa_offset" => try dwarf.genDebugFrame(start_offset, .{
.def_cfa_offset = lowered_inst.ops[0].imm.signed,
}),
.@".cfi_adjust_cfa_offset" => try dwarf.genDebugFrame(start_offset, .{
.adjust_cfa_offset = lowered_inst.ops[0].imm.signed,
}),
.@".cfi_offset" => try dwarf.genDebugFrame(start_offset, .{ .offset = .{
.reg = lowered_inst.ops[0].reg.dwarfNum(),
.off = lowered_inst.ops[1].imm.signed,
} }),
.@".cfi_val_offset" => try dwarf.genDebugFrame(start_offset, .{ .val_offset = .{
.reg = lowered_inst.ops[0].reg.dwarfNum(),
.off = lowered_inst.ops[1].imm.signed,
} }),
.@".cfi_rel_offset" => try dwarf.genDebugFrame(start_offset, .{ .rel_offset = .{
.reg = lowered_inst.ops[0].reg.dwarfNum(),
.off = lowered_inst.ops[1].imm.signed,
} }),
.@".cfi_register" => try dwarf.genDebugFrame(start_offset, .{ .register = .{
lowered_inst.ops[0].reg.dwarfNum(),
lowered_inst.ops[1].reg.dwarfNum(),
} }),
.@".cfi_restore" => try dwarf.genDebugFrame(start_offset, .{
.restore = lowered_inst.ops[0].reg.dwarfNum(),
}),
.@".cfi_undefined" => try dwarf.genDebugFrame(start_offset, .{
.undefined = lowered_inst.ops[0].reg.dwarfNum(),
}),
.@".cfi_same_value" => try dwarf.genDebugFrame(start_offset, .{
.same_value = lowered_inst.ops[0].reg.dwarfNum(),
}),
.@".cfi_remember_state" => try dwarf.genDebugFrame(start_offset, .remember_state),
.@".cfi_restore_state" => try dwarf.genDebugFrame(start_offset, .restore_state),
.@".cfi_escape" => try dwarf.genDebugFrame(start_offset, .{
.escape = lowered_inst.ops[0].bytes,
}),
else => unreachable,
},
.none => {},
}
continue;
}
var reloc_info_buf: [2]RelocInfo = undefined;
var reloc_info_index: usize = 0;
const ip = &emit.pt.zcu.intern_pool;
while (lowered_relocs.len > 0 and
lowered_relocs[0].lowered_inst_index == lowered_index) : ({
lowered_relocs = lowered_relocs[1..];
reloc_info_index += 1;
}) reloc_info_buf[reloc_info_index] = .{
.op_index = lowered_relocs[0].op_index,
.off = lowered_relocs[0].off,
.target = target: switch (lowered_relocs[0].target) {
.inst => |inst| .{ .index = inst, .is_extern = false, .type = .inst },
.table => .{ .index = undefined, .is_extern = false, .type = .table },
.nav => |nav| {
const sym_index = switch (try codegen.genNavRef(
emit.bin_file,
emit.pt,
emit.lower.src_loc,
nav,
emit.lower.target,
)) {
.sym_index => |sym_index| sym_index,
.fail => |em| {
assert(emit.lower.err_msg == null);
emit.lower.err_msg = em;
return error.EmitFail;
},
};
break :target switch (ip.getNav(nav).status) {
.unresolved => unreachable,
.type_resolved => |type_resolved| .{
.index = sym_index,
.is_extern = false,
.type = if (type_resolved.is_threadlocal and comp.config.any_non_single_threaded) .tlv else .symbol,
},
.fully_resolved => |fully_resolved| switch (ip.indexToKey(fully_resolved.val)) {
.@"extern" => |@"extern"| .{
.index = sym_index,
.is_extern = switch (@"extern".visibility) {
.default => true,
.hidden, .protected => false,
},
.type = if (@"extern".is_threadlocal and comp.config.any_non_single_threaded) .tlv else .symbol,
.force_pcrel_direct = switch (@"extern".relocation) {
.any => false,
.pcrel => true,
},
},
.variable => |variable| .{
.index = sym_index,
.is_extern = false,
.type = if (variable.is_threadlocal and comp.config.any_non_single_threaded) .tlv else .symbol,
},
else => .{ .index = sym_index, .is_extern = false, .type = .symbol },
},
};
},
.uav => |uav| .{
.index = switch (try emit.bin_file.lowerUav(
emit.pt,
uav.val,
Type.fromInterned(uav.orig_ty).ptrAlignment(emit.pt.zcu),
emit.lower.src_loc,
)) {
.sym_index => |sym_index| sym_index,
.fail => |em| {
assert(emit.lower.err_msg == null);
emit.lower.err_msg = em;
return error.EmitFail;
},
},
.is_extern = false,
.type = .symbol,
},
.lazy_sym => |lazy_sym| .{
.index = if (emit.bin_file.cast(.elf)) |elf_file|
elf_file.zigObjectPtr().?.getOrCreateMetadataForLazySymbol(elf_file, emit.pt, lazy_sym) catch |err|
return emit.fail("{s} creating lazy symbol", .{@errorName(err)})
else if (emit.bin_file.cast(.elf2)) |elf|
@intFromEnum(try elf.lazySymbol(lazy_sym))
else if (emit.bin_file.cast(.macho)) |macho_file|
macho_file.getZigObject().?.getOrCreateMetadataForLazySymbol(macho_file, emit.pt, lazy_sym) catch |err|
return emit.fail("{s} creating lazy symbol", .{@errorName(err)})
else if (emit.bin_file.cast(.coff2)) |elf|
@intFromEnum(try elf.lazySymbol(lazy_sym))
else
return emit.fail("lazy symbols unimplemented for {s}", .{@tagName(emit.bin_file.tag)}),
.is_extern = false,
.type = .symbol,
},
.extern_func => |extern_func| .{
.index = if (emit.bin_file.cast(.elf)) |elf_file|
try elf_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null)
else if (emit.bin_file.cast(.elf2)) |elf| @intFromEnum(try elf.globalSymbol(.{
.name = extern_func.toSlice(&emit.lower.mir).?,
.type = .FUNC,
})) else if (emit.bin_file.cast(.macho)) |macho_file|
try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null)
else if (emit.bin_file.cast(.coff2)) |coff| @intFromEnum(try coff.globalSymbol(
extern_func.toSlice(&emit.lower.mir).?,
switch (comp.compiler_rt_strat) {
.none, .lib, .obj, .zcu => null,
.dyn_lib => "compiler_rt",
},
)) else return emit.fail("external symbol unimplemented for {s}", .{@tagName(emit.bin_file.tag)}),
.is_extern = true,
.type = .symbol,
},
},
};
const reloc_info = reloc_info_buf[0..reloc_info_index];
for (reloc_info) |*reloc| switch (reloc.target.type) {
.inst, .table => {},
.symbol => {
switch (lowered_inst.encoding.mnemonic) {
.call => {
reloc.target.type = .branch;
try emit.encodeInst(lowered_inst, reloc_info);
continue :lowered_inst;
},
else => {},
}
if (emit.bin_file.cast(.elf) != null or emit.bin_file.cast(.elf2) != null) {
if (!emit.pic) switch (lowered_inst.encoding.mnemonic) {
.lea => try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .imm = .s(0) },
}, emit.lower.target), reloc_info),
.mov => try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{
.base = .{ .reg = .ds },
}) },
}, emit.lower.target), reloc_info),
else => unreachable,
} else if (reloc.target.is_extern) switch (lowered_inst.encoding.mnemonic) {
.lea => try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(.ptr, 0) },
}, emit.lower.target), reloc_info),
.mov => {
try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(.ptr, 0) },
}, emit.lower.target), reloc_info);
try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{ .base = .{
.reg = lowered_inst.ops[0].reg.to64(),
} }) },
}, emit.lower.target), &.{});
},
else => unreachable,
} else switch (lowered_inst.encoding.mnemonic) {
.lea => try emit.encodeInst(try .new(.none, .lea, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(.none, 0) },
}, emit.lower.target), reloc_info),
.mov => try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, 0) },
}, emit.lower.target), reloc_info),
else => unreachable,
}
} else if (emit.bin_file.cast(.macho)) |_| {
if (reloc.target.is_extern) switch (lowered_inst.encoding.mnemonic) {
.lea => try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(.ptr, 0) },
}, emit.lower.target), reloc_info),
.mov => {
try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(.ptr, 0) },
}, emit.lower.target), reloc_info);
try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initSib(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, .{ .base = .{
.reg = lowered_inst.ops[0].reg.to64(),
} }) },
}, emit.lower.target), &.{});
},
else => unreachable,
} else switch (lowered_inst.encoding.mnemonic) {
.lea => try emit.encodeInst(try .new(.none, .lea, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(.none, 0) },
}, emit.lower.target), reloc_info),
.mov => try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, 0) },
}, emit.lower.target), reloc_info),
else => unreachable,
}
} else if (emit.bin_file.cast(.coff2)) |_| {
switch (lowered_inst.encoding.mnemonic) {
.lea => try emit.encodeInst(try .new(.none, .lea, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(.none, 0) },
}, emit.lower.target), reloc_info),
.mov => try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initRip(lowered_inst.ops[reloc.op_index].mem.sib.ptr_size, 0) },
}, emit.lower.target), reloc_info),
else => unreachable,
}
} else return emit.fail("TODO implement relocs for {s}", .{
@tagName(emit.bin_file.tag),
});
continue :lowered_inst;
},
.branch, .tls => unreachable,
.tlv => {
if (emit.bin_file.cast(.elf) != null or emit.bin_file.cast(.elf2) != null) {
// TODO handle extern TLS vars, i.e., emit GD model
if (emit.pic) switch (lowered_inst.encoding.mnemonic) {
.lea, .mov => {
// Here, we currently assume local dynamic TLS vars, and so
// we emit LD model.
try emit.encodeInst(try .new(.none, .lea, &.{
.{ .reg = .rdi },
.{ .mem = .initRip(.none, 0) },
}, emit.lower.target), &.{.{
.op_index = 1,
.target = .{
.index = reloc.target.index,
.is_extern = false,
.type = .tls,
},
}});
try emit.encodeInst(try .new(.none, .call, &.{
.{ .imm = .s(0) },
}, emit.lower.target), &.{.{
.op_index = 0,
.target = .{
.index = if (emit.bin_file.cast(.elf)) |elf_file|
try elf_file.getGlobalSymbol("__tls_get_addr", null)
else if (emit.bin_file.cast(.elf2)) |elf| @intFromEnum(try elf.globalSymbol(.{
.name = "__tls_get_addr",
.type = .FUNC,
})) else unreachable,
.is_extern = true,
.type = .branch,
},
}});
try emit.encodeInst(try .new(.none, lowered_inst.encoding.mnemonic, &.{
lowered_inst.ops[0],
.{ .mem = .initSib(.none, .{
.base = .{ .reg = .rax },
.disp = std.math.minInt(i32),
}) },
}, emit.lower.target), reloc_info);
},
else => unreachable,
} else switch (lowered_inst.encoding.mnemonic) {
.lea, .mov => {
// Since we are linking statically, we emit LE model directly.
try emit.encodeInst(try .new(.none, .mov, &.{
.{ .reg = .rax },
.{ .mem = .initSib(.qword, .{ .base = .{ .reg = .fs } }) },
}, emit.lower.target), &.{});
try emit.encodeInst(try .new(.none, lowered_inst.encoding.mnemonic, &.{
lowered_inst.ops[0],
.{ .mem = .initSib(.none, .{
.base = .{ .reg = .rax },
.disp = std.math.minInt(i32),
}) },
}, emit.lower.target), reloc_info);
},
else => unreachable,
}
} else if (emit.bin_file.cast(.macho)) |_| switch (lowered_inst.encoding.mnemonic) {
.lea => {
try emit.encodeInst(try .new(.none, .mov, &.{
.{ .reg = .rdi },
.{ .mem = .initRip(.ptr, 0) },
}, emit.lower.target), reloc_info);
try emit.encodeInst(try .new(.none, .call, &.{
.{ .mem = .initSib(.qword, .{ .base = .{ .reg = .rdi } }) },
}, emit.lower.target), &.{});
try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .reg = .rax },
}, emit.lower.target), &.{});
},
.mov => {
try emit.encodeInst(try .new(.none, .mov, &.{
.{ .reg = .rdi },
.{ .mem = .initRip(.ptr, 0) },
}, emit.lower.target), reloc_info);
try emit.encodeInst(try .new(.none, .call, &.{
.{ .mem = .initSib(.qword, .{ .base = .{ .reg = .rdi } }) },
}, emit.lower.target), &.{});
try emit.encodeInst(try .new(.none, .mov, &.{
lowered_inst.ops[0],
.{ .mem = .initSib(.qword, .{ .base = .{ .reg = .rax } }) },
}, emit.lower.target), &.{});
},
else => unreachable,
} else if (emit.bin_file.cast(.coff2)) |coff| {
switch (emit.lower.target.cpu.arch) {
else => unreachable,
.x86 => {
try emit.encodeInst(try .new(.none, .mov, &.{
.{ .reg = .eax },
.{ .mem = .initSib(.qword, .{
.base = .{ .reg = .fs },
.disp = 4 * 11,
}) },
}, emit.lower.target), &.{});
try emit.encodeInst(try .new(.none, .mov, &.{
.{ .reg = .edi },
.{ .mem = .initSib(.dword, .{}) },
}, emit.lower.target), &.{.{
.op_index = 1,
.target = .{
.index = @intFromEnum(
try coff.globalSymbol("__tls_index", null),
),
.is_extern = false,
.type = .symbol,
},
}});
try emit.encodeInst(try .new(.none, .mov, &.{
.{ .reg = .eax },
.{ .mem = .initSib(.dword, .{
.base = .{ .reg = .eax },
.scale_index = .{ .index = .edi, .scale = 4 },
}) },
}, emit.lower.target), &.{});
try emit.encodeInst(try .new(.none, lowered_inst.encoding.mnemonic, &.{
lowered_inst.ops[0],
.{ .mem = .initSib(lowered_inst.ops[1].mem.sib.ptr_size, .{
.base = .{ .reg = .eax },
.disp = std.math.minInt(i32),
}) },
}, emit.lower.target), reloc_info);
},
.x86_64 => {
try emit.encodeInst(try .new(.none, .mov, &.{
.{ .reg = .rax },
.{ .mem = .initSib(.qword, .{
.base = .{ .reg = .gs },
.disp = 8 * 11,
}) },
}, emit.lower.target), &.{});
try emit.encodeInst(try .new(.none, .mov, &.{
.{ .reg = .edi },
.{ .mem = .initRip(.dword, 0) },
}, emit.lower.target), &.{.{
.op_index = 1,
.target = .{
.index = @intFromEnum(
try coff.globalSymbol("_tls_index", null),
),
.is_extern = false,
.type = .symbol,
},
}});
try emit.encodeInst(try .new(.none, .mov, &.{
.{ .reg = .rax },
.{ .mem = .initSib(.qword, .{
.base = .{ .reg = .rax },
.scale_index = .{ .index = .rdi, .scale = 8 },
}) },
}, emit.lower.target), &.{});
try emit.encodeInst(try .new(.none, lowered_inst.encoding.mnemonic, &.{
lowered_inst.ops[0],
.{ .mem = .initSib(lowered_inst.ops[1].mem.sib.ptr_size, .{
.base = .{ .reg = .rax },
.disp = std.math.minInt(i32),
}) },
}, emit.lower.target), reloc_info);
},
}
} else return emit.fail("TODO implement relocs for {s}", .{
@tagName(emit.bin_file.tag),
});
continue :lowered_inst;
},
};
try emit.encodeInst(lowered_inst, reloc_info);
}
assert(lowered_relocs.len == 0);
if (lowered.insts.len == 0) {
const mir_inst = emit.lower.mir.instructions.get(mir_index);
switch (mir_inst.tag) {
else => unreachable,
.pseudo => switch (mir_inst.ops) {
else => unreachable,
.pseudo_dbg_prologue_end_none => switch (emit.debug_output) {
.dwarf => |dwarf| try dwarf.setPrologueEnd(),
.none => {},
},
.pseudo_dbg_line_stmt_line_column => try emit.dbgAdvancePCAndLine(.{
.line = mir_inst.data.line_column.line,
.column = mir_inst.data.line_column.column,
.is_stmt = true,
}),
.pseudo_dbg_line_line_column => try emit.dbgAdvancePCAndLine(.{
.line = mir_inst.data.line_column.line,
.column = mir_inst.data.line_column.column,
.is_stmt = false,
}),
.pseudo_dbg_epilogue_begin_none => switch (emit.debug_output) {
.dwarf => |dwarf| {
try dwarf.setEpilogueBegin();
log.debug("mirDbgEpilogueBegin (line={d}, col={d})", .{
emit.prev_di_loc.line, emit.prev_di_loc.column,
});
try emit.dbgAdvancePCAndLine(emit.prev_di_loc);
},
.none => {},
},
.pseudo_dbg_enter_block_none => switch (emit.debug_output) {
.dwarf => |dwarf| {
log.debug("mirDbgEnterBlock (line={d}, col={d})", .{
emit.prev_di_loc.line, emit.prev_di_loc.column,
});
try dwarf.enterBlock(emit.w.end);
},
.none => {},
},
.pseudo_dbg_leave_block_none => switch (emit.debug_output) {
.dwarf => |dwarf| {
log.debug("mirDbgLeaveBlock (line={d}, col={d})", .{
emit.prev_di_loc.line, emit.prev_di_loc.column,
});
try dwarf.leaveBlock(emit.w.end);
},
.none => {},
},
.pseudo_dbg_enter_inline_func => switch (emit.debug_output) {
.dwarf => |dwarf| {
log.debug("mirDbgEnterInline (line={d}, col={d})", .{
emit.prev_di_loc.line, emit.prev_di_loc.column,
});
try dwarf.enterInlineFunc(mir_inst.data.ip_index, emit.w.end, emit.prev_di_loc.line, emit.prev_di_loc.column);
},
.none => {},
},
.pseudo_dbg_leave_inline_func => switch (emit.debug_output) {
.dwarf => |dwarf| {
log.debug("mirDbgLeaveInline (line={d}, col={d})", .{
emit.prev_di_loc.line, emit.prev_di_loc.column,
});
try dwarf.leaveInlineFunc(mir_inst.data.ip_index, emit.w.end);
},
.none => {},
},
.pseudo_dbg_arg_none,
.pseudo_dbg_arg_i_s,
.pseudo_dbg_arg_i_u,
.pseudo_dbg_arg_i_64,
.pseudo_dbg_arg_ro,
.pseudo_dbg_arg_fa,
.pseudo_dbg_arg_m,
.pseudo_dbg_var_none,
.pseudo_dbg_var_i_s,
.pseudo_dbg_var_i_u,
.pseudo_dbg_var_i_64,
.pseudo_dbg_var_ro,
.pseudo_dbg_var_fa,
.pseudo_dbg_var_m,
=> switch (emit.debug_output) {
.dwarf => |dwarf| {
var loc_buf: [2]link.File.Dwarf.Loc = undefined;
const loc: link.File.Dwarf.Loc = loc: switch (mir_inst.ops) {
else => unreachable,
.pseudo_dbg_arg_none, .pseudo_dbg_var_none => .empty,
.pseudo_dbg_arg_i_s,
.pseudo_dbg_arg_i_u,
.pseudo_dbg_var_i_s,
.pseudo_dbg_var_i_u,
=> .{ .stack_value = stack_value: {
loc_buf[0] = switch (emit.lower.imm(mir_inst.ops, mir_inst.data.i.i)) {
.signed => |s| .{ .consts = s },
.unsigned => |u| .{ .constu = u },
};
break :stack_value &loc_buf[0];
} },
.pseudo_dbg_arg_i_64, .pseudo_dbg_var_i_64 => .{ .stack_value = stack_value: {
loc_buf[0] = .{ .constu = mir_inst.data.i64 };
break :stack_value &loc_buf[0];
} },
.pseudo_dbg_arg_fa, .pseudo_dbg_var_fa => {
const reg_off = emit.lower.mir.resolveFrameAddr(mir_inst.data.fa);
break :loc .{ .plus = .{
reg: {
loc_buf[0] = .{ .breg = reg_off.reg.dwarfNum() };
break :reg &loc_buf[0];
},
off: {
loc_buf[1] = .{ .consts = reg_off.off };
break :off &loc_buf[1];
},
} };
},
.pseudo_dbg_arg_m, .pseudo_dbg_var_m => {
const mem = emit.lower.mir.resolveMemoryExtra(mir_inst.data.x.payload).decode();
break :loc .{ .plus = .{
base: {
loc_buf[0] = switch (mem.base()) {
.none => .{ .constu = 0 },
.reg => |reg| .{ .breg = reg.dwarfNum() },
.frame, .table, .rip_inst => unreachable,
.nav => |nav| .{ .addr_reloc = switch (codegen.genNavRef(
emit.bin_file,
emit.pt,
emit.lower.src_loc,
nav,
emit.lower.target,
) catch |err| switch (err) {
error.CodegenFail,
=> return emit.fail("unable to codegen: {s}", .{@errorName(err)}),
else => |e| return e,
}) {
.sym_index => |sym_index| sym_index,
.fail => |em| {
assert(emit.lower.err_msg == null);
emit.lower.err_msg = em;
return error.EmitFail;
},
} },
.uav => |uav| .{ .addr_reloc = switch (try emit.bin_file.lowerUav(
emit.pt,
uav.val,
Type.fromInterned(uav.orig_ty).ptrAlignment(emit.pt.zcu),
emit.lower.src_loc,
)) {
.sym_index => |sym_index| sym_index,
.fail => |em| {
assert(emit.lower.err_msg == null);
emit.lower.err_msg = em;
return error.EmitFail;
},
} },
.lazy_sym, .extern_func => unreachable,
};
break :base &loc_buf[0];
},
disp: {
loc_buf[1] = switch (mem.disp()) {
.signed => |s| .{ .consts = s },
.unsigned => |u| .{ .constu = u },
};
break :disp &loc_buf[1];
},
} };
},
};
const local = &emit.lower.mir.locals[local_index];
local_index += 1;
try dwarf.genLocalVarDebugInfo(
switch (mir_inst.ops) {
else => unreachable,
.pseudo_dbg_arg_none,
.pseudo_dbg_arg_i_s,
.pseudo_dbg_arg_i_u,
.pseudo_dbg_arg_i_64,
.pseudo_dbg_arg_ro,
.pseudo_dbg_arg_fa,
.pseudo_dbg_arg_m,
.pseudo_dbg_arg_val,
=> .arg,
.pseudo_dbg_var_none,
.pseudo_dbg_var_i_s,
.pseudo_dbg_var_i_u,
.pseudo_dbg_var_i_64,
.pseudo_dbg_var_ro,
.pseudo_dbg_var_fa,
.pseudo_dbg_var_m,
.pseudo_dbg_var_val,
=> .local_var,
},
local.name.toSlice(&emit.lower.mir),
.fromInterned(local.type),
loc,
);
},
.none => local_index += 1,
},
.pseudo_dbg_arg_val, .pseudo_dbg_var_val => switch (emit.debug_output) {
.dwarf => |dwarf| {
const local = &emit.lower.mir.locals[local_index];
local_index += 1;
try dwarf.genLocalConstDebugInfo(
emit.lower.src_loc,
switch (mir_inst.ops) {
else => unreachable,
.pseudo_dbg_arg_val => .comptime_arg,
.pseudo_dbg_var_val => .local_const,
},
local.name.toSlice(&emit.lower.mir),
.fromInterned(mir_inst.data.ip_index),
);
},
.none => local_index += 1,
},
.pseudo_dbg_var_args_none => switch (emit.debug_output) {
.dwarf => |dwarf| try dwarf.genVarArgsDebugInfo(),
.none => {},
},
.pseudo_dead_none => {},
},
}
}
}
for (emit.relocs.items) |reloc| {
const target = emit.code_offset_mapping.items[reloc.target];
const disp = @as(i64, @intCast(target)) - @as(i64, @intCast(reloc.inst_offset + reloc.inst_length)) + reloc.target_offset;
const inst_bytes = emit.w.buffered()[reloc.inst_offset..][0..reloc.inst_length];
switch (reloc.source_length) {
else => unreachable,
inline 1, 4 => |source_length| std.mem.writeInt(
@Type(.{ .int = .{ .signedness = .signed, .bits = @as(u16, 8) * source_length } }),
inst_bytes[reloc.source_offset..][0..source_length],
@intCast(disp),
.little,
),
}
}
if (emit.lower.mir.table.len > 0) {
const ptr_size = @divExact(emit.lower.target.ptrBitWidth(), 8);
var table_offset = std.mem.alignForward(u32, @intCast(emit.w.end), ptr_size);
if (emit.bin_file.cast(.elf)) |elf_file| {
const zo = elf_file.zigObjectPtr().?;
const atom = zo.symbol(emit.atom_index).atom(elf_file).?;
for (emit.table_relocs.items) |table_reloc| try atom.addReloc(gpa, .{
.r_offset = table_reloc.source_offset,
.r_info = @as(u64, emit.atom_index) << 32 | @intFromEnum(std.elf.R_X86_64.@"32"),
.r_addend = @as(i64, table_offset) + table_reloc.target_offset,
}, zo);
for (emit.lower.mir.table) |entry| {
try atom.addReloc(gpa, .{
.r_offset = table_offset,
.r_info = @as(u64, emit.atom_index) << 32 | @intFromEnum(std.elf.R_X86_64.@"64"),
.r_addend = emit.code_offset_mapping.items[entry],
}, zo);
table_offset += ptr_size;
}
try emit.w.splatByteAll(0, table_offset - emit.w.end);
} else if (emit.bin_file.cast(.elf2)) |elf| {
for (emit.table_relocs.items) |table_reloc| try elf.addReloc(
@enumFromInt(emit.atom_index),
table_reloc.source_offset,
@enumFromInt(emit.atom_index),
@as(i64, table_offset) + table_reloc.target_offset,
.{ .X86_64 = .@"32" },
);
for (emit.lower.mir.table) |entry| {
try elf.addReloc(
@enumFromInt(emit.atom_index),
table_offset,
@enumFromInt(emit.atom_index),
emit.code_offset_mapping.items[entry],
.{ .X86_64 = .@"64" },
);
table_offset += ptr_size;
}
try emit.w.splatByteAll(0, table_offset - emit.w.end);
} else unreachable;
}
}
pub fn deinit(emit: *Emit) void {
const gpa = emit.bin_file.comp.gpa;
emit.code_offset_mapping.deinit(gpa);
emit.relocs.deinit(gpa);
emit.table_relocs.deinit(gpa);
emit.* = undefined;
}
const RelocInfo = struct {
op_index: Lower.InstOpIndex,
off: i32 = 0,
target: Target,
const Target = struct {
index: u32,
is_extern: bool,
type: Target.Type,
force_pcrel_direct: bool = false,
const Type = enum { inst, table, symbol, branch, tls, tlv };
};
};
fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocInfo) Error!void {
const comp = emit.bin_file.comp;
const gpa = comp.gpa;
const start_offset: u32 = @intCast(emit.w.end);
lowered_inst.encode(emit.w, .{}) catch |err| switch (err) {
error.WriteFailed => return error.OutOfMemory,
else => |e| return e,
};
const end_offset: u32 = @intCast(emit.w.end);
for (reloc_info) |reloc| switch (reloc.target.type) {
.inst => {
const inst_length: u4 = @intCast(end_offset - start_offset);
const reloc_offset, const reloc_length = reloc_offset_length: {
var reloc_offset = inst_length;
var op_index: usize = lowered_inst.ops.len;
while (true) {
op_index -= 1;
const op = lowered_inst.encoding.data.ops[op_index];
if (op == .none) continue;
const is_mem = op.isMemory();
const enc_length: u4 = if (is_mem) switch (lowered_inst.ops[op_index].mem.sib.base) {
.rip_inst => 4,
else => unreachable,
} else @intCast(std.math.divCeil(u7, @intCast(op.immBitSize()), 8) catch unreachable);
reloc_offset -= enc_length;
if (op_index == reloc.op_index) break :reloc_offset_length .{ reloc_offset, enc_length };
assert(!is_mem);
}
};
try emit.relocs.append(emit.lower.allocator, .{
.inst_offset = start_offset,
.inst_length = inst_length,
.source_offset = reloc_offset,
.source_length = reloc_length,
.target = reloc.target.index,
.target_offset = reloc.off,
});
},
.table => try emit.table_relocs.append(emit.lower.allocator, .{
.source_offset = end_offset - 4,
.target_offset = reloc.off,
}),
.symbol => if (emit.bin_file.cast(.elf)) |elf_file| {
const zo = elf_file.zigObjectPtr().?;
const atom = zo.symbol(emit.atom_index).atom(elf_file).?;
const r_type: std.elf.R_X86_64 = if (!emit.pic)
.@"32"
else if (reloc.target.is_extern and !reloc.target.force_pcrel_direct)
.GOTPCREL
else
.PC32;
try atom.addReloc(gpa, .{
.r_offset = end_offset - 4,
.r_info = @as(u64, reloc.target.index) << 32 | @intFromEnum(r_type),
.r_addend = if (emit.pic) reloc.off - 4 else reloc.off,
}, zo);
} else if (emit.bin_file.cast(.macho)) |macho_file| {
const zo = macho_file.getZigObject().?;
const atom = zo.symbols.items[emit.atom_index].getAtom(macho_file).?;
try atom.addReloc(macho_file, .{
.tag = .@"extern",
.offset = end_offset - 4,
.target = reloc.target.index,
.addend = reloc.off,
.type = if (reloc.target.is_extern and !reloc.target.force_pcrel_direct) .got_load else .signed,
.meta = .{
.pcrel = true,
.has_subtractor = false,
.length = 2,
.symbolnum = @intCast(reloc.target.index),
},
});
} else if (emit.bin_file.cast(.elf2)) |elf| try elf.addReloc(
@enumFromInt(emit.atom_index),
end_offset - 4,
@enumFromInt(reloc.target.index),
reloc.off,
.{ .X86_64 = .@"32" },
) else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc(
@enumFromInt(emit.atom_index),
end_offset - 4,
@enumFromInt(reloc.target.index),
reloc.off,
.{ .AMD64 = .REL32 },
) else unreachable,
.branch => if (emit.bin_file.cast(.elf)) |elf_file| {
const zo = elf_file.zigObjectPtr().?;
const atom = zo.symbol(emit.atom_index).atom(elf_file).?;
const r_type: std.elf.R_X86_64 = .PLT32;
try atom.addReloc(gpa, .{
.r_offset = end_offset - 4,
.r_info = @as(u64, reloc.target.index) << 32 | @intFromEnum(r_type),
.r_addend = reloc.off - 4,
}, zo);
} else if (emit.bin_file.cast(.elf2)) |elf| try elf.addReloc(
@enumFromInt(emit.atom_index),
end_offset - 4,
@enumFromInt(reloc.target.index),
reloc.off - 4,
.{ .X86_64 = .PC32 },
) else if (emit.bin_file.cast(.macho)) |macho_file| {
const zo = macho_file.getZigObject().?;
const atom = zo.symbols.items[emit.atom_index].getAtom(macho_file).?;
try atom.addReloc(macho_file, .{
.tag = .@"extern",
.offset = end_offset - 4,
.target = reloc.target.index,
.addend = reloc.off,
.type = .branch,
.meta = .{
.pcrel = true,
.has_subtractor = false,
.length = 2,
.symbolnum = @intCast(reloc.target.index),
},
});
} else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc(
@enumFromInt(emit.atom_index),
end_offset - 4,
@enumFromInt(reloc.target.index),
reloc.off,
.{ .AMD64 = .REL32 },
) else return emit.fail("TODO implement {s} reloc for {s}", .{
@tagName(reloc.target.type), @tagName(emit.bin_file.tag),
}),
.tls => if (emit.bin_file.cast(.elf)) |elf_file| {
const zo = elf_file.zigObjectPtr().?;
const atom = zo.symbol(emit.atom_index).atom(elf_file).?;
const r_type: std.elf.R_X86_64 = if (emit.pic) .TLSLD else unreachable;
try atom.addReloc(gpa, .{
.r_offset = end_offset - 4,
.r_info = @as(u64, reloc.target.index) << 32 | @intFromEnum(r_type),
.r_addend = reloc.off - 4,
}, zo);
} else return emit.fail("TODO implement {s} reloc for {s}", .{
@tagName(reloc.target.type), @tagName(emit.bin_file.tag),
}),
.tlv => if (emit.bin_file.cast(.elf)) |elf_file| {
const zo = elf_file.zigObjectPtr().?;
const atom = zo.symbol(emit.atom_index).atom(elf_file).?;
const r_type: std.elf.R_X86_64 = if (emit.pic) .DTPOFF32 else .TPOFF32;
try atom.addReloc(gpa, .{
.r_offset = end_offset - 4,
.r_info = @as(u64, reloc.target.index) << 32 | @intFromEnum(r_type),
.r_addend = reloc.off,
}, zo);
} else if (emit.bin_file.cast(.elf2)) |elf| try elf.addReloc(
@enumFromInt(emit.atom_index),
end_offset - 4,
@enumFromInt(reloc.target.index),
reloc.off,
.{ .X86_64 = .TPOFF32 },
) else if (emit.bin_file.cast(.macho)) |macho_file| {
const zo = macho_file.getZigObject().?;
const atom = zo.symbols.items[emit.atom_index].getAtom(macho_file).?;
try atom.addReloc(macho_file, .{
.tag = .@"extern",
.offset = end_offset - 4,
.target = reloc.target.index,
.addend = reloc.off,
.type = .tlv,
.meta = .{
.pcrel = true,
.has_subtractor = false,
.length = 2,
.symbolnum = @intCast(reloc.target.index),
},
});
} else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc(
@enumFromInt(emit.atom_index),
end_offset - 4,
@enumFromInt(reloc.target.index),
reloc.off,
.{ .AMD64 = .SECREL },
) else return emit.fail("TODO implement {s} reloc for {s}", .{
@tagName(reloc.target.type), @tagName(emit.bin_file.tag),
}),
};
}
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.
inst_offset: u32,
/// Length of the instruction.
inst_length: u4,
/// Offset of the relocation within the instruction.
source_offset: u4,
/// Length of the relocation.
source_length: u4,
/// Target of the relocation.
target: Mir.Inst.Index,
/// Offset from the target.
target_offset: i32,
};
const TableReloc = struct {
/// Offset of the relocation.
source_offset: u32,
/// Offset from the start of the table.
target_offset: i32,
};
const Loc = struct {
line: u32,
column: u32,
is_stmt: bool,
};
fn dbgAdvancePCAndLine(emit: *Emit, loc: Loc) Error!void {
const delta_line = @as(i33, loc.line) - @as(i33, emit.prev_di_loc.line);
const delta_pc: usize = emit.w.end - emit.prev_di_pc;
log.debug(" (advance pc={d} and line={d})", .{ delta_pc, delta_line });
switch (emit.debug_output) {
.dwarf => |dwarf| {
if (loc.is_stmt != emit.prev_di_loc.is_stmt) try dwarf.negateStmt();
if (loc.column != emit.prev_di_loc.column) try dwarf.setColumn(loc.column);
try dwarf.advancePCAndLine(delta_line, delta_pc);
emit.prev_di_loc = loc;
emit.prev_di_pc = emit.w.end;
},
.none => {},
}
}
const assert = std.debug.assert;
const bits = @import("bits.zig");
const codegen = @import("../../codegen.zig");
const Emit = @This();
const encoder = @import("encoder.zig");
const Instruction = encoder.Instruction;
const InternPool = @import("../../InternPool.zig");
const link = @import("../../link.zig");
const log = std.log.scoped(.emit);
const Lower = @import("Lower.zig");
const Mir = @import("Mir.zig");
const std = @import("std");
const Type = @import("../../Type.zig");
const Zcu = @import("../../Zcu.zig");