aboutsummaryrefslogtreecommitdiff
path: root/src/link.zig
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2025-05-28 09:30:31 +0100
committermlugg <mlugg@mlugg.co.uk>2025-06-12 13:55:39 +0100
commit2fb6f5c1adcd764372ad28ed4014fdaf558da778 (patch)
treec38d1e129c3ee15c29198aacab1c2d76df857cf5 /src/link.zig
parent3743c3e39c6bb645db7403fd446953d43ac7c7dc (diff)
downloadzig-2fb6f5c1adcd764372ad28ed4014fdaf558da778.tar.gz
zig-2fb6f5c1adcd764372ad28ed4014fdaf558da778.zip
link: divorce LLD from the self-hosted linkers
Similar to the previous commit, this commit untangles LLD integration from the self-hosted linkers. Despite the big network of functions which were involved, it turns out what was going on here is quite simple. The LLD linking logic is actually very self-contained; it requires a few flags from the `link.File.OpenOptions`, but that's really about it. We don't need any of the mutable state on `Elf`/`Coff`/`Wasm`, for instance. There was some legacy code trying to handle support for using self-hosted codegen with LLD, but that's not a supported use case, so I've just stripped it out. For now, I've just pasted the logic for linking the 3 targets we currently support using LLD for into this new linker implementation, `link.Lld`; however, it's almost certainly possible to combine some of the logic and simplify this file a bit. But to be honest, it's not actually that bad right now. This commit ends up eliminating the distinction between `flush` and `flushZcu` (formerly `flushModule`) in linkers, where the latter previously meant something along the lines of "flush, but if you're going to be linking with LLD, just flush the ZCU object file, don't actually link"?. The distinction here doesn't seem like it was properly defined, and most linkers seem to treat them as essentially identical anyway. Regardless, all calls to `flushZcu` are gone now, so it's deleted -- one `flush` to rule them all! The end result of this commit and the preceding one is that LLVM and LLD fit into the pipeline much more sanely: * If we're using LLVM for the ZCU, that state is on `zcu.llvm_object` * If we're using LLD to link, then the `link.File` is a `link.Lld` * Calls to "ZCU link functions" (e.g. `updateNav`) lower to calls to the LLVM object if it's available, or otherwise to the `link.File` if it's available (neither is available under `-fno-emit-bin`) * After everything is done, linking is finalized by calling `flush` on the `link.File`; for `link.Lld` this invokes LLD, for other linkers it flushes self-hosted linker state There's one messy thing remaining, and that's how self-hosted function codegen in a ZCU works; right now, we process AIR with a call sequence something like this: * `link.doTask` * `Zcu.PerThread.linkerUpdateFunc` * `link.File.updateFunc` * `link.Elf.updateFunc` * `link.Elf.ZigObject.updateFunc` * `codegen.generateFunction` * `arch.x86_64.CodeGen.generate` So, we start in the linker, take a scenic detour through `Zcu`, go back to the linker, into its implementation, and then... right back out, into code which is generic over the linker implementation, and then dispatch on the *backend* instead! Of course, within `arch.x86_64.CodeGen`, there are some more places which switch on the `link` implementation being used. This is all pretty silly... so it shall be my next target.
Diffstat (limited to 'src/link.zig')
-rw-r--r--src/link.zig371
1 files changed, 42 insertions, 329 deletions
diff --git a/src/link.zig b/src/link.zig
index 3270d10c87..68ea533eed 100644
--- a/src/link.zig
+++ b/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) {