link/Coff: simplify file structure by collapsing all files into Coff.zig (#21761)

* coff: collapse Coff/lld.zig logic into Coff.zig

* coff: rename std.coff uses to coff_util

* coff: rename self to coff for *Coff references

* coff: collapse Coff/Atom.zig logic into Coff.zig

* coff: collapse Coff/Relocation.zig logic into Coff.zig

* coff: collapse Coff/ImportTable.zig logic into Coff.zig

* coff: remove unused Coff/Object.zig

* link/Coff: fix rebase gone wrong
This commit is contained in:
Jakub Konka 2024-10-24 15:50:02 +02:00 committed by GitHub
parent 9ffee5abed
commit 56996a2809
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 1636 additions and 1708 deletions

View File

@ -592,11 +592,6 @@ set(ZIG_STAGE2_SOURCES
src/link.zig
src/link/C.zig
src/link/Coff.zig
src/link/Coff/Atom.zig
src/link/Coff/ImportTable.zig
src/link/Coff/Object.zig
src/link/Coff/Relocation.zig
src/link/Coff/lld.zig
src/link/Dwarf.zig
src/link/Elf.zig
src/link/Elf/Archive.zig

View File

@ -942,7 +942,7 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void {
.load_memory_import => coff_file.getGlobalByIndex(data.sym_index),
else => unreachable,
};
try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{
try coff_file.addRelocation(atom_index, .{
.target = target,
.offset = offset,
.addend = 0,
@ -959,7 +959,7 @@ fn mirLoadMemoryPie(emit: *Emit, inst: Mir.Inst.Index) !void {
else => unreachable,
},
});
try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{
try coff_file.addRelocation(atom_index, .{
.target = target,
.offset = offset + 4,
.addend = 0,

View File

@ -132,7 +132,7 @@ pub fn emitMir(emit: *Emit) Error!void {
coff_file.getGlobalByIndex(link.File.Coff.global_symbol_mask & sym_index)
else
link.File.Coff.SymbolWithLoc{ .sym_index = sym_index, .file = null };
try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{
try coff_file.addRelocation(atom_index, .{
.type = .direct,
.target = target,
.offset = end_offset - 4,
@ -230,7 +230,7 @@ pub fn emitMir(emit: *Emit) Error!void {
coff_file.getGlobalByIndex(link.File.Coff.global_symbol_mask & sym_index)
else
link.File.Coff.SymbolWithLoc{ .sym_index = sym_index, .file = null };
try link.File.Coff.Atom.addRelocation(coff_file, atom_index, .{
try coff_file.addRelocation(atom_index, .{
.type = switch (lowered_relocs[0].target) {
.linker_got => .got,
.linker_direct => .direct,

File diff suppressed because it is too large Load Diff

View File

@ -1,128 +0,0 @@
const Atom = @This();
const std = @import("std");
const coff = std.coff;
const log = std.log.scoped(.link);
const Coff = @import("../Coff.zig");
const Relocation = @import("Relocation.zig");
const SymbolWithLoc = Coff.SymbolWithLoc;
/// Each decl always gets a local symbol with the fully qualified name.
/// The vaddr and size are found here directly.
/// The file offset is found by computing the vaddr offset from the section vaddr
/// the symbol references, and adding that to the file offset of the section.
/// If this field is 0, it means the codegen size = 0 and there is no symbol or
/// offset table entry.
sym_index: u32,
/// null means symbol defined by Zig source.
file: ?u32,
/// Size of the atom
size: u32,
/// Points to the previous and next neighbors, based on the `text_offset`.
/// This can be used to find, for example, the capacity of this `Atom`.
prev_index: ?Index,
next_index: ?Index,
pub const Index = u32;
pub fn getSymbolIndex(self: Atom) ?u32 {
if (self.sym_index == 0) return null;
return self.sym_index;
}
/// Returns symbol referencing this atom.
pub fn getSymbol(self: Atom, coff_file: *const Coff) *const coff.Symbol {
const sym_index = self.getSymbolIndex().?;
return coff_file.getSymbol(.{
.sym_index = sym_index,
.file = self.file,
});
}
/// Returns pointer-to-symbol referencing this atom.
pub fn getSymbolPtr(self: Atom, coff_file: *Coff) *coff.Symbol {
const sym_index = self.getSymbolIndex().?;
return coff_file.getSymbolPtr(.{
.sym_index = sym_index,
.file = self.file,
});
}
pub fn getSymbolWithLoc(self: Atom) SymbolWithLoc {
const sym_index = self.getSymbolIndex().?;
return .{ .sym_index = sym_index, .file = self.file };
}
/// Returns the name of this atom.
pub fn getName(self: Atom, coff_file: *const Coff) []const u8 {
const sym_index = self.getSymbolIndex().?;
return coff_file.getSymbolName(.{
.sym_index = sym_index,
.file = self.file,
});
}
/// Returns how much room there is to grow in virtual address space.
pub fn capacity(self: Atom, coff_file: *const Coff) u32 {
const self_sym = self.getSymbol(coff_file);
if (self.next_index) |next_index| {
const next = coff_file.getAtom(next_index);
const next_sym = next.getSymbol(coff_file);
return next_sym.value - self_sym.value;
} else {
// We are the last atom.
// The capacity is limited only by virtual address space.
return std.math.maxInt(u32) - self_sym.value;
}
}
pub fn freeListEligible(self: Atom, coff_file: *const Coff) bool {
// No need to keep a free list node for the last atom.
const next_index = self.next_index orelse return false;
const next = coff_file.getAtom(next_index);
const self_sym = self.getSymbol(coff_file);
const next_sym = next.getSymbol(coff_file);
const cap = next_sym.value - self_sym.value;
const ideal_cap = Coff.padToIdeal(self.size);
if (cap <= ideal_cap) return false;
const surplus = cap - ideal_cap;
return surplus >= Coff.min_text_capacity;
}
pub fn addRelocation(coff_file: *Coff, atom_index: Index, reloc: Relocation) !void {
const comp = coff_file.base.comp;
const gpa = comp.gpa;
log.debug(" (adding reloc of type {s} to target %{d})", .{ @tagName(reloc.type), reloc.target.sym_index });
const gop = try coff_file.relocs.getOrPut(gpa, atom_index);
if (!gop.found_existing) {
gop.value_ptr.* = .{};
}
try gop.value_ptr.append(gpa, reloc);
}
pub fn addBaseRelocation(coff_file: *Coff, atom_index: Index, offset: u32) !void {
const comp = coff_file.base.comp;
const gpa = comp.gpa;
log.debug(" (adding base relocation at offset 0x{x} in %{d})", .{
offset,
coff_file.getAtom(atom_index).getSymbolIndex().?,
});
const gop = try coff_file.base_relocs.getOrPut(gpa, atom_index);
if (!gop.found_existing) {
gop.value_ptr.* = .{};
}
try gop.value_ptr.append(gpa, offset);
}
pub fn freeRelocations(coff_file: *Coff, atom_index: Index) void {
const comp = coff_file.base.comp;
const gpa = comp.gpa;
var removed_relocs = coff_file.relocs.fetchOrderedRemove(atom_index);
if (removed_relocs) |*relocs| relocs.value.deinit(gpa);
var removed_base_relocs = coff_file.base_relocs.fetchOrderedRemove(atom_index);
if (removed_base_relocs) |*base_relocs| base_relocs.value.deinit(gpa);
}

View File

@ -1,133 +0,0 @@
//! Represents an import table in the .idata section where each contained pointer
//! is to a symbol from the same DLL.
//!
//! The layout of .idata section is as follows:
//!
//! --- ADDR1 : IAT (all import tables concatenated together)
//! ptr
//! ptr
//! 0 sentinel
//! ptr
//! 0 sentinel
//! --- ADDR2: headers
//! ImportDirectoryEntry header
//! ImportDirectoryEntry header
//! sentinel
//! --- ADDR2: lookup tables
//! Lookup table
//! 0 sentinel
//! Lookup table
//! 0 sentinel
//! --- ADDR3: name hint tables
//! hint-symname
//! hint-symname
//! --- ADDR4: DLL names
//! DLL#1 name
//! DLL#2 name
//! --- END
entries: std.ArrayListUnmanaged(SymbolWithLoc) = .empty,
free_list: std.ArrayListUnmanaged(u32) = .empty,
lookup: std.AutoHashMapUnmanaged(SymbolWithLoc, u32) = .empty,
pub fn deinit(itab: *ImportTable, allocator: Allocator) void {
itab.entries.deinit(allocator);
itab.free_list.deinit(allocator);
itab.lookup.deinit(allocator);
}
/// Size of the import table does not include the sentinel.
pub fn size(itab: ImportTable) u32 {
return @as(u32, @intCast(itab.entries.items.len)) * @sizeOf(u64);
}
pub fn addImport(itab: *ImportTable, allocator: Allocator, target: SymbolWithLoc) !ImportIndex {
try itab.entries.ensureUnusedCapacity(allocator, 1);
const index: u32 = blk: {
if (itab.free_list.popOrNull()) |index| {
log.debug(" (reusing import entry index {d})", .{index});
break :blk index;
} else {
log.debug(" (allocating import entry at index {d})", .{itab.entries.items.len});
const index = @as(u32, @intCast(itab.entries.items.len));
_ = itab.entries.addOneAssumeCapacity();
break :blk index;
}
};
itab.entries.items[index] = target;
try itab.lookup.putNoClobber(allocator, target, index);
return index;
}
const Context = struct {
coff_file: *const Coff,
/// Index of this ImportTable in a global list of all tables.
/// This is required in order to calculate the base vaddr of this ImportTable.
index: usize,
/// Offset into the string interning table of the DLL this ImportTable corresponds to.
name_off: u32,
};
fn getBaseAddress(ctx: Context) u32 {
const header = ctx.coff_file.sections.items(.header)[ctx.coff_file.idata_section_index.?];
var addr = header.virtual_address;
for (ctx.coff_file.import_tables.values(), 0..) |other_itab, i| {
if (ctx.index == i) break;
addr += @as(u32, @intCast(other_itab.entries.items.len * @sizeOf(u64))) + 8;
}
return addr;
}
pub fn getImportAddress(itab: *const ImportTable, target: SymbolWithLoc, ctx: Context) ?u32 {
const index = itab.lookup.get(target) orelse return null;
const base_vaddr = getBaseAddress(ctx);
return base_vaddr + index * @sizeOf(u64);
}
const FormatContext = struct {
itab: ImportTable,
ctx: Context,
};
fn fmt(
fmt_ctx: FormatContext,
comptime unused_format_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) @TypeOf(writer).Error!void {
_ = options;
comptime assert(unused_format_string.len == 0);
const lib_name = fmt_ctx.ctx.coff_file.temp_strtab.getAssumeExists(fmt_ctx.ctx.name_off);
const base_vaddr = getBaseAddress(fmt_ctx.ctx);
try writer.print("IAT({s}.dll) @{x}:", .{ lib_name, base_vaddr });
for (fmt_ctx.itab.entries.items, 0..) |entry, i| {
try writer.print("\n {d}@{?x} => {s}", .{
i,
fmt_ctx.itab.getImportAddress(entry, fmt_ctx.ctx),
fmt_ctx.ctx.coff_file.getSymbolName(entry),
});
}
}
fn format(itab: ImportTable, comptime unused_format_string: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
_ = itab;
_ = unused_format_string;
_ = options;
_ = writer;
@compileError("do not format ImportTable directly; use itab.fmtDebug()");
}
pub fn fmtDebug(itab: ImportTable, ctx: Context) std.fmt.Formatter(fmt) {
return .{ .data = .{ .itab = itab, .ctx = ctx } };
}
pub const ImportIndex = u32;
const ImportTable = @This();
const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.link);
const Allocator = std.mem.Allocator;
const Coff = @import("../Coff.zig");
const SymbolWithLoc = Coff.SymbolWithLoc;

View File

@ -1,12 +0,0 @@
const Object = @This();
const std = @import("std");
const mem = std.mem;
const Allocator = mem.Allocator;
name: []const u8,
pub fn deinit(self: *Object, gpa: Allocator) void {
gpa.free(self.name);
}

View File

@ -1,233 +0,0 @@
const Relocation = @This();
const std = @import("std");
const assert = std.debug.assert;
const log = std.log.scoped(.link);
const math = std.math;
const mem = std.mem;
const meta = std.meta;
const aarch64 = @import("../../arch/aarch64/bits.zig");
const Atom = @import("Atom.zig");
const Coff = @import("../Coff.zig");
const SymbolWithLoc = Coff.SymbolWithLoc;
type: enum {
// x86, x86_64
/// RIP-relative displacement to a GOT pointer
got,
/// RIP-relative displacement to an import pointer
import,
// aarch64
/// PC-relative distance to target page in GOT section
got_page,
/// Offset to a GOT pointer relative to the start of a page in GOT section
got_pageoff,
/// PC-relative distance to target page in a section (e.g., .rdata)
page,
/// Offset to a pointer relative to the start of a page in a section (e.g., .rdata)
pageoff,
/// PC-relative distance to target page in a import section
import_page,
/// Offset to a pointer relative to the start of a page in an import section (e.g., .rdata)
import_pageoff,
// common
/// Absolute pointer value
direct,
},
target: SymbolWithLoc,
offset: u32,
addend: u32,
pcrel: bool,
length: u2,
dirty: bool = true,
/// Returns true if and only if the reloc can be resolved.
pub fn isResolvable(self: Relocation, coff_file: *Coff) bool {
_ = self.getTargetAddress(coff_file) orelse return false;
return true;
}
pub fn isGotIndirection(self: Relocation) bool {
return switch (self.type) {
.got, .got_page, .got_pageoff => true,
else => false,
};
}
/// Returns address of the target if any.
pub fn getTargetAddress(self: Relocation, coff_file: *const Coff) ?u32 {
switch (self.type) {
.got, .got_page, .got_pageoff => {
const got_index = coff_file.got_table.lookup.get(self.target) orelse return null;
const header = coff_file.sections.items(.header)[coff_file.got_section_index.?];
return header.virtual_address + got_index * coff_file.ptr_width.size();
},
.import, .import_page, .import_pageoff => {
const sym = coff_file.getSymbol(self.target);
const index = coff_file.import_tables.getIndex(sym.value) orelse return null;
const itab = coff_file.import_tables.values()[index];
return itab.getImportAddress(self.target, .{
.coff_file = coff_file,
.index = index,
.name_off = sym.value,
});
},
else => {
const target_atom_index = coff_file.getAtomIndexForSymbol(self.target) orelse return null;
const target_atom = coff_file.getAtom(target_atom_index);
return target_atom.getSymbol(coff_file).value;
},
}
}
pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, image_base: u64, coff_file: *Coff) void {
const atom = coff_file.getAtom(atom_index);
const source_sym = atom.getSymbol(coff_file);
const source_vaddr = source_sym.value + self.offset;
const target_vaddr = self.getTargetAddress(coff_file).?; // Oops, you didn't check if the relocation can be resolved with isResolvable().
const target_vaddr_with_addend = target_vaddr + self.addend;
log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) ", .{
source_vaddr,
target_vaddr_with_addend,
coff_file.getSymbolName(self.target),
@tagName(self.type),
});
const ctx: Context = .{
.source_vaddr = source_vaddr,
.target_vaddr = target_vaddr_with_addend,
.image_base = image_base,
.code = code,
.ptr_width = coff_file.ptr_width,
};
const target = coff_file.base.comp.root_mod.resolved_target.result;
switch (target.cpu.arch) {
.aarch64 => self.resolveAarch64(ctx),
.x86, .x86_64 => self.resolveX86(ctx),
else => unreachable, // unhandled target architecture
}
}
const Context = struct {
source_vaddr: u32,
target_vaddr: u32,
image_base: u64,
code: []u8,
ptr_width: Coff.PtrWidth,
};
fn resolveAarch64(self: Relocation, ctx: Context) void {
var buffer = ctx.code[self.offset..];
switch (self.type) {
.got_page, .import_page, .page => {
const source_page = @as(i32, @intCast(ctx.source_vaddr >> 12));
const target_page = @as(i32, @intCast(ctx.target_vaddr >> 12));
const pages = @as(u21, @bitCast(@as(i21, @intCast(target_page - source_page))));
var inst = aarch64.Instruction{
.pc_relative_address = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.pc_relative_address,
), buffer[0..4]),
};
inst.pc_relative_address.immhi = @as(u19, @truncate(pages >> 2));
inst.pc_relative_address.immlo = @as(u2, @truncate(pages));
mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
},
.got_pageoff, .import_pageoff, .pageoff => {
assert(!self.pcrel);
const narrowed = @as(u12, @truncate(@as(u64, @intCast(ctx.target_vaddr))));
if (isArithmeticOp(buffer[0..4])) {
var inst = aarch64.Instruction{
.add_subtract_immediate = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.add_subtract_immediate,
), buffer[0..4]),
};
inst.add_subtract_immediate.imm12 = narrowed;
mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
} else {
var inst = aarch64.Instruction{
.load_store_register = mem.bytesToValue(meta.TagPayload(
aarch64.Instruction,
aarch64.Instruction.load_store_register,
), buffer[0..4]),
};
const offset: u12 = blk: {
if (inst.load_store_register.size == 0) {
if (inst.load_store_register.v == 1) {
// 128-bit SIMD is scaled by 16.
break :blk @divExact(narrowed, 16);
}
// Otherwise, 8-bit SIMD or ldrb.
break :blk narrowed;
} else {
const denom: u4 = math.powi(u4, 2, inst.load_store_register.size) catch unreachable;
break :blk @divExact(narrowed, denom);
}
};
inst.load_store_register.offset = offset;
mem.writeInt(u32, buffer[0..4], inst.toU32(), .little);
}
},
.direct => {
assert(!self.pcrel);
switch (self.length) {
2 => mem.writeInt(
u32,
buffer[0..4],
@as(u32, @truncate(ctx.target_vaddr + ctx.image_base)),
.little,
),
3 => mem.writeInt(u64, buffer[0..8], ctx.target_vaddr + ctx.image_base, .little),
else => unreachable,
}
},
.got => unreachable,
.import => unreachable,
}
}
fn resolveX86(self: Relocation, ctx: Context) void {
var buffer = ctx.code[self.offset..];
switch (self.type) {
.got_page => unreachable,
.got_pageoff => unreachable,
.page => unreachable,
.pageoff => unreachable,
.import_page => unreachable,
.import_pageoff => unreachable,
.got, .import => {
assert(self.pcrel);
const disp = @as(i32, @intCast(ctx.target_vaddr)) - @as(i32, @intCast(ctx.source_vaddr)) - 4;
mem.writeInt(i32, buffer[0..4], disp, .little);
},
.direct => {
if (self.pcrel) {
const disp = @as(i32, @intCast(ctx.target_vaddr)) - @as(i32, @intCast(ctx.source_vaddr)) - 4;
mem.writeInt(i32, buffer[0..4], disp, .little);
} else switch (ctx.ptr_width) {
.p32 => mem.writeInt(u32, buffer[0..4], @as(u32, @intCast(ctx.target_vaddr + ctx.image_base)), .little),
.p64 => switch (self.length) {
2 => mem.writeInt(u32, buffer[0..4], @as(u32, @truncate(ctx.target_vaddr + ctx.image_base)), .little),
3 => mem.writeInt(u64, buffer[0..8], ctx.target_vaddr + ctx.image_base, .little),
else => unreachable,
},
}
},
}
}
inline fn isArithmeticOp(inst: *const [4]u8) bool {
const group_decode = @as(u5, @truncate(inst[3]));
return ((group_decode >> 2) == 4);
}

View File

@ -1,548 +0,0 @@
const std = @import("std");
const build_options = @import("build_options");
const allocPrint = std.fmt.allocPrint;
const assert = std.debug.assert;
const dev = @import("../../dev.zig");
const fs = std.fs;
const log = std.log.scoped(.link);
const mem = std.mem;
const Cache = std.Build.Cache;
const Path = std.Build.Cache.Path;
const Directory = std.Build.Cache.Directory;
const mingw = @import("../../mingw.zig");
const link = @import("../../link.zig");
const trace = @import("../../tracy.zig").trace;
const Allocator = mem.Allocator;
const Coff = @import("../Coff.zig");
const Compilation = @import("../../Compilation.zig");
const Zcu = @import("../../Zcu.zig");
pub fn linkWithLLD(self: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void {
dev.check(.lld_linker);
const tracy = trace(@src());
defer tracy.end();
const comp = self.base.comp;
const gpa = comp.gpa;
const directory = self.base.emit.root_dir; // Just an alias to make it shorter to type.
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
// If there is no Zig code to compile, then we should skip flushing the output file because it
// will not be part of the linker line anyway.
const module_obj_path: ?[]const u8 = if (comp.zcu != null) blk: {
try self.flushModule(arena, tid, prog_node);
if (fs.path.dirname(full_out_path)) |dirname| {
break :blk try fs.path.join(arena, &.{ dirname, self.base.zcu_object_sub_path.? });
} else {
break :blk self.base.zcu_object_sub_path.?;
}
} else null;
const sub_prog_node = prog_node.start("LLD Link", 0);
defer sub_prog_node.end();
const is_lib = comp.config.output_mode == .Lib;
const is_dyn_lib = comp.config.link_mode == .dynamic and is_lib;
const is_exe_or_dyn_lib = is_dyn_lib or comp.config.output_mode == .Exe;
const link_in_crt = comp.config.link_libc and is_exe_or_dyn_lib;
const target = comp.root_mod.resolved_target.result;
const optimize_mode = comp.root_mod.optimize_mode;
const entry_name: ?[]const u8 = switch (self.entry) {
// This logic isn't quite right for disabled or enabled. No point in fixing it
// when the goal is to eliminate dependency on LLD anyway.
// https://github.com/ziglang/zig/issues/17751
.disabled, .default, .enabled => null,
.named => |name| name,
};
// See link/Elf.zig for comments on how this mechanism works.
const id_symlink_basename = "lld.id";
var man: Cache.Manifest = undefined;
defer if (!self.base.disable_lld_caching) man.deinit();
var digest: [Cache.hex_digest_len]u8 = undefined;
if (!self.base.disable_lld_caching) {
man = comp.cache_parent.obtain();
self.base.releaseLock();
comptime assert(Compilation.link_hash_implementation_version == 14);
try link.hashInputs(&man, comp.link_inputs);
for (comp.c_object_table.keys()) |key| {
_ = try man.addFilePath(key.status.success.object_path, null);
}
for (comp.win32_resource_table.keys()) |key| {
_ = try man.addFile(key.status.success.res_path, null);
}
try man.addOptionalFile(module_obj_path);
man.hash.addOptionalBytes(entry_name);
man.hash.add(self.base.stack_size);
man.hash.add(self.image_base);
{
// TODO remove this, libraries must instead be resolved by the frontend.
for (self.lib_directories) |lib_directory| man.hash.addOptionalBytes(lib_directory.path);
}
man.hash.add(comp.skip_linker_dependencies);
if (comp.config.link_libc) {
man.hash.add(comp.libc_installation != null);
if (comp.libc_installation) |libc_installation| {
man.hash.addBytes(libc_installation.crt_dir.?);
if (target.abi == .msvc or target.abi == .itanium) {
man.hash.addBytes(libc_installation.msvc_lib_dir.?);
man.hash.addBytes(libc_installation.kernel32_lib_dir.?);
}
}
}
man.hash.addListOfBytes(comp.windows_libs.keys());
man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
man.hash.addOptional(self.subsystem);
man.hash.add(comp.config.is_test);
man.hash.add(self.tsaware);
man.hash.add(self.nxcompat);
man.hash.add(self.dynamicbase);
man.hash.add(self.base.allow_shlib_undefined);
// strip does not need to go into the linker hash because it is part of the hash namespace
man.hash.add(self.major_subsystem_version);
man.hash.add(self.minor_subsystem_version);
man.hash.add(self.repro);
man.hash.addOptional(comp.version);
try man.addOptionalFile(self.module_definition_file);
// We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
_ = try man.hit();
digest = man.final();
var prev_digest_buf: [digest.len]u8 = undefined;
const prev_digest: []u8 = Cache.readSmallFile(
directory.handle,
id_symlink_basename,
&prev_digest_buf,
) catch |err| blk: {
log.debug("COFF LLD new_digest={s} error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) });
// Handle this as a cache miss.
break :blk prev_digest_buf[0..0];
};
if (mem.eql(u8, prev_digest, &digest)) {
log.debug("COFF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
// Hot diggity dog! The output binary is already there.
self.base.lock = man.toOwnedLock();
return;
}
log.debug("COFF LLD prev_digest={s} new_digest={s}", .{ std.fmt.fmtSliceHexLower(prev_digest), std.fmt.fmtSliceHexLower(&digest) });
// We are about to change the output file to be different, so we invalidate the build hash now.
directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
error.FileNotFound => {},
else => |e| return e,
};
}
if (comp.config.output_mode == .Obj) {
// LLD's COFF driver does not support the equivalent of `-r` so we do a simple file copy
// here. TODO: think carefully about how we can avoid this redundant operation when doing
// build-obj. See also the corresponding TODO in linkAsArchive.
const the_object_path = blk: {
if (link.firstObjectInput(comp.link_inputs)) |obj| break :blk obj.path;
if (comp.c_object_table.count() != 0)
break :blk comp.c_object_table.keys()[0].status.success.object_path;
if (module_obj_path) |p|
break :blk Path.initCwd(p);
// TODO I think this is unreachable. Audit this situation when solving the above TODO
// regarding eliding redundant object -> object transformations.
return error.NoObjectsToLink;
};
try std.fs.Dir.copyFile(
the_object_path.root_dir.handle,
the_object_path.sub_path,
directory.handle,
self.base.emit.sub_path,
.{},
);
} else {
// Create an LLD command line and invoke it.
var argv = std.ArrayList([]const u8).init(gpa);
defer argv.deinit();
// We will invoke ourselves as a child process to gain access to LLD.
// This is necessary because LLD does not behave properly as a library -
// it calls exit() and does not reset all global data between invocations.
const linker_command = "lld-link";
try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command });
try argv.append("-ERRORLIMIT:0");
try argv.append("-NOLOGO");
if (comp.config.debug_format != .strip) {
try argv.append("-DEBUG");
const out_ext = std.fs.path.extension(full_out_path);
const out_pdb = self.pdb_out_path orelse try allocPrint(arena, "{s}.pdb", .{
full_out_path[0 .. full_out_path.len - out_ext.len],
});
const out_pdb_basename = std.fs.path.basename(out_pdb);
try argv.append(try allocPrint(arena, "-PDB:{s}", .{out_pdb}));
try argv.append(try allocPrint(arena, "-PDBALTPATH:{s}", .{out_pdb_basename}));
}
if (comp.version) |version| {
try argv.append(try allocPrint(arena, "-VERSION:{}.{}", .{ version.major, version.minor }));
}
if (comp.config.lto) {
switch (optimize_mode) {
.Debug => {},
.ReleaseSmall => try argv.append("-OPT:lldlto=2"),
.ReleaseFast, .ReleaseSafe => try argv.append("-OPT:lldlto=3"),
}
}
if (comp.config.output_mode == .Exe) {
try argv.append(try allocPrint(arena, "-STACK:{d}", .{self.base.stack_size}));
}
try argv.append(try std.fmt.allocPrint(arena, "-BASE:{d}", .{self.image_base}));
if (target.cpu.arch == .x86) {
try argv.append("-MACHINE:X86");
} else if (target.cpu.arch == .x86_64) {
try argv.append("-MACHINE:X64");
} else if (target.cpu.arch.isARM()) {
if (target.ptrBitWidth() == 32) {
try argv.append("-MACHINE:ARM");
} else {
try argv.append("-MACHINE:ARM64");
}
}
for (comp.force_undefined_symbols.keys()) |symbol| {
try argv.append(try allocPrint(arena, "-INCLUDE:{s}", .{symbol}));
}
if (is_dyn_lib) {
try argv.append("-DLL");
}
if (entry_name) |name| {
try argv.append(try allocPrint(arena, "-ENTRY:{s}", .{name}));
}
if (self.repro) {
try argv.append("-BREPRO");
}
if (self.tsaware) {
try argv.append("-tsaware");
}
if (self.nxcompat) {
try argv.append("-nxcompat");
}
if (!self.dynamicbase) {
try argv.append("-dynamicbase:NO");
}
if (self.base.allow_shlib_undefined) {
try argv.append("-FORCE:UNRESOLVED");
}
try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path}));
if (comp.implib_emit) |emit| {
const implib_out_path = try emit.root_dir.join(arena, &[_][]const u8{emit.sub_path});
try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path}));
}
if (comp.config.link_libc) {
if (comp.libc_installation) |libc_installation| {
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.crt_dir.?}));
if (target.abi == .msvc or target.abi == .itanium) {
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.msvc_lib_dir.?}));
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{libc_installation.kernel32_lib_dir.?}));
}
}
}
for (self.lib_directories) |lib_directory| {
try argv.append(try allocPrint(arena, "-LIBPATH:{s}", .{lib_directory.path orelse "."}));
}
try argv.ensureUnusedCapacity(comp.link_inputs.len);
for (comp.link_inputs) |link_input| switch (link_input) {
.dso_exact => unreachable, // not applicable to PE/COFF
inline .dso, .res => |x| {
argv.appendAssumeCapacity(try x.path.toString(arena));
},
.object, .archive => |obj| {
if (obj.must_link) {
argv.appendAssumeCapacity(try allocPrint(arena, "-WHOLEARCHIVE:{}", .{@as(Path, obj.path)}));
} else {
argv.appendAssumeCapacity(try obj.path.toString(arena));
}
},
};
for (comp.c_object_table.keys()) |key| {
try argv.append(try key.status.success.object_path.toString(arena));
}
for (comp.win32_resource_table.keys()) |key| {
try argv.append(key.status.success.res_path);
}
if (module_obj_path) |p| {
try argv.append(p);
}
if (self.module_definition_file) |def| {
try argv.append(try allocPrint(arena, "-DEF:{s}", .{def}));
}
const resolved_subsystem: ?std.Target.SubSystem = blk: {
if (self.subsystem) |explicit| break :blk explicit;
switch (target.os.tag) {
.windows => {
if (comp.zcu) |module| {
if (module.stage1_flags.have_dllmain_crt_startup or is_dyn_lib)
break :blk null;
if (module.stage1_flags.have_c_main or comp.config.is_test or
module.stage1_flags.have_winmain_crt_startup or
module.stage1_flags.have_wwinmain_crt_startup)
{
break :blk .Console;
}
if (module.stage1_flags.have_winmain or module.stage1_flags.have_wwinmain)
break :blk .Windows;
}
},
.uefi => break :blk .EfiApplication,
else => {},
}
break :blk null;
};
const Mode = enum { uefi, win32 };
const mode: Mode = mode: {
if (resolved_subsystem) |subsystem| {
const subsystem_suffix = try allocPrint(arena, ",{d}.{d}", .{
self.major_subsystem_version, self.minor_subsystem_version,
});
switch (subsystem) {
.Console => {
try argv.append(try allocPrint(arena, "-SUBSYSTEM:console{s}", .{
subsystem_suffix,
}));
break :mode .win32;
},
.EfiApplication => {
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_application{s}", .{
subsystem_suffix,
}));
break :mode .uefi;
},
.EfiBootServiceDriver => {
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_boot_service_driver{s}", .{
subsystem_suffix,
}));
break :mode .uefi;
},
.EfiRom => {
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_rom{s}", .{
subsystem_suffix,
}));
break :mode .uefi;
},
.EfiRuntimeDriver => {
try argv.append(try allocPrint(arena, "-SUBSYSTEM:efi_runtime_driver{s}", .{
subsystem_suffix,
}));
break :mode .uefi;
},
.Native => {
try argv.append(try allocPrint(arena, "-SUBSYSTEM:native{s}", .{
subsystem_suffix,
}));
break :mode .win32;
},
.Posix => {
try argv.append(try allocPrint(arena, "-SUBSYSTEM:posix{s}", .{
subsystem_suffix,
}));
break :mode .win32;
},
.Windows => {
try argv.append(try allocPrint(arena, "-SUBSYSTEM:windows{s}", .{
subsystem_suffix,
}));
break :mode .win32;
},
}
} else if (target.os.tag == .uefi) {
break :mode .uefi;
} else {
break :mode .win32;
}
};
switch (mode) {
.uefi => try argv.appendSlice(&[_][]const u8{
"-BASE:0",
"-ENTRY:EfiMain",
"-OPT:REF",
"-SAFESEH:NO",
"-MERGE:.rdata=.data",
"-NODEFAULTLIB",
"-SECTION:.xdata,D",
}),
.win32 => {
if (link_in_crt) {
if (target.abi.isGnu()) {
try argv.append("-lldmingw");
if (target.cpu.arch == .x86) {
try argv.append("-ALTERNATENAME:__image_base__=___ImageBase");
} else {
try argv.append("-ALTERNATENAME:__image_base__=__ImageBase");
}
if (is_dyn_lib) {
try argv.append(try comp.crtFileAsString(arena, "dllcrt2.obj"));
if (target.cpu.arch == .x86) {
try argv.append("-ALTERNATENAME:__DllMainCRTStartup@12=_DllMainCRTStartup@12");
} else {
try argv.append("-ALTERNATENAME:_DllMainCRTStartup=DllMainCRTStartup");
}
} else {
try argv.append(try comp.crtFileAsString(arena, "crt2.obj"));
}
try argv.append(try comp.crtFileAsString(arena, "mingw32.lib"));
} else {
const lib_str = switch (comp.config.link_mode) {
.dynamic => "",
.static => "lib",
};
const d_str = switch (optimize_mode) {
.Debug => "d",
else => "",
};
switch (comp.config.link_mode) {
.static => try argv.append(try allocPrint(arena, "libcmt{s}.lib", .{d_str})),
.dynamic => try argv.append(try allocPrint(arena, "msvcrt{s}.lib", .{d_str})),
}
try argv.append(try allocPrint(arena, "{s}vcruntime{s}.lib", .{ lib_str, d_str }));
try argv.append(try allocPrint(arena, "{s}ucrt{s}.lib", .{ lib_str, d_str }));
//Visual C++ 2015 Conformance Changes
//https://msdn.microsoft.com/en-us/library/bb531344.aspx
try argv.append("legacy_stdio_definitions.lib");
// msvcrt depends on kernel32 and ntdll
try argv.append("kernel32.lib");
try argv.append("ntdll.lib");
}
} else {
try argv.append("-NODEFAULTLIB");
if (!is_lib and entry_name == null) {
if (comp.zcu) |module| {
if (module.stage1_flags.have_winmain_crt_startup) {
try argv.append("-ENTRY:WinMainCRTStartup");
} else {
try argv.append("-ENTRY:wWinMainCRTStartup");
}
} else {
try argv.append("-ENTRY:wWinMainCRTStartup");
}
}
}
},
}
// libc++ dep
if (comp.config.link_libcpp) {
try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena));
try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena));
}
// libunwind dep
if (comp.config.link_libunwind) {
try argv.append(try comp.libunwind_static_lib.?.full_object_path.toString(arena));
}
if (comp.config.any_fuzz) {
try argv.append(try comp.fuzzer_lib.?.full_object_path.toString(arena));
}
if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) {
if (!comp.config.link_libc) {
if (comp.libc_static_lib) |lib| {
try argv.append(try lib.full_object_path.toString(arena));
}
}
// MSVC compiler_rt is missing some stuff, so we build it unconditionally but
// and rely on weak linkage to allow MSVC compiler_rt functions to override ours.
if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena));
if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena));
}
try argv.ensureUnusedCapacity(comp.windows_libs.count());
for (comp.windows_libs.keys()) |key| {
const lib_basename = try allocPrint(arena, "{s}.lib", .{key});
if (comp.crt_files.get(lib_basename)) |crt_file| {
argv.appendAssumeCapacity(try crt_file.full_object_path.toString(arena));
continue;
}
if (try findLib(arena, lib_basename, self.lib_directories)) |full_path| {
argv.appendAssumeCapacity(full_path);
continue;
}
if (target.abi.isGnu()) {
const fallback_name = try allocPrint(arena, "lib{s}.dll.a", .{key});
if (try findLib(arena, fallback_name, self.lib_directories)) |full_path| {
argv.appendAssumeCapacity(full_path);
continue;
}
}
if (target.abi == .msvc or target.abi == .itanium) {
argv.appendAssumeCapacity(lib_basename);
continue;
}
log.err("DLL import library for -l{s} not found", .{key});
return error.DllImportLibraryNotFound;
}
try link.spawnLld(comp, arena, argv.items);
}
if (!self.base.disable_lld_caching) {
// Update the file with the digest. If it fails we can continue; it only
// means that the next invocation will have an unnecessary cache miss.
Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
log.warn("failed to save linking hash digest file: {s}", .{@errorName(err)});
};
// Again failure here only means an unnecessary cache miss.
man.writeManifest() catch |err| {
log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)});
};
// We hang on to this lock so that the output file path can be used without
// other processes clobbering it.
self.base.lock = man.toOwnedLock();
}
}
fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Directory) !?[]const u8 {
for (lib_directories) |lib_directory| {
lib_directory.handle.access(name, .{}) catch |err| switch (err) {
error.FileNotFound => continue,
else => |e| return e,
};
return try lib_directory.join(arena, &.{name});
}
return null;
}