Commit 2fb6f5c1ad
Changed files (18)
src/codegen/llvm.zig
@@ -1587,12 +1587,15 @@ pub const Object = struct {
const comp = zcu.comp;
// If we're on COFF and linking with LLD, the linker cares about our exports to determine the subsystem in use.
- if (comp.bin_file != null and
- comp.bin_file.?.tag == .coff and
- zcu.comp.config.use_lld and
- ip.isFunctionType(ip.getNav(nav_index).typeOf(ip)))
- {
- const flags = &comp.bin_file.?.cast(.coff).?.lld_export_flags;
+ coff_export_flags: {
+ const lf = comp.bin_file orelse break :coff_export_flags;
+ const lld = lf.cast(.lld) orelse break :coff_export_flags;
+ const coff = switch (lld.ofmt) {
+ .elf, .wasm => break :coff_export_flags,
+ .coff => |*coff| coff,
+ };
+ if (!ip.isFunctionType(ip.getNav(nav_index).typeOf(ip))) break :coff_export_flags;
+ const flags = &coff.lld_export_flags;
for (export_indices) |export_index| {
const name = export_index.ptr(zcu).opts.name;
if (name.eqlSlice("main", ip)) flags.c_main = true;
src/link/Elf/ZigObject.zig
@@ -310,7 +310,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void {
if (self.dwarf) |*dwarf| {
const pt: Zcu.PerThread = .activate(elf_file.base.comp.zcu.?, tid);
defer pt.deactivate();
- try dwarf.flushZcu(pt);
+ try dwarf.flush(pt);
const gpa = elf_file.base.comp.gpa;
const cpu_arch = elf_file.getTarget().cpu.arch;
@@ -481,7 +481,7 @@ pub fn flush(self: *ZigObject, elf_file: *Elf, tid: Zcu.PerThread.Id) !void {
self.debug_str_section_dirty = false;
}
- // The point of flushZcu() is to commit changes, so in theory, nothing should
+ // The point of flush() is to commit changes, so in theory, nothing should
// be dirty after this. However, it is possible for some things to remain
// dirty because they fail to be written in the event of compile errors,
// such as debug_line_header_dirty and debug_info_header_dirty.
@@ -661,7 +661,7 @@ pub fn scanRelocs(self: *ZigObject, elf_file: *Elf, undefs: anytype) !void {
if (shdr.sh_type == elf.SHT_NOBITS) continue;
if (atom_ptr.scanRelocsRequiresCode(elf_file)) {
// TODO ideally we don't have to fetch the code here.
- // Perhaps it would make sense to save the code until flushZcu where we
+ // Perhaps it would make sense to save the code until flush where we
// would free all of generated code?
const code = try self.codeAlloc(elf_file, atom_index);
defer gpa.free(code);
@@ -1075,7 +1075,7 @@ pub fn getOrCreateMetadataForLazySymbol(
}
state_ptr.* = .pending_flush;
const symbol_index = symbol_index_ptr.*;
- // anyerror needs to be deferred until flushZcu
+ // anyerror needs to be deferred until flush
if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbol(elf_file, pt, lazy_sym, symbol_index);
return symbol_index;
}
src/link/MachO/DebugSymbols.zig
@@ -178,7 +178,7 @@ fn findFreeSpace(self: *DebugSymbols, object_size: u64, min_alignment: u64) !u64
return offset;
}
-pub fn flushZcu(self: *DebugSymbols, macho_file: *MachO) !void {
+pub fn flush(self: *DebugSymbols, macho_file: *MachO) !void {
const zo = macho_file.getZigObject().?;
for (self.relocs.items) |*reloc| {
const sym = zo.symbols.items[reloc.target];
src/link/MachO/ZigObject.zig
@@ -550,7 +550,7 @@ pub fn getInputSection(self: ZigObject, atom: Atom, macho_file: *MachO) macho.se
return sect;
}
-pub fn flushZcu(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) link.File.FlushError!void {
+pub fn flush(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) link.File.FlushError!void {
const diags = &macho_file.base.comp.link_diags;
// Handle any lazy symbols that were emitted by incremental compilation.
@@ -589,7 +589,7 @@ pub fn flushZcu(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) lin
if (self.dwarf) |*dwarf| {
const pt: Zcu.PerThread = .activate(macho_file.base.comp.zcu.?, tid);
defer pt.deactivate();
- dwarf.flushZcu(pt) catch |err| switch (err) {
+ dwarf.flush(pt) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => |e| return diags.fail("failed to flush dwarf module: {s}", .{@errorName(e)}),
};
@@ -599,7 +599,7 @@ pub fn flushZcu(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) lin
self.debug_strtab_dirty = false;
}
- // The point of flushZcu() is to commit changes, so in theory, nothing should
+ // The point of flush() is to commit changes, so in theory, nothing should
// be dirty after this. However, it is possible for some things to remain
// dirty because they fail to be written in the event of compile errors,
// such as debug_line_header_dirty and debug_info_header_dirty.
@@ -1537,7 +1537,7 @@ pub fn getOrCreateMetadataForLazySymbol(
}
state_ptr.* = .pending_flush;
const symbol_index = symbol_index_ptr.*;
- // anyerror needs to be deferred until flushZcu
+ // anyerror needs to be deferred until flush
if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbol(macho_file, pt, lazy_sym, symbol_index);
return symbol_index;
}
src/link/C.zig
@@ -145,7 +145,6 @@ pub fn createEmpty(
.stack_size = options.stack_size orelse 16777216,
.allow_shlib_undefined = options.allow_shlib_undefined orelse false,
.file = file,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
};
@@ -381,10 +380,6 @@ pub fn updateLineNumber(self: *C, pt: Zcu.PerThread, ti_id: InternPool.TrackedIn
_ = ti_id;
}
-pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
- return self.flushZcu(arena, tid, prog_node);
-}
-
fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) {
const gpa = self.base.comp.gpa;
var defines = std.ArrayList(u8).init(gpa);
@@ -400,7 +395,7 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) {
return defines;
}
-pub fn flushZcu(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
+pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
_ = arena; // Has the same lifetime as the call to Compilation.update.
const tracy = trace(@src());
src/link/Coff.zig
@@ -1,23 +1,14 @@
-//! The main driver of the COFF linker.
-//! Currently uses our own implementation for the incremental linker, and falls back to
-//! LLD for traditional linking (linking relocatable object files).
-//! LLD is also the default linker for LLVM.
+//! The main driver of the self-hosted COFF linker.
base: link.File,
image_base: u64,
-subsystem: ?std.Target.SubSystem,
-tsaware: bool,
-nxcompat: bool,
-dynamicbase: bool,
/// TODO this and minor_subsystem_version should be combined into one property and left as
/// default or populated together. They should not be separate fields.
major_subsystem_version: u16,
minor_subsystem_version: u16,
-lib_directories: []const Directory,
entry: link.File.OpenOptions.Entry,
entry_addr: ?u32,
module_definition_file: ?[]const u8,
-pdb_out_path: ?[]const u8,
repro: bool,
ptr_width: PtrWidth,
@@ -84,16 +75,6 @@ base_relocs: BaseRelocationTable = .{},
/// Hot-code swapping state.
hot_state: if (is_hot_update_compatible) HotUpdateState else struct {} = .{},
-/// When linking with LLD, these flags are used to determine the subsystem to pass on the LLD command line.
-lld_export_flags: struct {
- c_main: bool = false,
- winmain: bool = false,
- wwinmain: bool = false,
- winmain_crt_startup: bool = false,
- wwinmain_crt_startup: bool = false,
- dllmain_crt_startup: bool = false,
-} = .{},
-
const is_hot_update_compatible = switch (builtin.target.os.tag) {
.windows => true,
else => false,
@@ -233,7 +214,6 @@ pub fn createEmpty(
const output_mode = comp.config.output_mode;
const link_mode = comp.config.link_mode;
const use_llvm = comp.config.use_llvm;
- const use_lld = build_options.have_llvm and comp.config.use_lld;
const ptr_width: PtrWidth = switch (target.ptrBitWidth()) {
0...32 => .p32,
@@ -244,12 +224,10 @@ pub fn createEmpty(
else => 0x1000,
};
- // If using LLD to link, this code should produce an object file so that it
- // can be passed to LLD.
// If using LLVM to generate the object file for the zig compilation unit,
// we need a place to put the object file so that it can be subsequently
// handled.
- const zcu_object_sub_path = if (!use_lld and !use_llvm)
+ const zcu_object_sub_path = if (!use_llvm)
null
else
try allocPrint(arena, "{s}.obj", .{emit.sub_path});
@@ -266,7 +244,6 @@ pub fn createEmpty(
.print_gc_sections = options.print_gc_sections,
.allow_shlib_undefined = options.allow_shlib_undefined orelse false,
.file = null,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
.ptr_width = ptr_width,
@@ -291,39 +268,21 @@ pub fn createEmpty(
.Obj => 0,
},
- // Subsystem depends on the set of public symbol names from linked objects.
- // See LinkerDriver::inferSubsystem from the LLD project for the flow chart.
- .subsystem = options.subsystem,
-
.entry = options.entry,
- .tsaware = options.tsaware,
- .nxcompat = options.nxcompat,
- .dynamicbase = options.dynamicbase,
.major_subsystem_version = options.major_subsystem_version orelse 6,
.minor_subsystem_version = options.minor_subsystem_version orelse 0,
- .lib_directories = options.lib_directories,
.entry_addr = math.cast(u32, options.entry_addr orelse 0) orelse
return error.EntryAddressTooBig,
.module_definition_file = options.module_definition_file,
- .pdb_out_path = options.pdb_out_path,
.repro = options.repro,
};
errdefer coff.base.destroy();
- if (use_lld and (use_llvm or !comp.config.have_zcu)) {
- // LLVM emits the object file (if any); LLD links it into the final product.
- return coff;
- }
-
- // What path should this COFF linker code output to?
- // If using LLD to link, this code should produce an object file so that it
- // can be passed to LLD.
- const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path;
- coff.base.file = try emit.root_dir.handle.createFile(sub_path, .{
+ coff.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
.truncate = true,
.read = true,
- .mode = link.File.determineMode(use_lld, output_mode, link_mode),
+ .mode = link.File.determineMode(output_mode, link_mode),
});
const gpa = comp.gpa;
@@ -1327,7 +1286,7 @@ pub fn getOrCreateAtomForLazySymbol(
}
state_ptr.* = .pending_flush;
const atom = atom_ptr.*;
- // anyerror needs to be deferred until flushZcu
+ // anyerror needs to be deferred until flush
if (lazy_sym.ty != .anyerror_type) try coff.updateLazySymbolAtom(pt, lazy_sym, atom, switch (lazy_sym.kind) {
.code => coff.text_section_index.?,
.const_data => coff.rdata_section_index.?,
@@ -1631,575 +1590,7 @@ fn resolveGlobalSymbol(coff: *Coff, current: SymbolWithLoc) !void {
gop.value_ptr.* = current;
}
-pub fn flush(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
- const comp = coff.base.comp;
- const use_lld = build_options.have_llvm and comp.config.use_lld;
- const diags = &comp.link_diags;
- if (use_lld) {
- return coff.linkWithLLD(arena, tid, prog_node) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.LinkFailure => return error.LinkFailure,
- else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}),
- };
- }
- switch (comp.config.output_mode) {
- .Exe, .Obj => return coff.flushZcu(arena, tid, prog_node),
- .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}),
- }
-}
-
-fn linkWithLLD(coff: *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 = coff.base.comp;
- const gpa = comp.gpa;
-
- const directory = coff.base.emit.root_dir; // Just an alias to make it shorter to type.
- const full_out_path = try directory.join(arena, &[_][]const u8{coff.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) |zcu| blk: {
- if (zcu.llvm_object == null) {
- try coff.flushZcu(arena, tid, prog_node);
- } else {
- // `Compilation.flush` has already made LLVM emit this object file for us.
- }
-
- if (fs.path.dirname(full_out_path)) |dirname| {
- break :blk try fs.path.join(arena, &.{ dirname, coff.base.zcu_object_sub_path.? });
- } else {
- break :blk coff.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 (coff.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 (!coff.base.disable_lld_caching) man.deinit();
-
- var digest: [Cache.hex_digest_len]u8 = undefined;
-
- if (!coff.base.disable_lld_caching) {
- man = comp.cache_parent.obtain();
- coff.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(coff.base.stack_size);
- man.hash.add(coff.image_base);
- man.hash.add(coff.base.build_id);
- {
- // TODO remove this, libraries must instead be resolved by the frontend.
- for (coff.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(coff.subsystem);
- man.hash.add(comp.config.is_test);
- man.hash.add(coff.tsaware);
- man.hash.add(coff.nxcompat);
- man.hash.add(coff.dynamicbase);
- man.hash.add(coff.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(coff.major_subsystem_version);
- man.hash.add(coff.minor_subsystem_version);
- man.hash.add(coff.repro);
- man.hash.addOptional(comp.version);
- try man.addOptionalFile(coff.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.
- coff.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,
- coff.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 });
-
- if (target.isMinGW()) {
- try argv.append("-lldmingw");
- }
-
- 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 = coff.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 (target_util.llvmMachineAbi(target)) |mabi| {
- try argv.append(try allocPrint(arena, "-MLLVM:-target-abi={s}", .{mabi}));
- }
-
- try argv.append(try allocPrint(arena, "-MLLVM:-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"}));
-
- if (comp.config.lto != .none) {
- 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}", .{coff.base.stack_size}));
- }
- try argv.append(try allocPrint(arena, "-BASE:{d}", .{coff.image_base}));
-
- switch (coff.base.build_id) {
- .none => try argv.append("-BUILD-ID:NO"),
- .fast => try argv.append("-BUILD-ID"),
- .uuid, .sha1, .md5, .hexstring => {},
- }
-
- 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 == .thumb) {
- try argv.append("-MACHINE:ARM");
- } else if (target.cpu.arch == .aarch64) {
- 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 (coff.repro) {
- try argv.append("-BREPRO");
- }
-
- if (coff.tsaware) {
- try argv.append("-tsaware");
- }
- if (coff.nxcompat) {
- try argv.append("-nxcompat");
- }
- if (!coff.dynamicbase) {
- try argv.append("-dynamicbase:NO");
- }
- if (coff.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 (coff.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 (coff.module_definition_file) |def| {
- try argv.append(try allocPrint(arena, "-DEF:{s}", .{def}));
- }
-
- const resolved_subsystem: ?std.Target.SubSystem = blk: {
- if (coff.subsystem) |explicit| break :blk explicit;
- switch (target.os.tag) {
- .windows => {
- if (comp.zcu != null) {
- if (coff.lld_export_flags.dllmain_crt_startup or is_dyn_lib)
- break :blk null;
- if (coff.lld_export_flags.c_main or comp.config.is_test or
- coff.lld_export_flags.winmain_crt_startup or
- coff.lld_export_flags.wwinmain_crt_startup)
- {
- break :blk .Console;
- }
- if (coff.lld_export_flags.winmain or coff.lld_export_flags.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}", .{
- coff.major_subsystem_version, coff.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()) {
- 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, "libmingw32.lib"));
- } else {
- try argv.append(switch (comp.config.link_mode) {
- .static => "libcmt.lib",
- .dynamic => "msvcrt.lib",
- });
-
- const lib_str = switch (comp.config.link_mode) {
- .static => "lib",
- .dynamic => "",
- };
- try argv.append(try allocPrint(arena, "{s}vcruntime.lib", .{lib_str}));
- try argv.append(try allocPrint(arena, "{s}ucrt.lib", .{lib_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 != null) {
- if (coff.lld_export_flags.winmain_crt_startup) {
- try argv.append("-ENTRY:WinMainCRTStartup");
- } else {
- try argv.append("-ENTRY:wWinMainCRTStartup");
- }
- } else {
- try argv.append("-ENTRY:wWinMainCRTStartup");
- }
- }
- }
- },
- }
-
- if (comp.config.link_libc and link_in_crt) {
- if (comp.zigc_static_lib) |zigc| {
- try argv.append(try zigc.full_object_path.toString(arena));
- }
- }
-
- // 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));
- }
-
- const ubsan_rt_path: ?Path = blk: {
- if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path;
- if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path;
- break :blk null;
- };
- if (ubsan_rt_path) |path| {
- try argv.append(try path.toString(arena));
- }
-
- if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) {
- // 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, coff.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, coff.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 (!coff.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.
- coff.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;
-}
-
-pub fn flushZcu(
+pub fn flush(
coff: *Coff,
arena: Allocator,
tid: Zcu.PerThread.Id,
@@ -2211,17 +1602,22 @@ pub fn flushZcu(
const comp = coff.base.comp;
const diags = &comp.link_diags;
+ switch (coff.base.comp.config.output_mode) {
+ .Exe, .Obj => {},
+ .Lib => return diags.fail("writing lib files not yet implemented for COFF", .{}),
+ }
+
const sub_prog_node = prog_node.start("COFF Flush", 0);
defer sub_prog_node.end();
- return flushZcuInner(coff, arena, tid) catch |err| switch (err) {
+ return flushInner(coff, arena, tid) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.LinkFailure => return error.LinkFailure,
else => |e| return diags.fail("COFF flush failed: {s}", .{@errorName(e)}),
};
}
-fn flushZcuInner(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id) !void {
+fn flushInner(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id) !void {
_ = arena;
const comp = coff.base.comp;
src/link/Dwarf.zig
@@ -4391,7 +4391,7 @@ fn refAbbrevCode(dwarf: *Dwarf, abbrev_code: AbbrevCode) UpdateError!@typeInfo(A
return @intFromEnum(abbrev_code);
}
-pub fn flushZcu(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void {
+pub fn flush(dwarf: *Dwarf, pt: Zcu.PerThread) FlushError!void {
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
src/link/Elf.zig
@@ -4,7 +4,6 @@ base: link.File,
zig_object: ?*ZigObject,
rpath_table: std.StringArrayHashMapUnmanaged(void),
image_base: u64,
-emit_relocs: bool,
z_nodelete: bool,
z_notext: bool,
z_defs: bool,
@@ -16,18 +15,7 @@ z_relro: bool,
z_common_page_size: ?u64,
/// TODO make this non optional and resolve the default in open()
z_max_page_size: ?u64,
-hash_style: HashStyle,
-compress_debug_sections: CompressDebugSections,
-symbol_wrap_set: std.StringArrayHashMapUnmanaged(void),
-sort_section: ?SortSection,
soname: ?[]const u8,
-bind_global_refs_locally: bool,
-linker_script: ?[]const u8,
-version_script: ?[]const u8,
-allow_undefined_version: bool,
-enable_new_dtags: ?bool,
-print_icf_sections: bool,
-print_map: bool,
entry_name: ?[]const u8,
ptr_width: PtrWidth,
@@ -201,9 +189,6 @@ const minimum_atom_size = 64;
pub const min_text_capacity = padToIdeal(minimum_atom_size);
pub const PtrWidth = enum { p32, p64 };
-pub const HashStyle = enum { sysv, gnu, both };
-pub const CompressDebugSections = enum { none, zlib, zstd };
-pub const SortSection = enum { name, alignment };
pub fn createEmpty(
arena: Allocator,
@@ -214,7 +199,6 @@ pub fn createEmpty(
const target = comp.root_mod.resolved_target.result;
assert(target.ofmt == .elf);
- const use_lld = build_options.have_llvm and comp.config.use_lld;
const use_llvm = comp.config.use_llvm;
const opt_zcu = comp.zcu;
const output_mode = comp.config.output_mode;
@@ -265,12 +249,10 @@ pub fn createEmpty(
const is_dyn_lib = output_mode == .Lib and link_mode == .dynamic;
const default_sym_version: elf.Versym = if (is_dyn_lib or comp.config.rdynamic) .GLOBAL else .LOCAL;
- // If using LLD to link, this code should produce an object file so that it
- // can be passed to LLD.
// If using LLVM to generate the object file for the zig compilation unit,
// we need a place to put the object file so that it can be subsequently
// handled.
- const zcu_object_sub_path = if (!use_lld and !use_llvm)
+ const zcu_object_sub_path = if (!use_llvm)
null
else
try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path});
@@ -292,7 +274,6 @@ pub fn createEmpty(
.stack_size = options.stack_size orelse 16777216,
.allow_shlib_undefined = options.allow_shlib_undefined orelse !is_native_os,
.file = null,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
.zig_object = null,
@@ -317,7 +298,6 @@ pub fn createEmpty(
};
},
- .emit_relocs = options.emit_relocs,
.z_nodelete = options.z_nodelete,
.z_notext = options.z_notext,
.z_defs = options.z_defs,
@@ -327,27 +307,11 @@ pub fn createEmpty(
.z_relro = options.z_relro,
.z_common_page_size = options.z_common_page_size,
.z_max_page_size = options.z_max_page_size,
- .hash_style = options.hash_style,
- .compress_debug_sections = options.compress_debug_sections,
- .symbol_wrap_set = options.symbol_wrap_set,
- .sort_section = options.sort_section,
.soname = options.soname,
- .bind_global_refs_locally = options.bind_global_refs_locally,
- .linker_script = options.linker_script,
- .version_script = options.version_script,
- .allow_undefined_version = options.allow_undefined_version,
- .enable_new_dtags = options.enable_new_dtags,
- .print_icf_sections = options.print_icf_sections,
- .print_map = options.print_map,
.dump_argv_list = .empty,
};
errdefer self.base.destroy();
- if (use_lld and (use_llvm or !comp.config.have_zcu)) {
- // LLVM emits the object file (if any); LLD links it into the final product.
- return self;
- }
-
// --verbose-link
if (comp.verbose_link) try dumpArgvInit(self, arena);
@@ -355,13 +319,11 @@ pub fn createEmpty(
const is_obj_or_ar = is_obj or (output_mode == .Lib and link_mode == .static);
// What path should this ELF linker code output to?
- // If using LLD to link, this code should produce an object file so that it
- // can be passed to LLD.
- const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path;
+ const sub_path = emit.sub_path;
self.base.file = try emit.root_dir.handle.createFile(sub_path, .{
.truncate = true,
.read = true,
- .mode = link.File.determineMode(use_lld, output_mode, link_mode),
+ .mode = link.File.determineMode(output_mode, link_mode),
});
const gpa = comp.gpa;
@@ -785,20 +747,6 @@ pub fn loadInput(self: *Elf, input: link.Input) !void {
}
pub fn flush(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
- const comp = self.base.comp;
- const use_lld = build_options.have_llvm and comp.config.use_lld;
- const diags = &comp.link_diags;
- if (use_lld) {
- return self.linkWithLLD(arena, tid, prog_node) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.LinkFailure => return error.LinkFailure,
- else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}),
- };
- }
- try self.flushZcu(arena, tid, prog_node);
-}
-
-pub fn flushZcu(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -810,14 +758,14 @@ pub fn flushZcu(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
const sub_prog_node = prog_node.start("ELF Flush", 0);
defer sub_prog_node.end();
- return flushZcuInner(self, arena, tid) catch |err| switch (err) {
+ return flushInner(self, arena, tid) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.LinkFailure => return error.LinkFailure,
else => |e| return diags.fail("ELF flush failed: {s}", .{@errorName(e)}),
};
}
-fn flushZcuInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void {
+fn flushInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void {
const comp = self.base.comp;
const gpa = comp.gpa;
const diags = &comp.link_diags;
@@ -1492,643 +1440,6 @@ pub fn initOutputSection(self: *Elf, args: struct {
return out_shndx;
}
-fn linkWithLLD(self: *Elf, 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 diags = &comp.link_diags;
-
- 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) |zcu| blk: {
- if (zcu.llvm_object == null) {
- try self.flushZcu(arena, tid, prog_node);
- } else {
- // `Compilation.flush` has already made LLVM emit this object file for us.
- }
-
- 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 output_mode = comp.config.output_mode;
- const is_obj = output_mode == .Obj;
- const is_lib = output_mode == .Lib;
- const link_mode = comp.config.link_mode;
- const is_dyn_lib = link_mode == .dynamic and is_lib;
- const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe;
- const have_dynamic_linker = link_mode == .dynamic and is_exe_or_dyn_lib;
- const target = self.getTarget();
- const compiler_rt_path: ?Path = blk: {
- if (comp.compiler_rt_lib) |x| break :blk x.full_object_path;
- if (comp.compiler_rt_obj) |x| break :blk x.full_object_path;
- break :blk null;
- };
- const ubsan_rt_path: ?Path = blk: {
- if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path;
- if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path;
- break :blk null;
- };
-
- // Here we want to determine whether we can save time by not invoking LLD when the
- // output is unchanged. None of the linker options or the object files that are being
- // linked are in the hash that namespaces the directory we are outputting to. Therefore,
- // we must hash those now, and the resulting digest will form the "id" of the linking
- // job we are about to perform.
- // After a successful link, we store the id in the metadata of a symlink named "lld.id" in
- // the artifact directory. So, now, we check if this symlink exists, and if it matches
- // our digest. If so, we can skip linking. Otherwise, we proceed with invoking LLD.
- const id_symlink_basename = "lld.id";
-
- var man: std.Build.Cache.Manifest = undefined;
- defer if (!self.base.disable_lld_caching) man.deinit();
-
- var digest: [std.Build.Cache.hex_digest_len]u8 = undefined;
-
- if (!self.base.disable_lld_caching) {
- man = comp.cache_parent.obtain();
-
- // We are about to obtain this lock, so here we give other processes a chance first.
- self.base.releaseLock();
-
- comptime assert(Compilation.link_hash_implementation_version == 14);
-
- try man.addOptionalFile(self.linker_script);
- try man.addOptionalFile(self.version_script);
- man.hash.add(self.allow_undefined_version);
- man.hash.addOptional(self.enable_new_dtags);
- try link.hashInputs(&man, comp.link_inputs);
- for (comp.c_object_table.keys()) |key| {
- _ = try man.addFilePath(key.status.success.object_path, null);
- }
- try man.addOptionalFile(module_obj_path);
- try man.addOptionalFilePath(compiler_rt_path);
- try man.addOptionalFilePath(ubsan_rt_path);
- try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null);
- try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null);
-
- // We can skip hashing libc and libc++ components that we are in charge of building from Zig
- // installation sources because they are always a product of the compiler version + target information.
- man.hash.addOptionalBytes(self.entry_name);
- man.hash.add(self.image_base);
- man.hash.add(self.base.gc_sections);
- man.hash.addOptional(self.sort_section);
- man.hash.add(comp.link_eh_frame_hdr);
- man.hash.add(self.emit_relocs);
- man.hash.add(comp.config.rdynamic);
- man.hash.addListOfBytes(self.rpath_table.keys());
- if (output_mode == .Exe) {
- man.hash.add(self.base.stack_size);
- }
- man.hash.add(self.base.build_id);
- man.hash.addListOfBytes(self.symbol_wrap_set.keys());
- man.hash.add(comp.skip_linker_dependencies);
- man.hash.add(self.z_nodelete);
- man.hash.add(self.z_notext);
- man.hash.add(self.z_defs);
- man.hash.add(self.z_origin);
- man.hash.add(self.z_nocopyreloc);
- man.hash.add(self.z_now);
- man.hash.add(self.z_relro);
- man.hash.add(self.z_common_page_size orelse 0);
- man.hash.add(self.z_max_page_size orelse 0);
- man.hash.add(self.hash_style);
- // strip does not need to go into the linker hash because it is part of the hash namespace
- 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 (have_dynamic_linker) {
- man.hash.addOptionalBytes(target.dynamic_linker.get());
- }
- man.hash.addOptionalBytes(self.soname);
- man.hash.addOptional(comp.version);
- man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
- man.hash.add(self.base.allow_shlib_undefined);
- man.hash.add(self.bind_global_refs_locally);
- man.hash.add(self.compress_debug_sections);
- man.hash.add(comp.config.any_sanitize_thread);
- man.hash.add(comp.config.any_fuzz);
- man.hash.addOptionalBytes(comp.sysroot);
-
- // 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 = std.Build.Cache.readSmallFile(
- directory.handle,
- id_symlink_basename,
- &prev_digest_buf,
- ) catch |err| blk: {
- log.debug("ELF 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("ELF 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("ELF 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,
- };
- }
-
- // Due to a deficiency in LLD, we need to special-case BPF to a simple file
- // copy when generating relocatables. Normally, we would expect `lld -r` to work.
- // However, because LLD wants to resolve BPF relocations which it shouldn't, it fails
- // before even generating the relocatable.
- //
- // For m68k, we go through this path because LLD doesn't support it yet, but LLVM can
- // produce usable object files.
- if (output_mode == .Obj and
- (comp.config.lto != .none or
- target.cpu.arch.isBpf() or
- target.cpu.arch == .lanai or
- target.cpu.arch == .m68k or
- target.cpu.arch.isSPARC() or
- target.cpu.arch == .ve or
- target.cpu.arch == .xcore))
- {
- // In this case we must 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 = "ld.lld";
- try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command });
- if (is_obj) {
- try argv.append("-r");
- }
-
- try argv.append("--error-limit=0");
-
- if (comp.sysroot) |sysroot| {
- try argv.append(try std.fmt.allocPrint(arena, "--sysroot={s}", .{sysroot}));
- }
-
- if (target_util.llvmMachineAbi(target)) |mabi| {
- try argv.appendSlice(&.{
- "-mllvm",
- try std.fmt.allocPrint(arena, "-target-abi={s}", .{mabi}),
- });
- }
-
- try argv.appendSlice(&.{
- "-mllvm",
- try std.fmt.allocPrint(arena, "-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"}),
- });
-
- if (comp.config.lto != .none) {
- switch (comp.root_mod.optimize_mode) {
- .Debug => {},
- .ReleaseSmall => try argv.append("--lto-O2"),
- .ReleaseFast, .ReleaseSafe => try argv.append("--lto-O3"),
- }
- }
- switch (comp.root_mod.optimize_mode) {
- .Debug => {},
- .ReleaseSmall => try argv.append("-O2"),
- .ReleaseFast, .ReleaseSafe => try argv.append("-O3"),
- }
-
- if (self.entry_name) |name| {
- try argv.appendSlice(&.{ "--entry", name });
- }
-
- for (comp.force_undefined_symbols.keys()) |sym| {
- try argv.append("-u");
- try argv.append(sym);
- }
-
- switch (self.hash_style) {
- .gnu => try argv.append("--hash-style=gnu"),
- .sysv => try argv.append("--hash-style=sysv"),
- .both => {}, // this is the default
- }
-
- if (output_mode == .Exe) {
- try argv.appendSlice(&.{
- "-z",
- try std.fmt.allocPrint(arena, "stack-size={d}", .{self.base.stack_size}),
- });
- }
-
- switch (self.base.build_id) {
- .none => try argv.append("--build-id=none"),
- .fast, .uuid, .sha1, .md5 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{
- @tagName(self.base.build_id),
- })),
- .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{
- std.fmt.fmtSliceHexLower(hs.toSlice()),
- })),
- }
-
- try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{self.image_base}));
-
- if (self.linker_script) |linker_script| {
- try argv.append("-T");
- try argv.append(linker_script);
- }
-
- if (self.sort_section) |how| {
- const arg = try std.fmt.allocPrint(arena, "--sort-section={s}", .{@tagName(how)});
- try argv.append(arg);
- }
-
- if (self.base.gc_sections) {
- try argv.append("--gc-sections");
- }
-
- if (self.base.print_gc_sections) {
- try argv.append("--print-gc-sections");
- }
-
- if (self.print_icf_sections) {
- try argv.append("--print-icf-sections");
- }
-
- if (self.print_map) {
- try argv.append("--print-map");
- }
-
- if (comp.link_eh_frame_hdr) {
- try argv.append("--eh-frame-hdr");
- }
-
- if (self.emit_relocs) {
- try argv.append("--emit-relocs");
- }
-
- if (comp.config.rdynamic) {
- try argv.append("--export-dynamic");
- }
-
- if (comp.config.debug_format == .strip) {
- try argv.append("-s");
- }
-
- if (self.z_nodelete) {
- try argv.append("-z");
- try argv.append("nodelete");
- }
- if (self.z_notext) {
- try argv.append("-z");
- try argv.append("notext");
- }
- if (self.z_defs) {
- try argv.append("-z");
- try argv.append("defs");
- }
- if (self.z_origin) {
- try argv.append("-z");
- try argv.append("origin");
- }
- if (self.z_nocopyreloc) {
- try argv.append("-z");
- try argv.append("nocopyreloc");
- }
- if (self.z_now) {
- // LLD defaults to -zlazy
- try argv.append("-znow");
- }
- if (!self.z_relro) {
- // LLD defaults to -zrelro
- try argv.append("-znorelro");
- }
- if (self.z_common_page_size) |size| {
- try argv.append("-z");
- try argv.append(try std.fmt.allocPrint(arena, "common-page-size={d}", .{size}));
- }
- if (self.z_max_page_size) |size| {
- try argv.append("-z");
- try argv.append(try std.fmt.allocPrint(arena, "max-page-size={d}", .{size}));
- }
-
- if (getLDMOption(target)) |ldm| {
- try argv.append("-m");
- try argv.append(ldm);
- }
-
- if (link_mode == .static) {
- if (target.cpu.arch.isArm()) {
- try argv.append("-Bstatic");
- } else {
- try argv.append("-static");
- }
- } else if (switch (target.os.tag) {
- else => is_dyn_lib,
- .haiku => is_exe_or_dyn_lib,
- }) {
- try argv.append("-shared");
- }
-
- if (comp.config.pie and output_mode == .Exe) {
- try argv.append("-pie");
- }
-
- if (is_exe_or_dyn_lib and target.os.tag == .netbsd) {
- // Add options to produce shared objects with only 2 PT_LOAD segments.
- // NetBSD expects 2 PT_LOAD segments in a shared object, otherwise
- // ld.elf_so fails loading dynamic libraries with "not found" error.
- // See https://github.com/ziglang/zig/issues/9109 .
- try argv.append("--no-rosegment");
- try argv.append("-znorelro");
- }
-
- try argv.append("-o");
- try argv.append(full_out_path);
-
- // csu prelude
- const csu = try comp.getCrtPaths(arena);
- if (csu.crt0) |p| try argv.append(try p.toString(arena));
- if (csu.crti) |p| try argv.append(try p.toString(arena));
- if (csu.crtbegin) |p| try argv.append(try p.toString(arena));
-
- for (self.rpath_table.keys()) |rpath| {
- try argv.appendSlice(&.{ "-rpath", rpath });
- }
-
- for (self.symbol_wrap_set.keys()) |symbol_name| {
- try argv.appendSlice(&.{ "-wrap", symbol_name });
- }
-
- if (comp.config.link_libc) {
- if (comp.libc_installation) |libc_installation| {
- try argv.append("-L");
- try argv.append(libc_installation.crt_dir.?);
- }
- }
-
- if (have_dynamic_linker and
- (comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker))
- {
- if (target.dynamic_linker.get()) |dynamic_linker| {
- try argv.append("-dynamic-linker");
- try argv.append(dynamic_linker);
- }
- }
-
- if (is_dyn_lib) {
- if (self.soname) |soname| {
- try argv.append("-soname");
- try argv.append(soname);
- }
- if (self.version_script) |version_script| {
- try argv.append("-version-script");
- try argv.append(version_script);
- }
- if (self.allow_undefined_version) {
- try argv.append("--undefined-version");
- } else {
- try argv.append("--no-undefined-version");
- }
- if (self.enable_new_dtags) |enable_new_dtags| {
- if (enable_new_dtags) {
- try argv.append("--enable-new-dtags");
- } else {
- try argv.append("--disable-new-dtags");
- }
- }
- }
-
- // Positional arguments to the linker such as object files.
- var whole_archive = false;
-
- for (self.base.comp.link_inputs) |link_input| switch (link_input) {
- .res => unreachable, // Windows-only
- .dso => continue,
- .object, .archive => |obj| {
- if (obj.must_link and !whole_archive) {
- try argv.append("-whole-archive");
- whole_archive = true;
- } else if (!obj.must_link and whole_archive) {
- try argv.append("-no-whole-archive");
- whole_archive = false;
- }
- try argv.append(try obj.path.toString(arena));
- },
- .dso_exact => |dso_exact| {
- assert(dso_exact.name[0] == ':');
- try argv.appendSlice(&.{ "-l", dso_exact.name });
- },
- };
-
- if (whole_archive) {
- try argv.append("-no-whole-archive");
- whole_archive = false;
- }
-
- for (comp.c_object_table.keys()) |key| {
- try argv.append(try key.status.success.object_path.toString(arena));
- }
-
- if (module_obj_path) |p| {
- try argv.append(p);
- }
-
- if (comp.tsan_lib) |lib| {
- assert(comp.config.any_sanitize_thread);
- try argv.append(try lib.full_object_path.toString(arena));
- }
-
- if (comp.fuzzer_lib) |lib| {
- assert(comp.config.any_fuzz);
- try argv.append(try lib.full_object_path.toString(arena));
- }
-
- if (ubsan_rt_path) |p| {
- try argv.append(try p.toString(arena));
- }
-
- // Shared libraries.
- if (is_exe_or_dyn_lib) {
- // Worst-case, we need an --as-needed argument for every lib, as well
- // as one before and one after.
- try argv.ensureUnusedCapacity(2 * self.base.comp.link_inputs.len + 2);
- argv.appendAssumeCapacity("--as-needed");
- var as_needed = true;
-
- for (self.base.comp.link_inputs) |link_input| switch (link_input) {
- .res => unreachable, // Windows-only
- .object, .archive, .dso_exact => continue,
- .dso => |dso| {
- const lib_as_needed = !dso.needed;
- switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) {
- 0b00, 0b11 => {},
- 0b01 => {
- argv.appendAssumeCapacity("--no-as-needed");
- as_needed = false;
- },
- 0b10 => {
- argv.appendAssumeCapacity("--as-needed");
- as_needed = true;
- },
- }
-
- // By this time, we depend on these libs being dynamically linked
- // libraries and not static libraries (the check for that needs to be earlier),
- // but they could be full paths to .so files, in which case we
- // want to avoid prepending "-l".
- argv.appendAssumeCapacity(try dso.path.toString(arena));
- },
- };
-
- if (!as_needed) {
- argv.appendAssumeCapacity("--as-needed");
- as_needed = true;
- }
-
- // 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));
- }
-
- // libc dep
- diags.flags.missing_libc = false;
- if (comp.config.link_libc) {
- if (comp.libc_installation != null) {
- const needs_grouping = link_mode == .static;
- if (needs_grouping) try argv.append("--start-group");
- try argv.appendSlice(target_util.libcFullLinkFlags(target));
- if (needs_grouping) try argv.append("--end-group");
- } else if (target.isGnuLibC()) {
- for (glibc.libs) |lib| {
- if (lib.removed_in) |rem_in| {
- if (target.os.versionRange().gnuLibCVersion().?.order(rem_in) != .lt) continue;
- }
-
- const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{
- comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
- });
- try argv.append(lib_path);
- }
- try argv.append(try comp.crtFileAsString(arena, "libc_nonshared.a"));
- } else if (target.isMuslLibC()) {
- try argv.append(try comp.crtFileAsString(arena, switch (link_mode) {
- .static => "libc.a",
- .dynamic => "libc.so",
- }));
- } else if (target.isFreeBSDLibC()) {
- for (freebsd.libs) |lib| {
- const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{
- comp.freebsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
- });
- try argv.append(lib_path);
- }
- } else if (target.isNetBSDLibC()) {
- for (netbsd.libs) |lib| {
- const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{
- comp.netbsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
- });
- try argv.append(lib_path);
- }
- } else {
- diags.flags.missing_libc = true;
- }
-
- if (comp.zigc_static_lib) |zigc| {
- try argv.append(try zigc.full_object_path.toString(arena));
- }
- }
- }
-
- // compiler-rt. Since compiler_rt exports symbols like `memset`, it needs
- // to be after the shared libraries, so they are picked up from the shared
- // libraries, not libcompiler_rt.
- if (compiler_rt_path) |p| {
- try argv.append(try p.toString(arena));
- }
-
- // crt postlude
- if (csu.crtend) |p| try argv.append(try p.toString(arena));
- if (csu.crtn) |p| try argv.append(try p.toString(arena));
-
- if (self.base.allow_shlib_undefined) {
- try argv.append("--allow-shlib-undefined");
- }
-
- switch (self.compress_debug_sections) {
- .none => {},
- .zlib => try argv.append("--compress-debug-sections=zlib"),
- .zstd => try argv.append("--compress-debug-sections=zstd"),
- }
-
- if (self.bind_global_refs_locally) {
- try argv.append("-Bsymbolic");
- }
-
- 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.
- std.Build.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();
- }
-}
-
pub fn writeShdrTable(self: *Elf) !void {
const gpa = self.base.comp.gpa;
const target_endian = self.getTarget().cpu.arch.endian();
@@ -4121,85 +3432,6 @@ fn shdrTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr {
};
}
-fn getLDMOption(target: std.Target) ?[]const u8 {
- // This should only return emulations understood by LLD's parseEmulation().
- return switch (target.cpu.arch) {
- .aarch64 => switch (target.os.tag) {
- .linux => "aarch64linux",
- else => "aarch64elf",
- },
- .aarch64_be => switch (target.os.tag) {
- .linux => "aarch64linuxb",
- else => "aarch64elfb",
- },
- .amdgcn => "elf64_amdgpu",
- .arm, .thumb => switch (target.os.tag) {
- .linux => "armelf_linux_eabi",
- else => "armelf",
- },
- .armeb, .thumbeb => switch (target.os.tag) {
- .linux => "armelfb_linux_eabi",
- else => "armelfb",
- },
- .hexagon => "hexagonelf",
- .loongarch32 => "elf32loongarch",
- .loongarch64 => "elf64loongarch",
- .mips => switch (target.os.tag) {
- .freebsd => "elf32btsmip_fbsd",
- else => "elf32btsmip",
- },
- .mipsel => switch (target.os.tag) {
- .freebsd => "elf32ltsmip_fbsd",
- else => "elf32ltsmip",
- },
- .mips64 => switch (target.os.tag) {
- .freebsd => switch (target.abi) {
- .gnuabin32, .muslabin32 => "elf32btsmipn32_fbsd",
- else => "elf64btsmip_fbsd",
- },
- else => switch (target.abi) {
- .gnuabin32, .muslabin32 => "elf32btsmipn32",
- else => "elf64btsmip",
- },
- },
- .mips64el => switch (target.os.tag) {
- .freebsd => switch (target.abi) {
- .gnuabin32, .muslabin32 => "elf32ltsmipn32_fbsd",
- else => "elf64ltsmip_fbsd",
- },
- else => switch (target.abi) {
- .gnuabin32, .muslabin32 => "elf32ltsmipn32",
- else => "elf64ltsmip",
- },
- },
- .msp430 => "msp430elf",
- .powerpc => switch (target.os.tag) {
- .freebsd => "elf32ppc_fbsd",
- .linux => "elf32ppclinux",
- else => "elf32ppc",
- },
- .powerpcle => switch (target.os.tag) {
- .linux => "elf32lppclinux",
- else => "elf32lppc",
- },
- .powerpc64 => "elf64ppc",
- .powerpc64le => "elf64lppc",
- .riscv32 => "elf32lriscv",
- .riscv64 => "elf64lriscv",
- .s390x => "elf64_s390",
- .sparc64 => "elf64_sparc",
- .x86 => switch (target.os.tag) {
- .freebsd => "elf_i386_fbsd",
- else => "elf_i386",
- },
- .x86_64 => switch (target.abi) {
- .gnux32, .muslx32 => "elf32_x86_64",
- else => "elf_x86_64",
- },
- else => null,
- };
-}
-
pub fn padToIdeal(actual_size: anytype) @TypeOf(actual_size) {
return actual_size +| (actual_size / ideal_factor);
}
@@ -5284,10 +4516,7 @@ const codegen = @import("../codegen.zig");
const dev = @import("../dev.zig");
const eh_frame = @import("Elf/eh_frame.zig");
const gc = @import("Elf/gc.zig");
-const glibc = @import("../libs/glibc.zig");
const musl = @import("../libs/musl.zig");
-const freebsd = @import("../libs/freebsd.zig");
-const netbsd = @import("../libs/netbsd.zig");
const link = @import("../link.zig");
const relocatable = @import("Elf/relocatable.zig");
const relocation = @import("Elf/relocation.zig");
src/link/Goff.zig
@@ -46,7 +46,6 @@ pub fn createEmpty(
.stack_size = options.stack_size orelse 0,
.allow_shlib_undefined = options.allow_shlib_undefined orelse false,
.file = null,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
};
@@ -105,10 +104,6 @@ pub fn updateExports(
}
pub fn flush(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
- return self.flushZcu(arena, tid, prog_node);
-}
-
-pub fn flushZcu(self: *Goff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
_ = self;
_ = arena;
_ = tid;
src/link/Lld.zig
@@ -0,0 +1,2148 @@
+base: link.File,
+disable_caching: bool,
+ofmt: union(enum) {
+ elf: Elf,
+ coff: Coff,
+ wasm: Wasm,
+},
+
+const Coff = struct {
+ image_base: u64,
+ entry: link.File.OpenOptions.Entry,
+ pdb_out_path: ?[]const u8,
+ repro: bool,
+ tsaware: bool,
+ nxcompat: bool,
+ dynamicbase: bool,
+ /// TODO this and minor_subsystem_version should be combined into one property and left as
+ /// default or populated together. They should not be separate fields.
+ major_subsystem_version: u16,
+ minor_subsystem_version: u16,
+ lib_directories: []const Cache.Directory,
+ module_definition_file: ?[]const u8,
+ subsystem: ?std.Target.SubSystem,
+ /// These flags are populated by `codegen.llvm.updateExports` to allow us to guess the subsystem.
+ lld_export_flags: struct {
+ c_main: bool,
+ winmain: bool,
+ wwinmain: bool,
+ winmain_crt_startup: bool,
+ wwinmain_crt_startup: bool,
+ dllmain_crt_startup: bool,
+ },
+ fn init(comp: *Compilation, options: link.File.OpenOptions) !Coff {
+ const target = comp.root_mod.resolved_target.result;
+ const output_mode = comp.config.output_mode;
+ return .{
+ .image_base = options.image_base orelse switch (output_mode) {
+ .Exe => switch (target.cpu.arch) {
+ .aarch64, .x86_64 => 0x140000000,
+ .thumb, .x86 => 0x400000,
+ else => unreachable,
+ },
+ .Lib => switch (target.cpu.arch) {
+ .aarch64, .x86_64 => 0x180000000,
+ .thumb, .x86 => 0x10000000,
+ else => unreachable,
+ },
+ .Obj => 0,
+ },
+ .entry = options.entry,
+ .pdb_out_path = options.pdb_out_path,
+ .repro = options.repro,
+ .tsaware = options.tsaware,
+ .nxcompat = options.nxcompat,
+ .dynamicbase = options.dynamicbase,
+ .major_subsystem_version = options.major_subsystem_version orelse 6,
+ .minor_subsystem_version = options.minor_subsystem_version orelse 0,
+ .lib_directories = options.lib_directories,
+ .module_definition_file = options.module_definition_file,
+ // Subsystem depends on the set of public symbol names from linked objects.
+ // See LinkerDriver::inferSubsystem from the LLD project for the flow chart.
+ .subsystem = options.subsystem,
+ // These flags are initially all `false`; the LLVM backend populates them when it learns about exports.
+ .lld_export_flags = .{
+ .c_main = false,
+ .winmain = false,
+ .wwinmain = false,
+ .winmain_crt_startup = false,
+ .wwinmain_crt_startup = false,
+ .dllmain_crt_startup = false,
+ },
+ };
+ }
+};
+pub const Elf = struct {
+ entry_name: ?[]const u8,
+ hash_style: HashStyle,
+ image_base: u64,
+ linker_script: ?[]const u8,
+ version_script: ?[]const u8,
+ sort_section: ?SortSection,
+ print_icf_sections: bool,
+ print_map: bool,
+ emit_relocs: bool,
+ z_nodelete: bool,
+ z_notext: bool,
+ z_defs: bool,
+ z_origin: bool,
+ z_nocopyreloc: bool,
+ z_now: bool,
+ z_relro: bool,
+ z_common_page_size: ?u64,
+ z_max_page_size: ?u64,
+ rpath_list: []const []const u8,
+ symbol_wrap_set: []const []const u8,
+ soname: ?[]const u8,
+ allow_undefined_version: bool,
+ enable_new_dtags: ?bool,
+ compress_debug_sections: CompressDebugSections,
+ bind_global_refs_locally: bool,
+ pub const HashStyle = enum { sysv, gnu, both };
+ pub const SortSection = enum { name, alignment };
+ pub const CompressDebugSections = enum { none, zlib, zstd };
+
+ fn init(comp: *Compilation, options: link.File.OpenOptions) !Elf {
+ const PtrWidth = enum { p32, p64 };
+ const target = comp.root_mod.resolved_target.result;
+ const output_mode = comp.config.output_mode;
+ const is_dyn_lib = output_mode == .Lib and comp.config.link_mode == .dynamic;
+ const ptr_width: PtrWidth = switch (target.ptrBitWidth()) {
+ 0...32 => .p32,
+ 33...64 => .p64,
+ else => return error.UnsupportedElfArchitecture,
+ };
+ const default_entry_name: []const u8 = switch (target.cpu.arch) {
+ .mips, .mipsel, .mips64, .mips64el => "__start",
+ else => "_start",
+ };
+ return .{
+ .entry_name = switch (options.entry) {
+ .disabled => null,
+ .default => if (output_mode != .Exe) null else default_entry_name,
+ .enabled => default_entry_name,
+ .named => |name| name,
+ },
+ .hash_style = options.hash_style,
+ .image_base = b: {
+ if (is_dyn_lib) break :b 0;
+ if (output_mode == .Exe and comp.config.pie) break :b 0;
+ break :b options.image_base orelse switch (ptr_width) {
+ .p32 => 0x10000,
+ .p64 => 0x1000000,
+ };
+ },
+ .linker_script = options.linker_script,
+ .version_script = options.version_script,
+ .sort_section = options.sort_section,
+ .print_icf_sections = options.print_icf_sections,
+ .print_map = options.print_map,
+ .emit_relocs = options.emit_relocs,
+ .z_nodelete = options.z_nodelete,
+ .z_notext = options.z_notext,
+ .z_defs = options.z_defs,
+ .z_origin = options.z_origin,
+ .z_nocopyreloc = options.z_nocopyreloc,
+ .z_now = options.z_now,
+ .z_relro = options.z_relro,
+ .z_common_page_size = options.z_common_page_size,
+ .z_max_page_size = options.z_max_page_size,
+ .rpath_list = options.rpath_list,
+ .symbol_wrap_set = options.symbol_wrap_set.keys(),
+ .soname = options.soname,
+ .allow_undefined_version = options.allow_undefined_version,
+ .enable_new_dtags = options.enable_new_dtags,
+ .compress_debug_sections = options.compress_debug_sections,
+ .bind_global_refs_locally = options.bind_global_refs_locally,
+ };
+ }
+};
+const Wasm = struct {
+ /// Symbol name of the entry function to export
+ entry_name: ?[]const u8,
+ /// When true, will import the function table from the host environment.
+ import_table: bool,
+ /// When true, will export the function table to the host environment.
+ export_table: bool,
+ /// When defined, sets the initial memory size of the memory.
+ initial_memory: ?u64,
+ /// When defined, sets the maximum memory size of the memory.
+ max_memory: ?u64,
+ /// When defined, sets the start of the data section.
+ global_base: ?u64,
+ /// Set of *global* symbol names to export to the host environment.
+ export_symbol_names: []const []const u8,
+ /// When true, will allow undefined symbols
+ import_symbols: bool,
+ fn init(comp: *Compilation, options: link.File.OpenOptions) !Wasm {
+ const default_entry_name: []const u8 = switch (comp.config.wasi_exec_model) {
+ .reactor => "_initialize",
+ .command => "_start",
+ };
+ return .{
+ .entry_name = switch (options.entry) {
+ .disabled => null,
+ .default => if (comp.config.output_mode != .Exe) null else default_entry_name,
+ .enabled => default_entry_name,
+ .named => |name| name,
+ },
+ .import_table = options.import_table,
+ .export_table = options.export_table,
+ .initial_memory = options.initial_memory,
+ .max_memory = options.max_memory,
+ .global_base = options.global_base,
+ .export_symbol_names = options.export_symbol_names,
+ .import_symbols = options.import_symbols,
+ };
+ }
+};
+
+pub fn createEmpty(
+ arena: Allocator,
+ comp: *Compilation,
+ emit: Cache.Path,
+ options: link.File.OpenOptions,
+) !*Lld {
+ const target = comp.root_mod.resolved_target.result;
+ const output_mode = comp.config.output_mode;
+ const optimize_mode = comp.root_mod.optimize_mode;
+ const is_native_os = comp.root_mod.resolved_target.is_native_os;
+
+ const obj_file_ext: []const u8 = switch (target.ofmt) {
+ .coff => "obj",
+ .elf, .wasm => "o",
+ else => unreachable,
+ };
+ const gc_sections: bool = options.gc_sections orelse switch (target.ofmt) {
+ .coff => optimize_mode != .Debug,
+ .elf => optimize_mode != .Debug and output_mode != .Obj,
+ .wasm => output_mode != .Obj,
+ else => unreachable,
+ };
+ const stack_size: u64 = options.stack_size orelse default: {
+ if (target.ofmt == .wasm and target.os.tag == .freestanding)
+ break :default 1 * 1024 * 1024; // 1 MiB
+ break :default 16 * 1024 * 1024; // 16 MiB
+ };
+
+ const lld = try arena.create(Lld);
+ lld.* = .{
+ .base = .{
+ .tag = .lld,
+ .comp = comp,
+ .emit = emit,
+ .zcu_object_sub_path = try allocPrint(arena, "{s}.{s}", .{ emit.sub_path, obj_file_ext }),
+ .gc_sections = gc_sections,
+ .print_gc_sections = options.print_gc_sections,
+ .stack_size = stack_size,
+ .allow_shlib_undefined = options.allow_shlib_undefined orelse !is_native_os,
+ .file = null,
+ .build_id = options.build_id,
+ },
+ .disable_caching = options.disable_lld_caching,
+ .ofmt = switch (target.ofmt) {
+ .coff => .{ .coff = try .init(comp, options) },
+ .elf => .{ .elf = try .init(comp, options) },
+ .wasm => .{ .wasm = try .init(comp, options) },
+ else => unreachable,
+ },
+ };
+ return lld;
+}
+pub fn deinit(lld: *Lld) void {
+ _ = lld;
+}
+pub fn flush(
+ lld: *Lld,
+ arena: Allocator,
+ tid: Zcu.PerThread.Id,
+ prog_node: std.Progress.Node,
+) link.File.FlushError!void {
+ dev.check(.lld_linker);
+ _ = tid;
+
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const sub_prog_node = prog_node.start("LLD Link", 0);
+ defer sub_prog_node.end();
+
+ const comp = lld.base.comp;
+ const result = if (comp.config.output_mode == .Lib and comp.config.link_mode == .static) r: {
+ break :r linkAsArchive(lld, arena);
+ } else switch (lld.ofmt) {
+ .coff => coffLink(lld, arena),
+ .elf => elfLink(lld, arena),
+ .wasm => wasmLink(lld, arena),
+ };
+ result catch |err| switch (err) {
+ error.OutOfMemory, error.LinkFailure => |e| return e,
+ else => |e| return lld.base.comp.link_diags.fail("failed to link with LLD: {s}", .{@errorName(e)}),
+ };
+}
+
+fn linkAsArchive(lld: *Lld, arena: Allocator) !void {
+ const base = &lld.base;
+ const comp = base.comp;
+ const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
+ const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
+ const full_out_path_z = try arena.dupeZ(u8, full_out_path);
+ const opt_zcu = comp.zcu;
+
+ // 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 zcu_obj_path: ?[]const u8 = if (opt_zcu != null) blk: {
+ const dirname = fs.path.dirname(full_out_path_z) orelse ".";
+ break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
+ } else null;
+
+ log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"});
+
+ const compiler_rt_path: ?Cache.Path = if (comp.compiler_rt_strat == .obj)
+ comp.compiler_rt_obj.?.full_object_path
+ else
+ null;
+
+ const ubsan_rt_path: ?Cache.Path = if (comp.ubsan_rt_strat == .obj)
+ comp.ubsan_rt_obj.?.full_object_path
+ else
+ null;
+
+ // This function follows the same pattern as link.Elf.linkWithLLD so if you want some
+ // insight as to what's going on here you can read that function body which is more
+ // well-commented.
+
+ const id_symlink_basename = "llvm-ar.id";
+
+ var man: Cache.Manifest = undefined;
+ defer if (!lld.disable_caching) man.deinit();
+
+ const link_inputs = comp.link_inputs;
+
+ var digest: [Cache.hex_digest_len]u8 = undefined;
+
+ if (!lld.disable_caching) {
+ man = comp.cache_parent.obtain();
+
+ // We are about to obtain this lock, so here we give other processes a chance first.
+ base.releaseLock();
+
+ try link.hashInputs(&man, 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(zcu_obj_path);
+ try man.addOptionalFilePath(compiler_rt_path);
+ try man.addOptionalFilePath(ubsan_rt_path);
+
+ // 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| b: {
+ log.debug("archive new_digest={s} readFile error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) });
+ break :b prev_digest_buf[0..0];
+ };
+ if (mem.eql(u8, prev_digest, &digest)) {
+ log.debug("archive digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
+ base.lock = man.toOwnedLock();
+ return;
+ }
+
+ // 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,
+ };
+ }
+
+ var object_files: std.ArrayListUnmanaged([*:0]const u8) = .empty;
+
+ try object_files.ensureUnusedCapacity(arena, link_inputs.len);
+ for (link_inputs) |input| {
+ object_files.appendAssumeCapacity(try input.path().?.toStringZ(arena));
+ }
+
+ try object_files.ensureUnusedCapacity(arena, comp.c_object_table.count() +
+ comp.win32_resource_table.count() + 2);
+
+ for (comp.c_object_table.keys()) |key| {
+ object_files.appendAssumeCapacity(try key.status.success.object_path.toStringZ(arena));
+ }
+ for (comp.win32_resource_table.keys()) |key| {
+ object_files.appendAssumeCapacity(try arena.dupeZ(u8, key.status.success.res_path));
+ }
+ if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p));
+ if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
+ if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
+
+ if (comp.verbose_link) {
+ std.debug.print("ar rcs {s}", .{full_out_path_z});
+ for (object_files.items) |arg| {
+ std.debug.print(" {s}", .{arg});
+ }
+ std.debug.print("\n", .{});
+ }
+
+ const llvm_bindings = @import("../codegen/llvm/bindings.zig");
+ const llvm = @import("../codegen/llvm.zig");
+ const target = comp.root_mod.resolved_target.result;
+ llvm.initializeLLVMTarget(target.cpu.arch);
+ const bad = llvm_bindings.WriteArchive(
+ full_out_path_z,
+ object_files.items.ptr,
+ object_files.items.len,
+ switch (target.os.tag) {
+ .aix => .AIXBIG,
+ .windows => .COFF,
+ else => if (target.os.tag.isDarwin()) .DARWIN else .GNU,
+ },
+ );
+ if (bad) return error.UnableToWriteArchive;
+
+ if (!lld.disable_caching) {
+ Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
+ log.warn("failed to save archive hash digest file: {s}", .{@errorName(err)});
+ };
+
+ if (man.have_exclusive_lock) {
+ man.writeManifest() catch |err| {
+ log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)});
+ };
+ }
+
+ base.lock = man.toOwnedLock();
+ }
+}
+
+fn coffLink(lld: *Lld, arena: Allocator) !void {
+ const comp = lld.base.comp;
+ const gpa = comp.gpa;
+ const base = &lld.base;
+ const coff = &lld.ofmt.coff;
+
+ const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
+ const full_out_path = try directory.join(arena, &[_][]const u8{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) p: {
+ if (fs.path.dirname(full_out_path)) |dirname| {
+ break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
+ } else {
+ break :p base.zcu_object_sub_path.?;
+ }
+ } else null;
+
+ 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 (coff.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 (!lld.disable_caching) man.deinit();
+
+ var digest: [Cache.hex_digest_len]u8 = undefined;
+
+ if (!lld.disable_caching) {
+ man = comp.cache_parent.obtain();
+ 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(base.stack_size);
+ man.hash.add(coff.image_base);
+ man.hash.add(base.build_id);
+ {
+ // TODO remove this, libraries must instead be resolved by the frontend.
+ for (coff.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(coff.subsystem);
+ man.hash.add(comp.config.is_test);
+ man.hash.add(coff.tsaware);
+ man.hash.add(coff.nxcompat);
+ man.hash.add(coff.dynamicbase);
+ man.hash.add(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(coff.major_subsystem_version);
+ man.hash.add(coff.minor_subsystem_version);
+ man.hash.add(coff.repro);
+ man.hash.addOptional(comp.version);
+ try man.addOptionalFile(coff.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.
+ 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 Cache.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,
+ 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 });
+
+ if (target.isMinGW()) {
+ try argv.append("-lldmingw");
+ }
+
+ 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 = coff.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 (target_util.llvmMachineAbi(target)) |mabi| {
+ try argv.append(try allocPrint(arena, "-MLLVM:-target-abi={s}", .{mabi}));
+ }
+
+ try argv.append(try allocPrint(arena, "-MLLVM:-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"}));
+
+ if (comp.config.lto != .none) {
+ 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}", .{base.stack_size}));
+ }
+ try argv.append(try allocPrint(arena, "-BASE:{d}", .{coff.image_base}));
+
+ switch (base.build_id) {
+ .none => try argv.append("-BUILD-ID:NO"),
+ .fast => try argv.append("-BUILD-ID"),
+ .uuid, .sha1, .md5, .hexstring => {},
+ }
+
+ 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 == .thumb) {
+ try argv.append("-MACHINE:ARM");
+ } else if (target.cpu.arch == .aarch64) {
+ 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 (coff.repro) {
+ try argv.append("-BREPRO");
+ }
+
+ if (coff.tsaware) {
+ try argv.append("-tsaware");
+ }
+ if (coff.nxcompat) {
+ try argv.append("-nxcompat");
+ }
+ if (!coff.dynamicbase) {
+ try argv.append("-dynamicbase:NO");
+ }
+ if (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 (coff.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(Cache.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 (coff.module_definition_file) |def| {
+ try argv.append(try allocPrint(arena, "-DEF:{s}", .{def}));
+ }
+
+ const resolved_subsystem: ?std.Target.SubSystem = blk: {
+ if (coff.subsystem) |explicit| break :blk explicit;
+ switch (target.os.tag) {
+ .windows => {
+ if (comp.zcu != null) {
+ if (coff.lld_export_flags.dllmain_crt_startup or is_dyn_lib)
+ break :blk null;
+ if (coff.lld_export_flags.c_main or comp.config.is_test or
+ coff.lld_export_flags.winmain_crt_startup or
+ coff.lld_export_flags.wwinmain_crt_startup)
+ {
+ break :blk .Console;
+ }
+ if (coff.lld_export_flags.winmain or coff.lld_export_flags.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}", .{
+ coff.major_subsystem_version, coff.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()) {
+ 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, "libmingw32.lib"));
+ } else {
+ try argv.append(switch (comp.config.link_mode) {
+ .static => "libcmt.lib",
+ .dynamic => "msvcrt.lib",
+ });
+
+ const lib_str = switch (comp.config.link_mode) {
+ .static => "lib",
+ .dynamic => "",
+ };
+ try argv.append(try allocPrint(arena, "{s}vcruntime.lib", .{lib_str}));
+ try argv.append(try allocPrint(arena, "{s}ucrt.lib", .{lib_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 != null) {
+ if (coff.lld_export_flags.winmain_crt_startup) {
+ try argv.append("-ENTRY:WinMainCRTStartup");
+ } else {
+ try argv.append("-ENTRY:wWinMainCRTStartup");
+ }
+ } else {
+ try argv.append("-ENTRY:wWinMainCRTStartup");
+ }
+ }
+ }
+ },
+ }
+
+ if (comp.config.link_libc and link_in_crt) {
+ if (comp.zigc_static_lib) |zigc| {
+ try argv.append(try zigc.full_object_path.toString(arena));
+ }
+ }
+
+ // 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));
+ }
+
+ const ubsan_rt_path: ?Cache.Path = blk: {
+ if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path;
+ if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path;
+ break :blk null;
+ };
+ if (ubsan_rt_path) |path| {
+ try argv.append(try path.toString(arena));
+ }
+
+ if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) {
+ // 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, coff.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, coff.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 spawnLld(comp, arena, argv.items);
+ }
+
+ if (!lld.disable_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.
+ base.lock = man.toOwnedLock();
+ }
+}
+fn findLib(arena: Allocator, name: []const u8, lib_directories: []const Cache.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;
+}
+
+fn elfLink(lld: *Lld, arena: Allocator) !void {
+ const comp = lld.base.comp;
+ const gpa = comp.gpa;
+ const diags = &comp.link_diags;
+ const base = &lld.base;
+ const elf = &lld.ofmt.elf;
+
+ const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
+ const full_out_path = try directory.join(arena, &[_][]const u8{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) p: {
+ if (fs.path.dirname(full_out_path)) |dirname| {
+ break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
+ } else {
+ break :p base.zcu_object_sub_path.?;
+ }
+ } else null;
+
+ const output_mode = comp.config.output_mode;
+ const is_obj = output_mode == .Obj;
+ const is_lib = output_mode == .Lib;
+ const link_mode = comp.config.link_mode;
+ const is_dyn_lib = link_mode == .dynamic and is_lib;
+ const is_exe_or_dyn_lib = is_dyn_lib or output_mode == .Exe;
+ const have_dynamic_linker = link_mode == .dynamic and is_exe_or_dyn_lib;
+ const target = comp.root_mod.resolved_target.result;
+ const compiler_rt_path: ?Cache.Path = blk: {
+ if (comp.compiler_rt_lib) |x| break :blk x.full_object_path;
+ if (comp.compiler_rt_obj) |x| break :blk x.full_object_path;
+ break :blk null;
+ };
+ const ubsan_rt_path: ?Cache.Path = blk: {
+ if (comp.ubsan_rt_lib) |x| break :blk x.full_object_path;
+ if (comp.ubsan_rt_obj) |x| break :blk x.full_object_path;
+ break :blk null;
+ };
+
+ // Here we want to determine whether we can save time by not invoking LLD when the
+ // output is unchanged. None of the linker options or the object files that are being
+ // linked are in the hash that namespaces the directory we are outputting to. Therefore,
+ // we must hash those now, and the resulting digest will form the "id" of the linking
+ // job we are about to perform.
+ // After a successful link, we store the id in the metadata of a symlink named "lld.id" in
+ // the artifact directory. So, now, we check if this symlink exists, and if it matches
+ // our digest. If so, we can skip linking. Otherwise, we proceed with invoking LLD.
+ const id_symlink_basename = "lld.id";
+
+ var man: std.Build.Cache.Manifest = undefined;
+ defer if (!lld.disable_caching) man.deinit();
+
+ var digest: [std.Build.Cache.hex_digest_len]u8 = undefined;
+
+ if (!lld.disable_caching) {
+ man = comp.cache_parent.obtain();
+
+ // We are about to obtain this lock, so here we give other processes a chance first.
+ base.releaseLock();
+
+ comptime assert(Compilation.link_hash_implementation_version == 14);
+
+ try man.addOptionalFile(elf.linker_script);
+ try man.addOptionalFile(elf.version_script);
+ man.hash.add(elf.allow_undefined_version);
+ man.hash.addOptional(elf.enable_new_dtags);
+ try link.hashInputs(&man, comp.link_inputs);
+ for (comp.c_object_table.keys()) |key| {
+ _ = try man.addFilePath(key.status.success.object_path, null);
+ }
+ try man.addOptionalFile(module_obj_path);
+ try man.addOptionalFilePath(compiler_rt_path);
+ try man.addOptionalFilePath(ubsan_rt_path);
+ try man.addOptionalFilePath(if (comp.tsan_lib) |l| l.full_object_path else null);
+ try man.addOptionalFilePath(if (comp.fuzzer_lib) |l| l.full_object_path else null);
+
+ // We can skip hashing libc and libc++ components that we are in charge of building from Zig
+ // installation sources because they are always a product of the compiler version + target information.
+ man.hash.addOptionalBytes(elf.entry_name);
+ man.hash.add(elf.image_base);
+ man.hash.add(base.gc_sections);
+ man.hash.addOptional(elf.sort_section);
+ man.hash.add(comp.link_eh_frame_hdr);
+ man.hash.add(elf.emit_relocs);
+ man.hash.add(comp.config.rdynamic);
+ man.hash.addListOfBytes(elf.rpath_list);
+ if (output_mode == .Exe) {
+ man.hash.add(base.stack_size);
+ }
+ man.hash.add(base.build_id);
+ man.hash.addListOfBytes(elf.symbol_wrap_set);
+ man.hash.add(comp.skip_linker_dependencies);
+ man.hash.add(elf.z_nodelete);
+ man.hash.add(elf.z_notext);
+ man.hash.add(elf.z_defs);
+ man.hash.add(elf.z_origin);
+ man.hash.add(elf.z_nocopyreloc);
+ man.hash.add(elf.z_now);
+ man.hash.add(elf.z_relro);
+ man.hash.add(elf.z_common_page_size orelse 0);
+ man.hash.add(elf.z_max_page_size orelse 0);
+ man.hash.add(elf.hash_style);
+ // strip does not need to go into the linker hash because it is part of the hash namespace
+ 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 (have_dynamic_linker) {
+ man.hash.addOptionalBytes(target.dynamic_linker.get());
+ }
+ man.hash.addOptionalBytes(elf.soname);
+ man.hash.addOptional(comp.version);
+ man.hash.addListOfBytes(comp.force_undefined_symbols.keys());
+ man.hash.add(base.allow_shlib_undefined);
+ man.hash.add(elf.bind_global_refs_locally);
+ man.hash.add(elf.compress_debug_sections);
+ man.hash.add(comp.config.any_sanitize_thread);
+ man.hash.add(comp.config.any_fuzz);
+ man.hash.addOptionalBytes(comp.sysroot);
+
+ // 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 = std.Build.Cache.readSmallFile(
+ directory.handle,
+ id_symlink_basename,
+ &prev_digest_buf,
+ ) catch |err| blk: {
+ log.debug("ELF 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("ELF LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
+ // Hot diggity dog! The output binary is already there.
+ base.lock = man.toOwnedLock();
+ return;
+ }
+ log.debug("ELF 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,
+ };
+ }
+
+ // Due to a deficiency in LLD, we need to special-case BPF to a simple file
+ // copy when generating relocatables. Normally, we would expect `lld -r` to work.
+ // However, because LLD wants to resolve BPF relocations which it shouldn't, it fails
+ // before even generating the relocatable.
+ //
+ // For m68k, we go through this path because LLD doesn't support it yet, but LLVM can
+ // produce usable object files.
+ if (output_mode == .Obj and
+ (comp.config.lto != .none or
+ target.cpu.arch.isBpf() or
+ target.cpu.arch == .lanai or
+ target.cpu.arch == .m68k or
+ target.cpu.arch.isSPARC() or
+ target.cpu.arch == .ve or
+ target.cpu.arch == .xcore))
+ {
+ // In this case we must 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 Cache.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,
+ 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 = "ld.lld";
+ try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command });
+ if (is_obj) {
+ try argv.append("-r");
+ }
+
+ try argv.append("--error-limit=0");
+
+ if (comp.sysroot) |sysroot| {
+ try argv.append(try std.fmt.allocPrint(arena, "--sysroot={s}", .{sysroot}));
+ }
+
+ if (target_util.llvmMachineAbi(target)) |mabi| {
+ try argv.appendSlice(&.{
+ "-mllvm",
+ try std.fmt.allocPrint(arena, "-target-abi={s}", .{mabi}),
+ });
+ }
+
+ try argv.appendSlice(&.{
+ "-mllvm",
+ try std.fmt.allocPrint(arena, "-float-abi={s}", .{if (target.abi.float() == .hard) "hard" else "soft"}),
+ });
+
+ if (comp.config.lto != .none) {
+ switch (comp.root_mod.optimize_mode) {
+ .Debug => {},
+ .ReleaseSmall => try argv.append("--lto-O2"),
+ .ReleaseFast, .ReleaseSafe => try argv.append("--lto-O3"),
+ }
+ }
+ switch (comp.root_mod.optimize_mode) {
+ .Debug => {},
+ .ReleaseSmall => try argv.append("-O2"),
+ .ReleaseFast, .ReleaseSafe => try argv.append("-O3"),
+ }
+
+ if (elf.entry_name) |name| {
+ try argv.appendSlice(&.{ "--entry", name });
+ }
+
+ for (comp.force_undefined_symbols.keys()) |sym| {
+ try argv.append("-u");
+ try argv.append(sym);
+ }
+
+ switch (elf.hash_style) {
+ .gnu => try argv.append("--hash-style=gnu"),
+ .sysv => try argv.append("--hash-style=sysv"),
+ .both => {}, // this is the default
+ }
+
+ if (output_mode == .Exe) {
+ try argv.appendSlice(&.{
+ "-z",
+ try std.fmt.allocPrint(arena, "stack-size={d}", .{base.stack_size}),
+ });
+ }
+
+ switch (base.build_id) {
+ .none => try argv.append("--build-id=none"),
+ .fast, .uuid, .sha1, .md5 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{
+ @tagName(base.build_id),
+ })),
+ .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{
+ std.fmt.fmtSliceHexLower(hs.toSlice()),
+ })),
+ }
+
+ try argv.append(try std.fmt.allocPrint(arena, "--image-base={d}", .{elf.image_base}));
+
+ if (elf.linker_script) |linker_script| {
+ try argv.append("-T");
+ try argv.append(linker_script);
+ }
+
+ if (elf.sort_section) |how| {
+ const arg = try std.fmt.allocPrint(arena, "--sort-section={s}", .{@tagName(how)});
+ try argv.append(arg);
+ }
+
+ if (base.gc_sections) {
+ try argv.append("--gc-sections");
+ }
+
+ if (base.print_gc_sections) {
+ try argv.append("--print-gc-sections");
+ }
+
+ if (elf.print_icf_sections) {
+ try argv.append("--print-icf-sections");
+ }
+
+ if (elf.print_map) {
+ try argv.append("--print-map");
+ }
+
+ if (comp.link_eh_frame_hdr) {
+ try argv.append("--eh-frame-hdr");
+ }
+
+ if (elf.emit_relocs) {
+ try argv.append("--emit-relocs");
+ }
+
+ if (comp.config.rdynamic) {
+ try argv.append("--export-dynamic");
+ }
+
+ if (comp.config.debug_format == .strip) {
+ try argv.append("-s");
+ }
+
+ if (elf.z_nodelete) {
+ try argv.append("-z");
+ try argv.append("nodelete");
+ }
+ if (elf.z_notext) {
+ try argv.append("-z");
+ try argv.append("notext");
+ }
+ if (elf.z_defs) {
+ try argv.append("-z");
+ try argv.append("defs");
+ }
+ if (elf.z_origin) {
+ try argv.append("-z");
+ try argv.append("origin");
+ }
+ if (elf.z_nocopyreloc) {
+ try argv.append("-z");
+ try argv.append("nocopyreloc");
+ }
+ if (elf.z_now) {
+ // LLD defaults to -zlazy
+ try argv.append("-znow");
+ }
+ if (!elf.z_relro) {
+ // LLD defaults to -zrelro
+ try argv.append("-znorelro");
+ }
+ if (elf.z_common_page_size) |size| {
+ try argv.append("-z");
+ try argv.append(try std.fmt.allocPrint(arena, "common-page-size={d}", .{size}));
+ }
+ if (elf.z_max_page_size) |size| {
+ try argv.append("-z");
+ try argv.append(try std.fmt.allocPrint(arena, "max-page-size={d}", .{size}));
+ }
+
+ if (getLDMOption(target)) |ldm| {
+ try argv.append("-m");
+ try argv.append(ldm);
+ }
+
+ if (link_mode == .static) {
+ if (target.cpu.arch.isArm()) {
+ try argv.append("-Bstatic");
+ } else {
+ try argv.append("-static");
+ }
+ } else if (switch (target.os.tag) {
+ else => is_dyn_lib,
+ .haiku => is_exe_or_dyn_lib,
+ }) {
+ try argv.append("-shared");
+ }
+
+ if (comp.config.pie and output_mode == .Exe) {
+ try argv.append("-pie");
+ }
+
+ if (is_exe_or_dyn_lib and target.os.tag == .netbsd) {
+ // Add options to produce shared objects with only 2 PT_LOAD segments.
+ // NetBSD expects 2 PT_LOAD segments in a shared object, otherwise
+ // ld.elf_so fails loading dynamic libraries with "not found" error.
+ // See https://github.com/ziglang/zig/issues/9109 .
+ try argv.append("--no-rosegment");
+ try argv.append("-znorelro");
+ }
+
+ try argv.append("-o");
+ try argv.append(full_out_path);
+
+ // csu prelude
+ const csu = try comp.getCrtPaths(arena);
+ if (csu.crt0) |p| try argv.append(try p.toString(arena));
+ if (csu.crti) |p| try argv.append(try p.toString(arena));
+ if (csu.crtbegin) |p| try argv.append(try p.toString(arena));
+
+ for (elf.rpath_list) |rpath| {
+ try argv.appendSlice(&.{ "-rpath", rpath });
+ }
+
+ for (elf.symbol_wrap_set) |symbol_name| {
+ try argv.appendSlice(&.{ "-wrap", symbol_name });
+ }
+
+ if (comp.config.link_libc) {
+ if (comp.libc_installation) |libc_installation| {
+ try argv.append("-L");
+ try argv.append(libc_installation.crt_dir.?);
+ }
+ }
+
+ if (have_dynamic_linker and
+ (comp.config.link_libc or comp.root_mod.resolved_target.is_explicit_dynamic_linker))
+ {
+ if (target.dynamic_linker.get()) |dynamic_linker| {
+ try argv.append("-dynamic-linker");
+ try argv.append(dynamic_linker);
+ }
+ }
+
+ if (is_dyn_lib) {
+ if (elf.soname) |soname| {
+ try argv.append("-soname");
+ try argv.append(soname);
+ }
+ if (elf.version_script) |version_script| {
+ try argv.append("-version-script");
+ try argv.append(version_script);
+ }
+ if (elf.allow_undefined_version) {
+ try argv.append("--undefined-version");
+ } else {
+ try argv.append("--no-undefined-version");
+ }
+ if (elf.enable_new_dtags) |enable_new_dtags| {
+ if (enable_new_dtags) {
+ try argv.append("--enable-new-dtags");
+ } else {
+ try argv.append("--disable-new-dtags");
+ }
+ }
+ }
+
+ // Positional arguments to the linker such as object files.
+ var whole_archive = false;
+
+ for (base.comp.link_inputs) |link_input| switch (link_input) {
+ .res => unreachable, // Windows-only
+ .dso => continue,
+ .object, .archive => |obj| {
+ if (obj.must_link and !whole_archive) {
+ try argv.append("-whole-archive");
+ whole_archive = true;
+ } else if (!obj.must_link and whole_archive) {
+ try argv.append("-no-whole-archive");
+ whole_archive = false;
+ }
+ try argv.append(try obj.path.toString(arena));
+ },
+ .dso_exact => |dso_exact| {
+ assert(dso_exact.name[0] == ':');
+ try argv.appendSlice(&.{ "-l", dso_exact.name });
+ },
+ };
+
+ if (whole_archive) {
+ try argv.append("-no-whole-archive");
+ whole_archive = false;
+ }
+
+ for (comp.c_object_table.keys()) |key| {
+ try argv.append(try key.status.success.object_path.toString(arena));
+ }
+
+ if (module_obj_path) |p| {
+ try argv.append(p);
+ }
+
+ if (comp.tsan_lib) |lib| {
+ assert(comp.config.any_sanitize_thread);
+ try argv.append(try lib.full_object_path.toString(arena));
+ }
+
+ if (comp.fuzzer_lib) |lib| {
+ assert(comp.config.any_fuzz);
+ try argv.append(try lib.full_object_path.toString(arena));
+ }
+
+ if (ubsan_rt_path) |p| {
+ try argv.append(try p.toString(arena));
+ }
+
+ // Shared libraries.
+ if (is_exe_or_dyn_lib) {
+ // Worst-case, we need an --as-needed argument for every lib, as well
+ // as one before and one after.
+ try argv.ensureUnusedCapacity(2 * base.comp.link_inputs.len + 2);
+ argv.appendAssumeCapacity("--as-needed");
+ var as_needed = true;
+
+ for (base.comp.link_inputs) |link_input| switch (link_input) {
+ .res => unreachable, // Windows-only
+ .object, .archive, .dso_exact => continue,
+ .dso => |dso| {
+ const lib_as_needed = !dso.needed;
+ switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) {
+ 0b00, 0b11 => {},
+ 0b01 => {
+ argv.appendAssumeCapacity("--no-as-needed");
+ as_needed = false;
+ },
+ 0b10 => {
+ argv.appendAssumeCapacity("--as-needed");
+ as_needed = true;
+ },
+ }
+
+ // By this time, we depend on these libs being dynamically linked
+ // libraries and not static libraries (the check for that needs to be earlier),
+ // but they could be full paths to .so files, in which case we
+ // want to avoid prepending "-l".
+ argv.appendAssumeCapacity(try dso.path.toString(arena));
+ },
+ };
+
+ if (!as_needed) {
+ argv.appendAssumeCapacity("--as-needed");
+ as_needed = true;
+ }
+
+ // 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));
+ }
+
+ // libc dep
+ diags.flags.missing_libc = false;
+ if (comp.config.link_libc) {
+ if (comp.libc_installation != null) {
+ const needs_grouping = link_mode == .static;
+ if (needs_grouping) try argv.append("--start-group");
+ try argv.appendSlice(target_util.libcFullLinkFlags(target));
+ if (needs_grouping) try argv.append("--end-group");
+ } else if (target.isGnuLibC()) {
+ for (glibc.libs) |lib| {
+ if (lib.removed_in) |rem_in| {
+ if (target.os.versionRange().gnuLibCVersion().?.order(rem_in) != .lt) continue;
+ }
+
+ const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{
+ comp.glibc_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
+ });
+ try argv.append(lib_path);
+ }
+ try argv.append(try comp.crtFileAsString(arena, "libc_nonshared.a"));
+ } else if (target.isMuslLibC()) {
+ try argv.append(try comp.crtFileAsString(arena, switch (link_mode) {
+ .static => "libc.a",
+ .dynamic => "libc.so",
+ }));
+ } else if (target.isFreeBSDLibC()) {
+ for (freebsd.libs) |lib| {
+ const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{
+ comp.freebsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
+ });
+ try argv.append(lib_path);
+ }
+ } else if (target.isNetBSDLibC()) {
+ for (netbsd.libs) |lib| {
+ const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{
+ comp.netbsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
+ });
+ try argv.append(lib_path);
+ }
+ } else {
+ diags.flags.missing_libc = true;
+ }
+
+ if (comp.zigc_static_lib) |zigc| {
+ try argv.append(try zigc.full_object_path.toString(arena));
+ }
+ }
+ }
+
+ // compiler-rt. Since compiler_rt exports symbols like `memset`, it needs
+ // to be after the shared libraries, so they are picked up from the shared
+ // libraries, not libcompiler_rt.
+ if (compiler_rt_path) |p| {
+ try argv.append(try p.toString(arena));
+ }
+
+ // crt postlude
+ if (csu.crtend) |p| try argv.append(try p.toString(arena));
+ if (csu.crtn) |p| try argv.append(try p.toString(arena));
+
+ if (base.allow_shlib_undefined) {
+ try argv.append("--allow-shlib-undefined");
+ }
+
+ switch (elf.compress_debug_sections) {
+ .none => {},
+ .zlib => try argv.append("--compress-debug-sections=zlib"),
+ .zstd => try argv.append("--compress-debug-sections=zstd"),
+ }
+
+ if (elf.bind_global_refs_locally) {
+ try argv.append("-Bsymbolic");
+ }
+
+ try spawnLld(comp, arena, argv.items);
+ }
+
+ if (!lld.disable_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.
+ std.Build.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.
+ base.lock = man.toOwnedLock();
+ }
+}
+fn getLDMOption(target: std.Target) ?[]const u8 {
+ // This should only return emulations understood by LLD's parseEmulation().
+ return switch (target.cpu.arch) {
+ .aarch64 => switch (target.os.tag) {
+ .linux => "aarch64linux",
+ else => "aarch64elf",
+ },
+ .aarch64_be => switch (target.os.tag) {
+ .linux => "aarch64linuxb",
+ else => "aarch64elfb",
+ },
+ .amdgcn => "elf64_amdgpu",
+ .arm, .thumb => switch (target.os.tag) {
+ .linux => "armelf_linux_eabi",
+ else => "armelf",
+ },
+ .armeb, .thumbeb => switch (target.os.tag) {
+ .linux => "armelfb_linux_eabi",
+ else => "armelfb",
+ },
+ .hexagon => "hexagonelf",
+ .loongarch32 => "elf32loongarch",
+ .loongarch64 => "elf64loongarch",
+ .mips => switch (target.os.tag) {
+ .freebsd => "elf32btsmip_fbsd",
+ else => "elf32btsmip",
+ },
+ .mipsel => switch (target.os.tag) {
+ .freebsd => "elf32ltsmip_fbsd",
+ else => "elf32ltsmip",
+ },
+ .mips64 => switch (target.os.tag) {
+ .freebsd => switch (target.abi) {
+ .gnuabin32, .muslabin32 => "elf32btsmipn32_fbsd",
+ else => "elf64btsmip_fbsd",
+ },
+ else => switch (target.abi) {
+ .gnuabin32, .muslabin32 => "elf32btsmipn32",
+ else => "elf64btsmip",
+ },
+ },
+ .mips64el => switch (target.os.tag) {
+ .freebsd => switch (target.abi) {
+ .gnuabin32, .muslabin32 => "elf32ltsmipn32_fbsd",
+ else => "elf64ltsmip_fbsd",
+ },
+ else => switch (target.abi) {
+ .gnuabin32, .muslabin32 => "elf32ltsmipn32",
+ else => "elf64ltsmip",
+ },
+ },
+ .msp430 => "msp430elf",
+ .powerpc => switch (target.os.tag) {
+ .freebsd => "elf32ppc_fbsd",
+ .linux => "elf32ppclinux",
+ else => "elf32ppc",
+ },
+ .powerpcle => switch (target.os.tag) {
+ .linux => "elf32lppclinux",
+ else => "elf32lppc",
+ },
+ .powerpc64 => "elf64ppc",
+ .powerpc64le => "elf64lppc",
+ .riscv32 => "elf32lriscv",
+ .riscv64 => "elf64lriscv",
+ .s390x => "elf64_s390",
+ .sparc64 => "elf64_sparc",
+ .x86 => switch (target.os.tag) {
+ .freebsd => "elf_i386_fbsd",
+ else => "elf_i386",
+ },
+ .x86_64 => switch (target.abi) {
+ .gnux32, .muslx32 => "elf32_x86_64",
+ else => "elf_x86_64",
+ },
+ else => null,
+ };
+}
+fn wasmLink(lld: *Lld, arena: Allocator) !void {
+ const comp = lld.base.comp;
+ const shared_memory = comp.config.shared_memory;
+ const export_memory = comp.config.export_memory;
+ const import_memory = comp.config.import_memory;
+ const target = comp.root_mod.resolved_target.result;
+ const base = &lld.base;
+ const wasm = &lld.ofmt.wasm;
+
+ const gpa = comp.gpa;
+
+ const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
+ const full_out_path = try directory.join(arena, &[_][]const u8{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) p: {
+ if (fs.path.dirname(full_out_path)) |dirname| {
+ break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
+ } else {
+ break :p base.zcu_object_sub_path.?;
+ }
+ } else null;
+
+ const is_obj = comp.config.output_mode == .Obj;
+ const compiler_rt_path: ?Cache.Path = blk: {
+ if (comp.compiler_rt_lib) |lib| break :blk lib.full_object_path;
+ if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path;
+ break :blk null;
+ };
+ const ubsan_rt_path: ?Cache.Path = blk: {
+ if (comp.ubsan_rt_lib) |lib| break :blk lib.full_object_path;
+ if (comp.ubsan_rt_obj) |obj| break :blk obj.full_object_path;
+ break :blk null;
+ };
+
+ const id_symlink_basename = "lld.id";
+
+ var man: Cache.Manifest = undefined;
+ defer if (!lld.disable_caching) man.deinit();
+
+ var digest: [Cache.hex_digest_len]u8 = undefined;
+
+ if (!lld.disable_caching) {
+ man = comp.cache_parent.obtain();
+
+ // We are about to obtain this lock, so here we give other processes a chance first.
+ 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);
+ }
+ try man.addOptionalFile(module_obj_path);
+ try man.addOptionalFilePath(compiler_rt_path);
+ try man.addOptionalFilePath(ubsan_rt_path);
+ man.hash.addOptionalBytes(wasm.entry_name);
+ man.hash.add(base.stack_size);
+ man.hash.add(base.build_id);
+ man.hash.add(import_memory);
+ man.hash.add(export_memory);
+ man.hash.add(wasm.import_table);
+ man.hash.add(wasm.export_table);
+ man.hash.addOptional(wasm.initial_memory);
+ man.hash.addOptional(wasm.max_memory);
+ man.hash.add(shared_memory);
+ man.hash.addOptional(wasm.global_base);
+ man.hash.addListOfBytes(wasm.export_symbol_names);
+ // strip does not need to go into the linker hash because it is part of the hash namespace
+
+ // 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("WASM 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("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
+ // Hot diggity dog! The output binary is already there.
+ base.lock = man.toOwnedLock();
+ return;
+ }
+ log.debug("WASM 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 (is_obj) {
+ // LLD's WASM 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 Cache.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 fs.Dir.copyFile(
+ the_object_path.root_dir.handle,
+ the_object_path.sub_path,
+ directory.handle,
+ 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 = "wasm-ld";
+ try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command });
+ try argv.append("--error-limit=0");
+
+ if (comp.config.lto != .none) {
+ switch (comp.root_mod.optimize_mode) {
+ .Debug => {},
+ .ReleaseSmall => try argv.append("-O2"),
+ .ReleaseFast, .ReleaseSafe => try argv.append("-O3"),
+ }
+ }
+
+ if (import_memory) {
+ try argv.append("--import-memory");
+ }
+
+ if (export_memory) {
+ try argv.append("--export-memory");
+ }
+
+ if (wasm.import_table) {
+ assert(!wasm.export_table);
+ try argv.append("--import-table");
+ }
+
+ if (wasm.export_table) {
+ assert(!wasm.import_table);
+ try argv.append("--export-table");
+ }
+
+ // For wasm-ld we only need to specify '--no-gc-sections' when the user explicitly
+ // specified it as garbage collection is enabled by default.
+ if (!base.gc_sections) {
+ try argv.append("--no-gc-sections");
+ }
+
+ if (comp.config.debug_format == .strip) {
+ try argv.append("-s");
+ }
+
+ if (wasm.initial_memory) |initial_memory| {
+ const arg = try std.fmt.allocPrint(arena, "--initial-memory={d}", .{initial_memory});
+ try argv.append(arg);
+ }
+
+ if (wasm.max_memory) |max_memory| {
+ const arg = try std.fmt.allocPrint(arena, "--max-memory={d}", .{max_memory});
+ try argv.append(arg);
+ }
+
+ if (shared_memory) {
+ try argv.append("--shared-memory");
+ }
+
+ if (wasm.global_base) |global_base| {
+ const arg = try std.fmt.allocPrint(arena, "--global-base={d}", .{global_base});
+ try argv.append(arg);
+ } else {
+ // We prepend it by default, so when a stack overflow happens the runtime will trap correctly,
+ // rather than silently overwrite all global declarations. See https://github.com/ziglang/zig/issues/4496
+ //
+ // The user can overwrite this behavior by setting the global-base
+ try argv.append("--stack-first");
+ }
+
+ // Users are allowed to specify which symbols they want to export to the wasm host.
+ for (wasm.export_symbol_names) |symbol_name| {
+ const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name});
+ try argv.append(arg);
+ }
+
+ if (comp.config.rdynamic) {
+ try argv.append("--export-dynamic");
+ }
+
+ if (wasm.entry_name) |entry_name| {
+ try argv.appendSlice(&.{ "--entry", entry_name });
+ } else {
+ try argv.append("--no-entry");
+ }
+
+ try argv.appendSlice(&.{
+ "-z",
+ try std.fmt.allocPrint(arena, "stack-size={d}", .{base.stack_size}),
+ });
+
+ switch (base.build_id) {
+ .none => try argv.append("--build-id=none"),
+ .fast, .uuid, .sha1 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{
+ @tagName(base.build_id),
+ })),
+ .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{
+ std.fmt.fmtSliceHexLower(hs.toSlice()),
+ })),
+ .md5 => {},
+ }
+
+ if (wasm.import_symbols) {
+ try argv.append("--allow-undefined");
+ }
+
+ if (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic) {
+ try argv.append("--shared");
+ }
+ if (comp.config.pie) {
+ try argv.append("--pie");
+ }
+
+ try argv.appendSlice(&.{ "-o", full_out_path });
+
+ if (target.cpu.arch == .wasm64) {
+ try argv.append("-mwasm64");
+ }
+
+ const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or
+ (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic);
+
+ if (comp.config.link_libc and is_exe_or_dyn_lib) {
+ if (target.os.tag == .wasi) {
+ for (comp.wasi_emulated_libs) |crt_file| {
+ try argv.append(try comp.crtFileAsString(
+ arena,
+ wasi_libc.emulatedLibCRFileLibName(crt_file),
+ ));
+ }
+
+ try argv.append(try comp.crtFileAsString(
+ arena,
+ wasi_libc.execModelCrtFileFullName(comp.config.wasi_exec_model),
+ ));
+ try argv.append(try comp.crtFileAsString(arena, "libc.a"));
+ }
+
+ if (comp.zigc_static_lib) |zigc| {
+ try argv.append(try zigc.full_object_path.toString(arena));
+ }
+
+ if (comp.config.link_libcpp) {
+ try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena));
+ try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena));
+ }
+ }
+
+ // Positional arguments to the linker such as object files.
+ var whole_archive = false;
+ for (comp.link_inputs) |link_input| switch (link_input) {
+ .object, .archive => |obj| {
+ if (obj.must_link and !whole_archive) {
+ try argv.append("-whole-archive");
+ whole_archive = true;
+ } else if (!obj.must_link and whole_archive) {
+ try argv.append("-no-whole-archive");
+ whole_archive = false;
+ }
+ try argv.append(try obj.path.toString(arena));
+ },
+ .dso => |dso| {
+ try argv.append(try dso.path.toString(arena));
+ },
+ .dso_exact => unreachable,
+ .res => unreachable,
+ };
+ if (whole_archive) {
+ try argv.append("-no-whole-archive");
+ whole_archive = false;
+ }
+
+ for (comp.c_object_table.keys()) |key| {
+ try argv.append(try key.status.success.object_path.toString(arena));
+ }
+ if (module_obj_path) |p| {
+ try argv.append(p);
+ }
+
+ if (compiler_rt_path) |p| {
+ try argv.append(try p.toString(arena));
+ }
+
+ if (ubsan_rt_path) |p| {
+ try argv.append(try p.toStringZ(arena));
+ }
+
+ try spawnLld(comp, arena, argv.items);
+
+ // Give +x to the .wasm file if it is an executable and the OS is WASI.
+ // Some systems may be configured to execute such binaries directly. Even if that
+ // is not the case, it means we will get "exec format error" when trying to run
+ // it, and then can react to that in the same way as trying to run an ELF file
+ // from a foreign CPU architecture.
+ if (fs.has_executable_bit and target.os.tag == .wasi and
+ comp.config.output_mode == .Exe)
+ {
+ // TODO: what's our strategy for reporting linker errors from this function?
+ // report a nice error here with the file path if it fails instead of
+ // just returning the error code.
+ // chmod does not interact with umask, so we use a conservative -rwxr--r-- here.
+ std.posix.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) {
+ error.OperationNotSupported => unreachable, // Not a symlink.
+ else => |e| return e,
+ };
+ }
+ }
+
+ if (!lld.disable_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 symlink: {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.
+ base.lock = man.toOwnedLock();
+ }
+}
+
+fn spawnLld(
+ comp: *Compilation,
+ arena: Allocator,
+ argv: []const []const u8,
+) !void {
+ if (comp.verbose_link) {
+ // Skip over our own name so that the LLD linker name is the first argv item.
+ Compilation.dump_argv(argv[1..]);
+ }
+
+ // If possible, we run LLD as a child process because it does not always
+ // behave properly as a library, unfortunately.
+ // https://github.com/ziglang/zig/issues/3825
+ if (!std.process.can_spawn) {
+ const exit_code = try lldMain(arena, argv, false);
+ if (exit_code == 0) return;
+ if (comp.clang_passthrough_mode) std.process.exit(exit_code);
+ return error.LinkFailure;
+ }
+
+ var stderr: []u8 = &.{};
+ defer comp.gpa.free(stderr);
+
+ var child = std.process.Child.init(argv, arena);
+ const term = (if (comp.clang_passthrough_mode) term: {
+ child.stdin_behavior = .Inherit;
+ child.stdout_behavior = .Inherit;
+ child.stderr_behavior = .Inherit;
+
+ break :term child.spawnAndWait();
+ } else term: {
+ child.stdin_behavior = .Ignore;
+ child.stdout_behavior = .Ignore;
+ child.stderr_behavior = .Pipe;
+
+ child.spawn() catch |err| break :term err;
+ stderr = try child.stderr.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(usize));
+ break :term child.wait();
+ }) catch |first_err| term: {
+ const err = switch (first_err) {
+ error.NameTooLong => err: {
+ const s = fs.path.sep_str;
+ const rand_int = std.crypto.random.int(u64);
+ const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp";
+
+ const rsp_file = try comp.dirs.local_cache.handle.createFileZ(rsp_path, .{});
+ defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err|
+ log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) });
+ {
+ defer rsp_file.close();
+ var rsp_buf = std.io.bufferedWriter(rsp_file.writer());
+ const rsp_writer = rsp_buf.writer();
+ for (argv[2..]) |arg| {
+ try rsp_writer.writeByte('"');
+ for (arg) |c| {
+ switch (c) {
+ '\"', '\\' => try rsp_writer.writeByte('\\'),
+ else => {},
+ }
+ try rsp_writer.writeByte(c);
+ }
+ try rsp_writer.writeByte('"');
+ try rsp_writer.writeByte('\n');
+ }
+ try rsp_buf.flush();
+ }
+
+ var rsp_child = std.process.Child.init(&.{ argv[0], argv[1], try std.fmt.allocPrint(
+ arena,
+ "@{s}",
+ .{try comp.dirs.local_cache.join(arena, &.{rsp_path})},
+ ) }, arena);
+ if (comp.clang_passthrough_mode) {
+ rsp_child.stdin_behavior = .Inherit;
+ rsp_child.stdout_behavior = .Inherit;
+ rsp_child.stderr_behavior = .Inherit;
+
+ break :term rsp_child.spawnAndWait() catch |err| break :err err;
+ } else {
+ rsp_child.stdin_behavior = .Ignore;
+ rsp_child.stdout_behavior = .Ignore;
+ rsp_child.stderr_behavior = .Pipe;
+
+ rsp_child.spawn() catch |err| break :err err;
+ stderr = try rsp_child.stderr.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(usize));
+ break :term rsp_child.wait() catch |err| break :err err;
+ }
+ },
+ else => first_err,
+ };
+ log.err("unable to spawn LLD {s}: {s}", .{ argv[0], @errorName(err) });
+ return error.UnableToSpawnSelf;
+ };
+
+ const diags = &comp.link_diags;
+ switch (term) {
+ .Exited => |code| if (code != 0) {
+ if (comp.clang_passthrough_mode) std.process.exit(code);
+ diags.lockAndParseLldStderr(argv[1], stderr);
+ return error.LinkFailure;
+ },
+ else => {
+ if (comp.clang_passthrough_mode) std.process.abort();
+ return diags.fail("{s} terminated with stderr:\n{s}", .{ argv[0], stderr });
+ },
+ }
+
+ if (stderr.len > 0) log.warn("unexpected LLD stderr:\n{s}", .{stderr});
+}
+
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const Cache = std.Build.Cache;
+const allocPrint = std.fmt.allocPrint;
+const assert = std.debug.assert;
+const fs = std.fs;
+const log = std.log.scoped(.link);
+const mem = std.mem;
+
+const Compilation = @import("../Compilation.zig");
+const Zcu = @import("../Zcu.zig");
+const dev = @import("../dev.zig");
+const freebsd = @import("../libs/freebsd.zig");
+const glibc = @import("../libs/glibc.zig");
+const netbsd = @import("../libs/netbsd.zig");
+const wasi_libc = @import("../libs/wasi_libc.zig");
+const link = @import("../link.zig");
+const lldMain = @import("../main.zig").lldMain;
+const target_util = @import("../target.zig");
+const trace = @import("../tracy.zig").trace;
+const Lld = @This();
src/link/MachO.zig
@@ -194,7 +194,6 @@ pub fn createEmpty(
.stack_size = options.stack_size orelse 16777216,
.allow_shlib_undefined = allow_shlib_undefined,
.file = null,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
.rpath_list = options.rpath_list,
@@ -227,7 +226,7 @@ pub fn createEmpty(
self.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
.truncate = true,
.read = true,
- .mode = link.File.determineMode(false, output_mode, link_mode),
+ .mode = link.File.determineMode(output_mode, link_mode),
});
// Append null file
@@ -341,15 +340,6 @@ pub fn flush(
arena: Allocator,
tid: Zcu.PerThread.Id,
prog_node: std.Progress.Node,
-) link.File.FlushError!void {
- try self.flushZcu(arena, tid, prog_node);
-}
-
-pub fn flushZcu(
- self: *MachO,
- arena: Allocator,
- tid: Zcu.PerThread.Id,
- prog_node: std.Progress.Node,
) link.File.FlushError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -373,7 +363,7 @@ pub fn flushZcu(
// --verbose-link
if (comp.verbose_link) try self.dumpArgv(comp);
- if (self.getZigObject()) |zo| try zo.flushZcu(self, tid);
+ if (self.getZigObject()) |zo| try zo.flush(self, tid);
if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path);
if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path);
@@ -617,7 +607,7 @@ pub fn flushZcu(
error.LinkFailure => return error.LinkFailure,
else => |e| return diags.fail("failed to calculate and write uuid: {s}", .{@errorName(e)}),
};
- if (self.getDebugSymbols()) |dsym| dsym.flushZcu(self) catch |err| switch (err) {
+ if (self.getDebugSymbols()) |dsym| dsym.flush(self) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => |e| return diags.fail("failed to get debug symbols: {s}", .{@errorName(e)}),
};
src/link/Plan9.zig
@@ -301,7 +301,6 @@ pub fn createEmpty(
.stack_size = options.stack_size orelse 16777216,
.allow_shlib_undefined = options.allow_shlib_undefined orelse false,
.file = null,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
.sixtyfour_bit = sixtyfour_bit,
@@ -494,7 +493,7 @@ fn updateFinish(self: *Plan9, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index
// write the symbol
// we already have the got index
const sym: aout.Sym = .{
- .value = undefined, // the value of stuff gets filled in in flushZcu
+ .value = undefined, // the value of stuff gets filled in in flush
.type = atom.type,
.name = try gpa.dupe(u8, nav.name.toSlice(ip)),
};
@@ -527,25 +526,6 @@ fn allocateGotIndex(self: *Plan9) usize {
}
}
-pub fn flush(
- self: *Plan9,
- arena: Allocator,
- tid: Zcu.PerThread.Id,
- prog_node: std.Progress.Node,
-) link.File.FlushError!void {
- const comp = self.base.comp;
- const diags = &comp.link_diags;
- const use_lld = build_options.have_llvm and comp.config.use_lld;
- assert(!use_lld);
-
- switch (link.File.effectiveOutputMode(use_lld, comp.config.output_mode)) {
- .Exe => {},
- .Obj => return diags.fail("writing plan9 object files unimplemented", .{}),
- .Lib => return diags.fail("writing plan9 lib files unimplemented", .{}),
- }
- return self.flushZcu(arena, tid, prog_node);
-}
-
pub fn changeLine(l: *std.ArrayList(u8), delta_line: i32) !void {
if (delta_line > 0 and delta_line < 65) {
const toappend = @as(u8, @intCast(delta_line));
@@ -586,7 +566,7 @@ fn atomCount(self: *Plan9) usize {
return data_nav_count + fn_nav_count + lazy_atom_count + extern_atom_count + uav_atom_count;
}
-pub fn flushZcu(
+pub fn flush(
self: *Plan9,
arena: Allocator,
/// TODO: stop using this
@@ -607,10 +587,16 @@ pub fn flushZcu(
const gpa = comp.gpa;
const target = comp.root_mod.resolved_target.result;
+ switch (comp.config.output_mode) {
+ .Exe => {},
+ .Obj => return diags.fail("writing plan9 object files unimplemented", .{}),
+ .Lib => return diags.fail("writing plan9 lib files unimplemented", .{}),
+ }
+
const sub_prog_node = prog_node.start("Flush Module", 0);
defer sub_prog_node.end();
- log.debug("flushZcu", .{});
+ log.debug("flush", .{});
defer assert(self.hdr.entry != 0x0);
@@ -1039,7 +1025,7 @@ pub fn getOrCreateAtomForLazySymbol(self: *Plan9, pt: Zcu.PerThread, lazy_sym: F
const atom = atom_ptr.*;
_ = try self.getAtomPtr(atom).getOrCreateSymbolTableEntry(self);
_ = self.getAtomPtr(atom).getOrCreateOffsetTableEntry(self);
- // anyerror needs to be deferred until flushZcu
+ // anyerror needs to be deferred until flush
if (lazy_sym.ty != .anyerror_type) try self.updateLazySymbolAtom(pt, lazy_sym, atom);
return atom;
}
@@ -1182,11 +1168,7 @@ pub fn open(
const file = try emit.root_dir.handle.createFile(emit.sub_path, .{
.read = true,
- .mode = link.File.determineMode(
- use_lld,
- comp.config.output_mode,
- comp.config.link_mode,
- ),
+ .mode = link.File.determineMode(comp.config.output_mode, comp.config.link_mode),
});
errdefer file.close();
self.base.file = file;
src/link/SpirV.zig
@@ -17,7 +17,7 @@
//! All regular functions.
// Because SPIR-V requires re-compilation anyway, and so hot swapping will not work
-// anyway, we simply generate all the code in flushZcu. This keeps
+// anyway, we simply generate all the code in flush. This keeps
// things considerably simpler.
const SpirV = @This();
@@ -83,7 +83,6 @@ pub fn createEmpty(
.stack_size = options.stack_size orelse 0,
.allow_shlib_undefined = options.allow_shlib_undefined orelse false,
.file = null,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
.object = codegen.Object.init(gpa, comp.getTarget()),
@@ -193,18 +192,14 @@ pub fn updateExports(
// TODO: Export regular functions, variables, etc using Linkage attributes.
}
-pub fn flush(self: *SpirV, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
- return self.flushZcu(arena, tid, prog_node);
-}
-
-pub fn flushZcu(
+pub fn flush(
self: *SpirV,
arena: Allocator,
tid: Zcu.PerThread.Id,
prog_node: std.Progress.Node,
) link.File.FlushError!void {
// The goal is to never use this because it's only needed if we need to
- // write to InternPool, but flushZcu is too late to be writing to the
+ // write to InternPool, but flush is too late to be writing to the
// InternPool.
_ = tid;
src/link/Wasm.zig
@@ -40,7 +40,6 @@ const Zcu = @import("../Zcu.zig");
const codegen = @import("../codegen.zig");
const dev = @import("../dev.zig");
const link = @import("../link.zig");
-const lldMain = @import("../main.zig").lldMain;
const trace = @import("../tracy.zig").trace;
const wasi_libc = @import("../libs/wasi_libc.zig");
const Value = @import("../Value.zig");
@@ -74,8 +73,6 @@ global_base: ?u64,
initial_memory: ?u64,
/// When defined, sets the maximum memory size of the memory.
max_memory: ?u64,
-/// When true, will import the function table from the host environment.
-import_table: bool,
/// When true, will export the function table to the host environment.
export_table: bool,
/// Output name of the file
@@ -2935,17 +2932,14 @@ pub fn createEmpty(
const target = comp.root_mod.resolved_target.result;
assert(target.ofmt == .wasm);
- const use_lld = build_options.have_llvm and comp.config.use_lld;
const use_llvm = comp.config.use_llvm;
const output_mode = comp.config.output_mode;
const wasi_exec_model = comp.config.wasi_exec_model;
- // If using LLD to link, this code should produce an object file so that it
- // can be passed to LLD.
// If using LLVM to generate the object file for the zig compilation unit,
// we need a place to put the object file so that it can be subsequently
// handled.
- const zcu_object_sub_path = if (!use_lld and !use_llvm)
+ const zcu_object_sub_path = if (!use_llvm)
null
else
try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path});
@@ -2970,13 +2964,11 @@ pub fn createEmpty(
},
.allow_shlib_undefined = options.allow_shlib_undefined orelse false,
.file = null,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
.name = undefined,
.string_table = .empty,
.string_bytes = .empty,
- .import_table = options.import_table,
.export_table = options.export_table,
.import_symbols = options.import_symbols,
.export_symbol_names = options.export_symbol_names,
@@ -3004,17 +2996,7 @@ pub fn createEmpty(
.named => |name| (try wasm.internString(name)).toOptional(),
};
- if (use_lld and (use_llvm or !comp.config.have_zcu)) {
- // LLVM emits the object file (if any); LLD links it into the final product.
- return wasm;
- }
-
- // What path should this Wasm linker code output to?
- // If using LLD to link, this code should produce an object file so that it
- // can be passed to LLD.
- const sub_path = if (use_lld) zcu_object_sub_path.? else emit.sub_path;
-
- wasm.base.file = try emit.root_dir.handle.createFile(sub_path, .{
+ wasm.base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
.truncate = true,
.read = true,
.mode = if (fs.has_executable_bit)
@@ -3025,7 +3007,7 @@ pub fn createEmpty(
else
0,
});
- wasm.name = sub_path;
+ wasm.name = emit.sub_path;
return wasm;
}
@@ -3367,21 +3349,6 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void {
}
}
-pub fn flush(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
- const comp = wasm.base.comp;
- const use_lld = build_options.have_llvm and comp.config.use_lld;
- const diags = &comp.link_diags;
-
- if (use_lld) {
- return wasm.linkWithLLD(arena, tid, prog_node) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.LinkFailure => return error.LinkFailure,
- else => |e| return diags.fail("failed to link with LLD: {s}", .{@errorName(e)}),
- };
- }
- return wasm.flushZcu(arena, tid, prog_node);
-}
-
pub fn prelink(wasm: *Wasm, prog_node: std.Progress.Node) link.File.FlushError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -3773,14 +3740,14 @@ fn markTable(wasm: *Wasm, i: ObjectTableIndex) link.File.FlushError!void {
try wasm.tables.put(wasm.base.comp.gpa, .fromObjectTable(i), {});
}
-pub fn flushZcu(
+pub fn flush(
wasm: *Wasm,
arena: Allocator,
tid: Zcu.PerThread.Id,
prog_node: std.Progress.Node,
) link.File.FlushError!void {
// The goal is to never use this because it's only needed if we need to
- // write to InternPool, but flushZcu is too late to be writing to the
+ // write to InternPool, but flush is too late to be writing to the
// InternPool.
_ = tid;
const comp = wasm.base.comp;
@@ -3832,436 +3799,6 @@ pub fn flushZcu(
};
}
-fn linkWithLLD(wasm: *Wasm, 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 = wasm.base.comp;
- const diags = &comp.link_diags;
- const shared_memory = comp.config.shared_memory;
- const export_memory = comp.config.export_memory;
- const import_memory = comp.config.import_memory;
- const target = comp.root_mod.resolved_target.result;
-
- const gpa = comp.gpa;
-
- const directory = wasm.base.emit.root_dir; // Just an alias to make it shorter to type.
- const full_out_path = try directory.join(arena, &[_][]const u8{wasm.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) |zcu| blk: {
- if (zcu.llvm_object == null) {
- try wasm.flushZcu(arena, tid, prog_node);
- } else {
- // `Compilation.flush` has already made LLVM emit this object file for us.
- }
-
- if (fs.path.dirname(full_out_path)) |dirname| {
- break :blk try fs.path.join(arena, &.{ dirname, wasm.base.zcu_object_sub_path.? });
- } else {
- break :blk wasm.base.zcu_object_sub_path.?;
- }
- } else null;
-
- const sub_prog_node = prog_node.start("LLD Link", 0);
- defer sub_prog_node.end();
-
- const is_obj = comp.config.output_mode == .Obj;
- const compiler_rt_path: ?Path = blk: {
- if (comp.compiler_rt_lib) |lib| break :blk lib.full_object_path;
- if (comp.compiler_rt_obj) |obj| break :blk obj.full_object_path;
- break :blk null;
- };
- const ubsan_rt_path: ?Path = blk: {
- if (comp.ubsan_rt_lib) |lib| break :blk lib.full_object_path;
- if (comp.ubsan_rt_obj) |obj| break :blk obj.full_object_path;
- break :blk null;
- };
-
- const id_symlink_basename = "lld.id";
-
- var man: Cache.Manifest = undefined;
- defer if (!wasm.base.disable_lld_caching) man.deinit();
-
- var digest: [Cache.hex_digest_len]u8 = undefined;
-
- if (!wasm.base.disable_lld_caching) {
- man = comp.cache_parent.obtain();
-
- // We are about to obtain this lock, so here we give other processes a chance first.
- wasm.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);
- }
- try man.addOptionalFile(module_obj_path);
- try man.addOptionalFilePath(compiler_rt_path);
- try man.addOptionalFilePath(ubsan_rt_path);
- man.hash.addOptionalBytes(wasm.entry_name.slice(wasm));
- man.hash.add(wasm.base.stack_size);
- man.hash.add(wasm.base.build_id);
- man.hash.add(import_memory);
- man.hash.add(export_memory);
- man.hash.add(wasm.import_table);
- man.hash.add(wasm.export_table);
- man.hash.addOptional(wasm.initial_memory);
- man.hash.addOptional(wasm.max_memory);
- man.hash.add(shared_memory);
- man.hash.addOptional(wasm.global_base);
- man.hash.addListOfBytes(wasm.export_symbol_names);
- // strip does not need to go into the linker hash because it is part of the hash namespace
-
- // 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("WASM 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("WASM LLD digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
- // Hot diggity dog! The output binary is already there.
- wasm.base.lock = man.toOwnedLock();
- return;
- }
- log.debug("WASM 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 (is_obj) {
- // LLD's WASM 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 fs.Dir.copyFile(
- the_object_path.root_dir.handle,
- the_object_path.sub_path,
- directory.handle,
- wasm.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 = "wasm-ld";
- try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command });
- try argv.append("--error-limit=0");
-
- if (comp.config.lto != .none) {
- switch (comp.root_mod.optimize_mode) {
- .Debug => {},
- .ReleaseSmall => try argv.append("-O2"),
- .ReleaseFast, .ReleaseSafe => try argv.append("-O3"),
- }
- }
-
- if (import_memory) {
- try argv.append("--import-memory");
- }
-
- if (export_memory) {
- try argv.append("--export-memory");
- }
-
- if (wasm.import_table) {
- assert(!wasm.export_table);
- try argv.append("--import-table");
- }
-
- if (wasm.export_table) {
- assert(!wasm.import_table);
- try argv.append("--export-table");
- }
-
- // For wasm-ld we only need to specify '--no-gc-sections' when the user explicitly
- // specified it as garbage collection is enabled by default.
- if (!wasm.base.gc_sections) {
- try argv.append("--no-gc-sections");
- }
-
- if (comp.config.debug_format == .strip) {
- try argv.append("-s");
- }
-
- if (wasm.initial_memory) |initial_memory| {
- const arg = try std.fmt.allocPrint(arena, "--initial-memory={d}", .{initial_memory});
- try argv.append(arg);
- }
-
- if (wasm.max_memory) |max_memory| {
- const arg = try std.fmt.allocPrint(arena, "--max-memory={d}", .{max_memory});
- try argv.append(arg);
- }
-
- if (shared_memory) {
- try argv.append("--shared-memory");
- }
-
- if (wasm.global_base) |global_base| {
- const arg = try std.fmt.allocPrint(arena, "--global-base={d}", .{global_base});
- try argv.append(arg);
- } else {
- // We prepend it by default, so when a stack overflow happens the runtime will trap correctly,
- // rather than silently overwrite all global declarations. See https://github.com/ziglang/zig/issues/4496
- //
- // The user can overwrite this behavior by setting the global-base
- try argv.append("--stack-first");
- }
-
- // Users are allowed to specify which symbols they want to export to the wasm host.
- for (wasm.export_symbol_names) |symbol_name| {
- const arg = try std.fmt.allocPrint(arena, "--export={s}", .{symbol_name});
- try argv.append(arg);
- }
-
- if (comp.config.rdynamic) {
- try argv.append("--export-dynamic");
- }
-
- if (wasm.entry_name.slice(wasm)) |entry_name| {
- try argv.appendSlice(&.{ "--entry", entry_name });
- } else {
- try argv.append("--no-entry");
- }
-
- try argv.appendSlice(&.{
- "-z",
- try std.fmt.allocPrint(arena, "stack-size={d}", .{wasm.base.stack_size}),
- });
-
- switch (wasm.base.build_id) {
- .none => try argv.append("--build-id=none"),
- .fast, .uuid, .sha1 => try argv.append(try std.fmt.allocPrint(arena, "--build-id={s}", .{
- @tagName(wasm.base.build_id),
- })),
- .hexstring => |hs| try argv.append(try std.fmt.allocPrint(arena, "--build-id=0x{s}", .{
- std.fmt.fmtSliceHexLower(hs.toSlice()),
- })),
- .md5 => {},
- }
-
- if (wasm.import_symbols) {
- try argv.append("--allow-undefined");
- }
-
- if (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic) {
- try argv.append("--shared");
- }
- if (comp.config.pie) {
- try argv.append("--pie");
- }
-
- try argv.appendSlice(&.{ "-o", full_out_path });
-
- if (target.cpu.arch == .wasm64) {
- try argv.append("-mwasm64");
- }
-
- const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or
- (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic);
-
- if (comp.config.link_libc and is_exe_or_dyn_lib) {
- if (target.os.tag == .wasi) {
- for (comp.wasi_emulated_libs) |crt_file| {
- try argv.append(try comp.crtFileAsString(
- arena,
- wasi_libc.emulatedLibCRFileLibName(crt_file),
- ));
- }
-
- try argv.append(try comp.crtFileAsString(
- arena,
- wasi_libc.execModelCrtFileFullName(comp.config.wasi_exec_model),
- ));
- try argv.append(try comp.crtFileAsString(arena, "libc.a"));
- }
-
- if (comp.zigc_static_lib) |zigc| {
- try argv.append(try zigc.full_object_path.toString(arena));
- }
-
- if (comp.config.link_libcpp) {
- try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena));
- try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena));
- }
- }
-
- // Positional arguments to the linker such as object files.
- var whole_archive = false;
- for (comp.link_inputs) |link_input| switch (link_input) {
- .object, .archive => |obj| {
- if (obj.must_link and !whole_archive) {
- try argv.append("-whole-archive");
- whole_archive = true;
- } else if (!obj.must_link and whole_archive) {
- try argv.append("-no-whole-archive");
- whole_archive = false;
- }
- try argv.append(try obj.path.toString(arena));
- },
- .dso => |dso| {
- try argv.append(try dso.path.toString(arena));
- },
- .dso_exact => unreachable,
- .res => unreachable,
- };
- if (whole_archive) {
- try argv.append("-no-whole-archive");
- whole_archive = false;
- }
-
- for (comp.c_object_table.keys()) |key| {
- try argv.append(try key.status.success.object_path.toString(arena));
- }
- if (module_obj_path) |p| {
- try argv.append(p);
- }
-
- if (compiler_rt_path) |p| {
- try argv.append(try p.toString(arena));
- }
-
- if (ubsan_rt_path) |p| {
- try argv.append(try p.toStringZ(arena));
- }
-
- if (comp.verbose_link) {
- // Skip over our own name so that the LLD linker name is the first argv item.
- Compilation.dump_argv(argv.items[1..]);
- }
-
- if (std.process.can_spawn) {
- // If possible, we run LLD as a child process because it does not always
- // behave properly as a library, unfortunately.
- // https://github.com/ziglang/zig/issues/3825
- var child = std.process.Child.init(argv.items, arena);
- if (comp.clang_passthrough_mode) {
- child.stdin_behavior = .Inherit;
- child.stdout_behavior = .Inherit;
- child.stderr_behavior = .Inherit;
-
- const term = child.spawnAndWait() catch |err| {
- log.err("failed to spawn (passthrough mode) LLD {s}: {s}", .{ argv.items[0], @errorName(err) });
- return error.UnableToSpawnWasm;
- };
- switch (term) {
- .Exited => |code| {
- if (code != 0) {
- std.process.exit(code);
- }
- },
- else => std.process.abort(),
- }
- } else {
- child.stdin_behavior = .Ignore;
- child.stdout_behavior = .Ignore;
- child.stderr_behavior = .Pipe;
-
- try child.spawn();
-
- const stderr = try child.stderr.?.reader().readAllAlloc(arena, std.math.maxInt(usize));
-
- const term = child.wait() catch |err| {
- log.err("failed to spawn LLD {s}: {s}", .{ argv.items[0], @errorName(err) });
- return error.UnableToSpawnWasm;
- };
-
- switch (term) {
- .Exited => |code| {
- if (code != 0) {
- diags.lockAndParseLldStderr(linker_command, stderr);
- return error.LinkFailure;
- }
- },
- else => {
- return diags.fail("{s} terminated with stderr:\n{s}", .{ argv.items[0], stderr });
- },
- }
-
- if (stderr.len != 0) {
- log.warn("unexpected LLD stderr:\n{s}", .{stderr});
- }
- }
- } else {
- const exit_code = try lldMain(arena, argv.items, false);
- if (exit_code != 0) {
- if (comp.clang_passthrough_mode) {
- std.process.exit(exit_code);
- } else {
- return diags.fail("{s} returned exit code {d}:\n{s}", .{ argv.items[0], exit_code });
- }
- }
- }
-
- // Give +x to the .wasm file if it is an executable and the OS is WASI.
- // Some systems may be configured to execute such binaries directly. Even if that
- // is not the case, it means we will get "exec format error" when trying to run
- // it, and then can react to that in the same way as trying to run an ELF file
- // from a foreign CPU architecture.
- if (fs.has_executable_bit and target.os.tag == .wasi and
- comp.config.output_mode == .Exe)
- {
- // TODO: what's our strategy for reporting linker errors from this function?
- // report a nice error here with the file path if it fails instead of
- // just returning the error code.
- // chmod does not interact with umask, so we use a conservative -rwxr--r-- here.
- std.posix.fchmodat(fs.cwd().fd, full_out_path, 0o744, 0) catch |err| switch (err) {
- error.OperationNotSupported => unreachable, // Not a symlink.
- else => |e| return e,
- };
- }
- }
-
- if (!wasm.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 symlink: {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.
- wasm.base.lock = man.toOwnedLock();
- }
-}
-
fn defaultEntrySymbolName(
preloaded_strings: *const PreloadedStrings,
wasi_exec_model: std.builtin.WasiExecModel,
src/link/Xcoff.zig
@@ -46,7 +46,6 @@ pub fn createEmpty(
.stack_size = options.stack_size orelse 0,
.allow_shlib_undefined = options.allow_shlib_undefined orelse false,
.file = null,
- .disable_lld_caching = options.disable_lld_caching,
.build_id = options.build_id,
},
};
@@ -105,10 +104,6 @@ pub fn updateExports(
}
pub fn flush(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
- return self.flushZcu(arena, tid, prog_node);
-}
-
-pub fn flushZcu(self: *Xcoff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
_ = self;
_ = arena;
_ = tid;
src/Compilation.zig
@@ -1592,9 +1592,9 @@ pub const CreateOptions = struct {
linker_tsaware: bool = false,
linker_nxcompat: bool = false,
linker_dynamicbase: bool = true,
- linker_compress_debug_sections: ?link.File.Elf.CompressDebugSections = null,
+ linker_compress_debug_sections: ?link.File.Lld.Elf.CompressDebugSections = null,
linker_module_definition_file: ?[]const u8 = null,
- linker_sort_section: ?link.File.Elf.SortSection = null,
+ linker_sort_section: ?link.File.Lld.Elf.SortSection = null,
major_subsystem_version: ?u16 = null,
minor_subsystem_version: ?u16 = null,
clang_passthrough_mode: bool = false,
@@ -1616,7 +1616,7 @@ pub const CreateOptions = struct {
/// building such dependencies themselves, this flag must be set to avoid
/// infinite recursion.
skip_linker_dependencies: bool = false,
- hash_style: link.File.Elf.HashStyle = .both,
+ hash_style: link.File.Lld.Elf.HashStyle = .both,
entry: Entry = .default,
force_undefined_symbols: std.StringArrayHashMapUnmanaged(void) = .empty,
stack_size: ?u64 = null,
src/link.zig
@@ -19,7 +19,6 @@ const Zcu = @import("Zcu.zig");
const InternPool = @import("InternPool.zig");
const Type = @import("Type.zig");
const Value = @import("Value.zig");
-const lldMain = @import("main.zig").lldMain;
const Package = @import("Package.zig");
const dev = @import("dev.zig");
const ThreadSafeQueue = @import("ThreadSafeQueue.zig").ThreadSafeQueue;
@@ -388,7 +387,6 @@ pub const File = struct {
/// When linking with LLD, this linker code will output an object file only at
/// this location, and then this path can be placed on the LLD linker line.
zcu_object_sub_path: ?[]const u8 = null,
- disable_lld_caching: bool,
gc_sections: bool,
print_gc_sections: bool,
build_id: std.zig.BuildId,
@@ -424,7 +422,7 @@ pub const File = struct {
tsaware: bool,
nxcompat: bool,
dynamicbase: bool,
- compress_debug_sections: Elf.CompressDebugSections,
+ compress_debug_sections: Lld.Elf.CompressDebugSections,
bind_global_refs_locally: bool,
import_symbols: bool,
import_table: bool,
@@ -436,8 +434,8 @@ pub const File = struct {
global_base: ?u64,
build_id: std.zig.BuildId,
disable_lld_caching: bool,
- hash_style: Elf.HashStyle,
- sort_section: ?Elf.SortSection,
+ hash_style: Lld.Elf.HashStyle,
+ sort_section: ?Lld.Elf.SortSection,
major_subsystem_version: ?u16,
minor_subsystem_version: ?u16,
gc_sections: ?bool,
@@ -521,12 +519,20 @@ pub const File = struct {
emit: Path,
options: OpenOptions,
) !*File {
+ if (comp.config.use_lld) {
+ dev.check(.lld_linker);
+ assert(comp.zcu == null or comp.config.use_llvm);
+ // LLD does not support incremental linking.
+ const lld: *Lld = try .createEmpty(arena, comp, emit, options);
+ return &lld.base;
+ }
switch (Tag.fromObjectFormat(comp.root_mod.resolved_target.result.ofmt)) {
inline else => |tag| {
dev.check(tag.devFeature());
const ptr = try tag.Type().open(arena, comp, emit, options);
return &ptr.base;
},
+ .lld => unreachable, // not known from ofmt
}
}
@@ -536,12 +542,19 @@ pub const File = struct {
emit: Path,
options: OpenOptions,
) !*File {
+ if (comp.config.use_lld) {
+ dev.check(.lld_linker);
+ assert(comp.zcu == null or comp.config.use_llvm);
+ const lld: *Lld = try .createEmpty(arena, comp, emit, options);
+ return &lld.base;
+ }
switch (Tag.fromObjectFormat(comp.root_mod.resolved_target.result.ofmt)) {
inline else => |tag| {
dev.check(tag.devFeature());
const ptr = try tag.Type().createEmpty(arena, comp, emit, options);
return &ptr.base;
},
+ .lld => unreachable, // not known from ofmt
}
}
@@ -554,6 +567,7 @@ pub const File = struct {
const comp = base.comp;
const gpa = comp.gpa;
switch (base.tag) {
+ .lld => assert(base.file == null),
.coff, .elf, .macho, .plan9, .wasm, .goff, .xcoff => {
if (base.file != null) return;
dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker });
@@ -586,13 +600,12 @@ pub const File = struct {
}
}
}
- const use_lld = build_options.have_llvm and comp.config.use_lld;
const output_mode = comp.config.output_mode;
const link_mode = comp.config.link_mode;
base.file = try emit.root_dir.handle.createFile(emit.sub_path, .{
.truncate = false,
.read = true,
- .mode = determineMode(use_lld, output_mode, link_mode),
+ .mode = determineMode(output_mode, link_mode),
});
},
.c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }),
@@ -618,7 +631,6 @@ pub const File = struct {
const comp = base.comp;
const output_mode = comp.config.output_mode;
const link_mode = comp.config.link_mode;
- const use_lld = build_options.have_llvm and comp.config.use_lld;
switch (output_mode) {
.Obj => return,
@@ -629,13 +641,9 @@ pub const File = struct {
.Exe => {},
}
switch (base.tag) {
+ .lld => assert(base.file == null),
.elf => if (base.file) |f| {
dev.check(.elf_linker);
- if (base.zcu_object_sub_path != null and use_lld) {
- // The file we have open is not the final file that we want to
- // make executable, so we don't have to close it.
- return;
- }
f.close();
base.file = null;
@@ -650,11 +658,6 @@ pub const File = struct {
},
.coff, .macho, .plan9, .wasm, .goff, .xcoff => if (base.file) |f| {
dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker, .goff_linker, .xcoff_linker });
- if (base.zcu_object_sub_path != null) {
- // The file we have open is not the final file that we want to
- // make executable, so we don't have to close it.
- return;
- }
f.close();
base.file = null;
@@ -692,6 +695,7 @@ pub const File = struct {
pub fn getGlobalSymbol(base: *File, name: []const u8, lib_name: ?[]const u8) UpdateNavError!u32 {
log.debug("getGlobalSymbol '{s}' (expected in '{?s}')", .{ name, lib_name });
switch (base.tag) {
+ .lld => unreachable,
.plan9 => unreachable,
.spirv => unreachable,
.c => unreachable,
@@ -709,6 +713,7 @@ pub const File = struct {
const nav = pt.zcu.intern_pool.getNav(nav_index);
assert(nav.status == .fully_resolved);
switch (base.tag) {
+ .lld => unreachable,
inline else => |tag| {
dev.check(tag.devFeature());
return @as(*tag.Type(), @fieldParentPtr("base", base)).updateNav(pt, nav_index);
@@ -726,6 +731,7 @@ pub const File = struct {
fn updateContainerType(base: *File, pt: Zcu.PerThread, ty: InternPool.Index) UpdateContainerTypeError!void {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
+ .lld => unreachable,
else => {},
inline .elf => |tag| {
dev.check(tag.devFeature());
@@ -746,6 +752,7 @@ pub const File = struct {
) UpdateNavError!void {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
+ .lld => unreachable,
inline else => |tag| {
dev.check(tag.devFeature());
return @as(*tag.Type(), @fieldParentPtr("base", base)).updateFunc(pt, func_index, air, liveness);
@@ -772,6 +779,7 @@ pub const File = struct {
}
switch (base.tag) {
+ .lld => unreachable,
.spirv => {},
.goff, .xcoff => {},
inline else => |tag| {
@@ -811,8 +819,7 @@ pub const File = struct {
OutOfMemory,
};
- /// Commit pending changes and write headers. Takes into account final output mode
- /// and `use_lld`, not only `effectiveOutputMode`.
+ /// Commit pending changes and write headers. Takes into account final output mode.
/// `arena` has the lifetime of the call to `Compilation.update`.
pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void {
const comp = base.comp;
@@ -834,15 +841,7 @@ pub const File = struct {
};
return;
}
-
assert(base.post_prelink);
-
- const use_lld = build_options.have_llvm and comp.config.use_lld;
- const output_mode = comp.config.output_mode;
- const link_mode = comp.config.link_mode;
- if (use_lld and output_mode == .Lib and link_mode == .static) {
- return base.linkAsArchive(arena, tid, prog_node);
- }
switch (base.tag) {
inline else => |tag| {
dev.check(tag.devFeature());
@@ -851,19 +850,6 @@ pub const File = struct {
}
}
- /// Commit pending changes and write headers. Works based on `effectiveOutputMode`
- /// rather than final output mode.
- /// Never called when LLVM is codegenning the ZCU.
- fn flushZcu(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void {
- assert(base.comp.zcu.?.llvm_object == null);
- switch (base.tag) {
- inline else => |tag| {
- dev.check(tag.devFeature());
- return @as(*tag.Type(), @fieldParentPtr("base", base)).flushZcu(arena, tid, prog_node);
- },
- }
- }
-
pub const UpdateExportsError = error{
OutOfMemory,
AnalysisFail,
@@ -882,6 +868,7 @@ pub const File = struct {
) UpdateExportsError!void {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
+ .lld => unreachable,
inline else => |tag| {
dev.check(tag.devFeature());
return @as(*tag.Type(), @fieldParentPtr("base", base)).updateExports(pt, exported, export_indices);
@@ -911,6 +898,7 @@ pub const File = struct {
pub fn getNavVAddr(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: RelocInfo) !u64 {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
+ .lld => unreachable,
.c => unreachable,
.spirv => unreachable,
.wasm => unreachable,
@@ -932,6 +920,7 @@ pub const File = struct {
) !codegen.GenResult {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
+ .lld => unreachable,
.c => unreachable,
.spirv => unreachable,
.wasm => unreachable,
@@ -947,6 +936,7 @@ pub const File = struct {
pub fn getUavVAddr(base: *File, decl_val: InternPool.Index, reloc_info: RelocInfo) !u64 {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
+ .lld => unreachable,
.c => unreachable,
.spirv => unreachable,
.wasm => unreachable,
@@ -966,6 +956,8 @@ pub const File = struct {
) void {
assert(base.comp.zcu.?.llvm_object == null);
switch (base.tag) {
+ .lld => unreachable,
+
.plan9,
.spirv,
.goff,
@@ -981,6 +973,7 @@ pub const File = struct {
/// Opens a path as an object file and parses it into the linker.
fn openLoadObject(base: *File, path: Path) anyerror!void {
+ if (base.tag == .lld) return;
const diags = &base.comp.link_diags;
const input = try openObjectInput(diags, path);
errdefer input.object.file.close();
@@ -990,6 +983,7 @@ pub const File = struct {
/// Opens a path as a static library and parses it into the linker.
/// If `query` is non-null, allows GNU ld scripts.
fn openLoadArchive(base: *File, path: Path, opt_query: ?UnresolvedInput.Query) anyerror!void {
+ if (base.tag == .lld) return;
if (opt_query) |query| {
const archive = try openObject(path, query.must_link, query.hidden);
errdefer archive.file.close();
@@ -1012,6 +1006,7 @@ pub const File = struct {
/// Opens a path as a shared library and parses it into the linker.
/// Handles GNU ld scripts.
fn openLoadDso(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void {
+ if (base.tag == .lld) return;
const dso = try openDso(path, query.needed, query.weak, query.reexport);
errdefer dso.file.close();
loadInput(base, .{ .dso = dso }) catch |err| switch (err) {
@@ -1064,8 +1059,7 @@ pub const File = struct {
}
pub fn loadInput(base: *File, input: Input) anyerror!void {
- const use_lld = build_options.have_llvm and base.comp.config.use_lld;
- if (use_lld) return;
+ if (base.tag == .lld) return;
switch (base.tag) {
inline .elf, .wasm => |tag| {
dev.check(tag.devFeature());
@@ -1079,8 +1073,6 @@ pub const File = struct {
/// this, `loadInput` will not be called anymore.
pub fn prelink(base: *File, prog_node: std.Progress.Node) FlushError!void {
assert(!base.post_prelink);
- const use_lld = build_options.have_llvm and base.comp.config.use_lld;
- if (use_lld) return;
// In this case, an object file is created by the LLVM backend, so
// there is no prelink phase. The Zig code is linked as a standard
@@ -1096,170 +1088,6 @@ pub const File = struct {
}
}
- fn linkAsArchive(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) FlushError!void {
- dev.check(.lld_linker);
-
- const tracy = trace(@src());
- defer tracy.end();
-
- const comp = base.comp;
- const diags = &comp.link_diags;
-
- return linkAsArchiveInner(base, arena, tid, prog_node) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.LinkFailure => return error.LinkFailure,
- else => |e| return diags.fail("failed to link as archive: {s}", .{@errorName(e)}),
- };
- }
-
- fn linkAsArchiveInner(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) !void {
- const comp = base.comp;
-
- const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
- const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
- const full_out_path_z = try arena.dupeZ(u8, full_out_path);
- const opt_zcu = comp.zcu;
-
- // 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 zcu_obj_path: ?[]const u8 = if (opt_zcu) |zcu| blk: {
- if (zcu.llvm_object == null) {
- try base.flushZcu(arena, tid, prog_node);
- } else {
- // `Compilation.flush` has already made LLVM emit this object file for us.
- }
- const dirname = fs.path.dirname(full_out_path_z) orelse ".";
- break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
- } else null;
-
- log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"});
-
- const compiler_rt_path: ?Path = if (comp.compiler_rt_strat == .obj)
- comp.compiler_rt_obj.?.full_object_path
- else
- null;
-
- const ubsan_rt_path: ?Path = if (comp.ubsan_rt_strat == .obj)
- comp.ubsan_rt_obj.?.full_object_path
- else
- null;
-
- // This function follows the same pattern as link.Elf.linkWithLLD so if you want some
- // insight as to what's going on here you can read that function body which is more
- // well-commented.
-
- const id_symlink_basename = "llvm-ar.id";
-
- var man: Cache.Manifest = undefined;
- defer if (!base.disable_lld_caching) man.deinit();
-
- const link_inputs = comp.link_inputs;
-
- var digest: [Cache.hex_digest_len]u8 = undefined;
-
- if (!base.disable_lld_caching) {
- man = comp.cache_parent.obtain();
-
- // We are about to obtain this lock, so here we give other processes a chance first.
- base.releaseLock();
-
- try hashInputs(&man, 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(zcu_obj_path);
- try man.addOptionalFilePath(compiler_rt_path);
- try man.addOptionalFilePath(ubsan_rt_path);
-
- // 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| b: {
- log.debug("archive new_digest={s} readFile error: {s}", .{ std.fmt.fmtSliceHexLower(&digest), @errorName(err) });
- break :b prev_digest_buf[0..0];
- };
- if (mem.eql(u8, prev_digest, &digest)) {
- log.debug("archive digest={s} match - skipping invocation", .{std.fmt.fmtSliceHexLower(&digest)});
- base.lock = man.toOwnedLock();
- return;
- }
-
- // 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,
- };
- }
-
- var object_files: std.ArrayListUnmanaged([*:0]const u8) = .empty;
-
- try object_files.ensureUnusedCapacity(arena, link_inputs.len);
- for (link_inputs) |input| {
- object_files.appendAssumeCapacity(try input.path().?.toStringZ(arena));
- }
-
- try object_files.ensureUnusedCapacity(arena, comp.c_object_table.count() +
- comp.win32_resource_table.count() + 2);
-
- for (comp.c_object_table.keys()) |key| {
- object_files.appendAssumeCapacity(try key.status.success.object_path.toStringZ(arena));
- }
- for (comp.win32_resource_table.keys()) |key| {
- object_files.appendAssumeCapacity(try arena.dupeZ(u8, key.status.success.res_path));
- }
- if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p));
- if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
- if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
-
- if (comp.verbose_link) {
- std.debug.print("ar rcs {s}", .{full_out_path_z});
- for (object_files.items) |arg| {
- std.debug.print(" {s}", .{arg});
- }
- std.debug.print("\n", .{});
- }
-
- const llvm_bindings = @import("codegen/llvm/bindings.zig");
- const llvm = @import("codegen/llvm.zig");
- const target = comp.root_mod.resolved_target.result;
- llvm.initializeLLVMTarget(target.cpu.arch);
- const bad = llvm_bindings.WriteArchive(
- full_out_path_z,
- object_files.items.ptr,
- object_files.items.len,
- switch (target.os.tag) {
- .aix => .AIXBIG,
- .windows => .COFF,
- else => if (target.os.tag.isDarwin()) .DARWIN else .GNU,
- },
- );
- if (bad) return error.UnableToWriteArchive;
-
- if (!base.disable_lld_caching) {
- Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| {
- log.warn("failed to save archive hash digest file: {s}", .{@errorName(err)});
- };
-
- if (man.have_exclusive_lock) {
- man.writeManifest() catch |err| {
- log.warn("failed to write cache manifest when archiving: {s}", .{@errorName(err)});
- };
- }
-
- base.lock = man.toOwnedLock();
- }
- }
-
pub const Tag = enum {
coff,
elf,
@@ -1270,6 +1098,7 @@ pub const File = struct {
plan9,
goff,
xcoff,
+ lld,
pub fn Type(comptime tag: Tag) type {
return switch (tag) {
@@ -1282,10 +1111,11 @@ pub const File = struct {
.plan9 => Plan9,
.goff => Goff,
.xcoff => Xcoff,
+ .lld => Lld,
};
}
- pub fn fromObjectFormat(ofmt: std.Target.ObjectFormat) Tag {
+ fn fromObjectFormat(ofmt: std.Target.ObjectFormat) Tag {
return switch (ofmt) {
.coff => .coff,
.elf => .elf,
@@ -1313,15 +1143,7 @@ pub const File = struct {
ty: InternPool.Index,
};
- pub fn effectiveOutputMode(
- use_lld: bool,
- output_mode: std.builtin.OutputMode,
- ) std.builtin.OutputMode {
- return if (use_lld) .Obj else output_mode;
- }
-
pub fn determineMode(
- use_lld: bool,
output_mode: std.builtin.OutputMode,
link_mode: std.builtin.LinkMode,
) fs.File.Mode {
@@ -1330,7 +1152,7 @@ pub const File = struct {
// more leniently. As another data point, C's fopen seems to open files with the
// 666 mode.
const executable_mode = if (builtin.target.os.tag == .windows) 0 else 0o777;
- switch (effectiveOutputMode(use_lld, output_mode)) {
+ switch (output_mode) {
.Lib => return switch (link_mode) {
.dynamic => executable_mode,
.static => fs.File.default_mode,
@@ -1378,6 +1200,7 @@ pub const File = struct {
return base.comp.zcu.?.codegenFail(nav_index, format, args);
}
+ pub const Lld = @import("link/Lld.zig");
pub const C = @import("link/C.zig");
pub const Coff = @import("link/Coff.zig");
pub const Plan9 = @import("link/Plan9.zig");
@@ -1685,116 +1508,6 @@ pub fn doTask(comp: *Compilation, tid: usize, task: Task) void {
}
}
-pub fn spawnLld(
- comp: *Compilation,
- arena: Allocator,
- argv: []const []const u8,
-) !void {
- if (comp.verbose_link) {
- // Skip over our own name so that the LLD linker name is the first argv item.
- Compilation.dump_argv(argv[1..]);
- }
-
- // If possible, we run LLD as a child process because it does not always
- // behave properly as a library, unfortunately.
- // https://github.com/ziglang/zig/issues/3825
- if (!std.process.can_spawn) {
- const exit_code = try lldMain(arena, argv, false);
- if (exit_code == 0) return;
- if (comp.clang_passthrough_mode) std.process.exit(exit_code);
- return error.LinkFailure;
- }
-
- var stderr: []u8 = &.{};
- defer comp.gpa.free(stderr);
-
- var child = std.process.Child.init(argv, arena);
- const term = (if (comp.clang_passthrough_mode) term: {
- child.stdin_behavior = .Inherit;
- child.stdout_behavior = .Inherit;
- child.stderr_behavior = .Inherit;
-
- break :term child.spawnAndWait();
- } else term: {
- child.stdin_behavior = .Ignore;
- child.stdout_behavior = .Ignore;
- child.stderr_behavior = .Pipe;
-
- child.spawn() catch |err| break :term err;
- stderr = try child.stderr.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(usize));
- break :term child.wait();
- }) catch |first_err| term: {
- const err = switch (first_err) {
- error.NameTooLong => err: {
- const s = fs.path.sep_str;
- const rand_int = std.crypto.random.int(u64);
- const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp";
-
- const rsp_file = try comp.dirs.local_cache.handle.createFileZ(rsp_path, .{});
- defer comp.dirs.local_cache.handle.deleteFileZ(rsp_path) catch |err|
- log.warn("failed to delete response file {s}: {s}", .{ rsp_path, @errorName(err) });
- {
- defer rsp_file.close();
- var rsp_buf = std.io.bufferedWriter(rsp_file.writer());
- const rsp_writer = rsp_buf.writer();
- for (argv[2..]) |arg| {
- try rsp_writer.writeByte('"');
- for (arg) |c| {
- switch (c) {
- '\"', '\\' => try rsp_writer.writeByte('\\'),
- else => {},
- }
- try rsp_writer.writeByte(c);
- }
- try rsp_writer.writeByte('"');
- try rsp_writer.writeByte('\n');
- }
- try rsp_buf.flush();
- }
-
- var rsp_child = std.process.Child.init(&.{ argv[0], argv[1], try std.fmt.allocPrint(
- arena,
- "@{s}",
- .{try comp.dirs.local_cache.join(arena, &.{rsp_path})},
- ) }, arena);
- if (comp.clang_passthrough_mode) {
- rsp_child.stdin_behavior = .Inherit;
- rsp_child.stdout_behavior = .Inherit;
- rsp_child.stderr_behavior = .Inherit;
-
- break :term rsp_child.spawnAndWait() catch |err| break :err err;
- } else {
- rsp_child.stdin_behavior = .Ignore;
- rsp_child.stdout_behavior = .Ignore;
- rsp_child.stderr_behavior = .Pipe;
-
- rsp_child.spawn() catch |err| break :err err;
- stderr = try rsp_child.stderr.?.reader().readAllAlloc(comp.gpa, std.math.maxInt(usize));
- break :term rsp_child.wait() catch |err| break :err err;
- }
- },
- else => first_err,
- };
- log.err("unable to spawn LLD {s}: {s}", .{ argv[0], @errorName(err) });
- return error.UnableToSpawnSelf;
- };
-
- const diags = &comp.link_diags;
- switch (term) {
- .Exited => |code| if (code != 0) {
- if (comp.clang_passthrough_mode) std.process.exit(code);
- diags.lockAndParseLldStderr(argv[1], stderr);
- return error.LinkFailure;
- },
- else => {
- if (comp.clang_passthrough_mode) std.process.abort();
- return diags.fail("{s} terminated with stderr:\n{s}", .{ argv[0], stderr });
- },
- }
-
- if (stderr.len > 0) log.warn("unexpected LLD stderr:\n{s}", .{stderr});
-}
-
/// Provided by the CLI, processed into `LinkInput` instances at the start of
/// the compilation pipeline.
pub const UnresolvedInput = union(enum) {
src/main.zig
@@ -867,9 +867,9 @@ fn buildOutputType(
var linker_allow_undefined_version: bool = false;
var linker_enable_new_dtags: ?bool = null;
var disable_c_depfile = false;
- var linker_sort_section: ?link.File.Elf.SortSection = null;
+ var linker_sort_section: ?link.File.Lld.Elf.SortSection = null;
var linker_gc_sections: ?bool = null;
- var linker_compress_debug_sections: ?link.File.Elf.CompressDebugSections = null;
+ var linker_compress_debug_sections: ?link.File.Lld.Elf.CompressDebugSections = null;
var linker_allow_shlib_undefined: ?bool = null;
var allow_so_scripts: bool = false;
var linker_bind_global_refs_locally: ?bool = null;
@@ -921,7 +921,7 @@ fn buildOutputType(
var debug_compiler_runtime_libs = false;
var opt_incremental: ?bool = null;
var install_name: ?[]const u8 = null;
- var hash_style: link.File.Elf.HashStyle = .both;
+ var hash_style: link.File.Lld.Elf.HashStyle = .both;
var entitlements: ?[]const u8 = null;
var pagezero_size: ?u64 = null;
var lib_search_strategy: link.UnresolvedInput.SearchStrategy = .paths_first;
@@ -1196,11 +1196,11 @@ fn buildOutputType(
install_name = args_iter.nextOrFatal();
} else if (mem.startsWith(u8, arg, "--compress-debug-sections=")) {
const param = arg["--compress-debug-sections=".len..];
- linker_compress_debug_sections = std.meta.stringToEnum(link.File.Elf.CompressDebugSections, param) orelse {
+ linker_compress_debug_sections = std.meta.stringToEnum(link.File.Lld.Elf.CompressDebugSections, param) orelse {
fatal("expected --compress-debug-sections=[none|zlib|zstd], found '{s}'", .{param});
};
} else if (mem.eql(u8, arg, "--compress-debug-sections")) {
- linker_compress_debug_sections = link.File.Elf.CompressDebugSections.zlib;
+ linker_compress_debug_sections = link.File.Lld.Elf.CompressDebugSections.zlib;
} else if (mem.eql(u8, arg, "-pagezero_size")) {
const next_arg = args_iter.nextOrFatal();
pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| {
@@ -2368,7 +2368,7 @@ fn buildOutputType(
if (it.only_arg.len == 0) {
linker_compress_debug_sections = .zlib;
} else {
- linker_compress_debug_sections = std.meta.stringToEnum(link.File.Elf.CompressDebugSections, it.only_arg) orelse {
+ linker_compress_debug_sections = std.meta.stringToEnum(link.File.Lld.Elf.CompressDebugSections, it.only_arg) orelse {
fatal("expected [none|zlib|zstd] after --compress-debug-sections, found '{s}'", .{it.only_arg});
};
}
@@ -2505,7 +2505,7 @@ fn buildOutputType(
linker_print_map = true;
} else if (mem.eql(u8, arg, "--sort-section")) {
const arg1 = linker_args_it.nextOrFatal();
- linker_sort_section = std.meta.stringToEnum(link.File.Elf.SortSection, arg1) orelse {
+ linker_sort_section = std.meta.stringToEnum(link.File.Lld.Elf.SortSection, arg1) orelse {
fatal("expected [name|alignment] after --sort-section, found '{s}'", .{arg1});
};
} else if (mem.eql(u8, arg, "--allow-shlib-undefined") or
@@ -2551,7 +2551,7 @@ fn buildOutputType(
try linker_export_symbol_names.append(arena, linker_args_it.nextOrFatal());
} else if (mem.eql(u8, arg, "--compress-debug-sections")) {
const arg1 = linker_args_it.nextOrFatal();
- linker_compress_debug_sections = std.meta.stringToEnum(link.File.Elf.CompressDebugSections, arg1) orelse {
+ linker_compress_debug_sections = std.meta.stringToEnum(link.File.Lld.Elf.CompressDebugSections, arg1) orelse {
fatal("expected [none|zlib|zstd] after --compress-debug-sections, found '{s}'", .{arg1});
};
} else if (mem.startsWith(u8, arg, "-z")) {
@@ -2764,7 +2764,7 @@ fn buildOutputType(
mem.eql(u8, arg, "--hash-style"))
{
const next_arg = linker_args_it.nextOrFatal();
- hash_style = std.meta.stringToEnum(link.File.Elf.HashStyle, next_arg) orelse {
+ hash_style = std.meta.stringToEnum(link.File.Lld.Elf.HashStyle, next_arg) orelse {
fatal("expected [sysv|gnu|both] after --hash-style, found '{s}'", .{
next_arg,
});