From cf33f1003fd02abaf91078725d9c972dd51fc5e1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 21 Sep 2026 00:22:22 -0700 Subject: [PATCH] link frontend: remove dso_exact, add exact_name to dso linker still wants the path to the shared object, but now also accepts an exact name to put into the NEEDED entry. closes #31823 --- src/Compilation.zig | 5 ++-- src/link.zig | 48 +++++++++++++++------------------- src/link/Coff.zig | 3 +-- src/link/Elf.zig | 4 +-- src/link/Elf2.zig | 31 +++++++++------------- src/link/Lld.zig | 36 +++++++++++++++---------- src/link/MachO.zig | 34 +++++++++++++++--------- src/link/MachO/relocatable.zig | 6 ++--- src/link/Wasm.zig | 2 -- src/main.zig | 4 +-- 10 files changed, 87 insertions(+), 86 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index e7da1d5a790a8ac1c6a5e3f8edaab6341b7b77aa..b07db94185836ffcffb95cbf7a3286c947439820 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2891,12 +2891,13 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE } } - for (comp.link_inputs) |input| if (input.path()) |path| { + for (comp.link_inputs) |input| { + const path = input.path(); try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{ path.root_dir.path orelse ".", path.sub_path, })); - }; + } // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work. // Add a Job for each Win32 resource file. diff --git a/src/link.zig b/src/link.zig index 10c93ce3ad75a6e9796c4a4a9a8edb2104322ad5..e913efa77ae4c3e8113d07ff6cf81b79e098bdb8 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1506,7 +1506,6 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { .object => |obj| diags.addParseError(obj.path, "failed to parse object: {t}", .{e}), .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {t}", .{e}), .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {t}", .{e}), - .dso_exact => diags.addError("failed to handle dso_exact: {t}", .{e}), }, }; prog_node.completeOne(); @@ -1821,8 +1820,6 @@ pub const UnresolvedInput = union(enum) { /// Strings that come from GNU ld scripts. Is it a filename? Is it a path? /// Who knows! Fuck around and find out. ambiguous_name: NameQuery, - /// Put exactly this string in the dynamic section, no rpath. - dso_exact: Input.DsoExact, pub const NameQuery = struct { name: []const u8, @@ -1875,7 +1872,6 @@ pub const Input = union(enum) { /// May not be a GNU ld script. Those are resolved when converting from /// `UnresolvedInput` to `Input` values. dso: Dso, - dso_exact: DsoExact, pub const Object = struct { path: Path, @@ -1895,6 +1891,9 @@ pub const Input = union(enum) { needed: bool, weak: bool, reexport: bool, + /// Does not include any ":" prefix. Indicates that this string, exactly, should go into the NEEDED + /// entry, without affecting any rpaths. + exact_name: ?[]const u8, }; pub const DsoExact = struct { @@ -1903,21 +1902,17 @@ pub const Input = union(enum) { name: []const u8, }; - /// Returns `null` in the case of `dso_exact`. - pub fn path(input: Input) ?Path { + pub fn path(input: Input) Path { return switch (input) { .object, .archive => |obj| obj.path, inline .res, .dso => |x| x.path, - .dso_exact => null, }; } - /// Returns `null` in the case of `dso_exact`. - pub fn pathAndFile(input: Input) ?struct { Path, Io.File } { + pub fn pathAndFile(input: Input) struct { Path, Io.File } { return switch (input) { .object, .archive => |obj| .{ obj.path, obj.file }, inline .res, .dso => |x| .{ x.path, x.file }, - .dso_exact => null, }; } @@ -1925,7 +1920,6 @@ pub const Input = union(enum) { return switch (input) { .object, .archive => |obj| obj.path.basename(), inline .res, .dso => |x| x.path.basename(), - .dso_exact => "dso_exact", }; } }; @@ -1956,9 +1950,7 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void { man.hash.add(dso.needed); man.hash.add(dso.weak); man.hash.add(dso.reexport); - }, - .dso_exact => |dso_exact| { - man.hash.addBytes(dso_exact.name); + man.hash.addOptionalBytes(dso.exact_name); }, } } @@ -2194,10 +2186,6 @@ pub fn resolveInputs( } continue; }, - .dso_exact => |dso_exact| { - try resolved_inputs.append(gpa, .{ .dso_exact = dso_exact }); - continue; - }, } comptime unreachable; } @@ -2418,13 +2406,18 @@ fn finishResolveLibInput( .hidden = query.hidden, } }); }, - .dynamic => resolved_inputs.appendAssumeCapacity(.{ .dso = .{ - .path = path, - .file = file, - .needed = query.needed, - .weak = query.weak, - .reexport = query.reexport, - } }), + .dynamic => resolved_inputs.appendAssumeCapacity(.{ + .dso = .{ + .path = path, + .file = file, + .needed = query.needed, + .weak = query.weak, + .reexport = query.reexport, + // Here we assume that `Path.sub_path` is usable as `exact_name`, however the code after this point + // has no business making that assumption. + .exact_name = if (query.name_done) path.sub_path else null, + }, + }), } return .ok; } @@ -2608,6 +2601,7 @@ pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !In .needed = needed, .weak = weak, .reexport = reexport, + .exact_name = null, }; } @@ -2639,7 +2633,7 @@ pub fn anyObjectInputs(inputs: []const Input) bool { pub fn countObjectInputs(inputs: []const Input) usize { var count: usize = 0; for (inputs) |input| switch (input) { - .dso, .dso_exact => continue, + .dso => continue, .res, .object, .archive => count += 1, }; return count; @@ -2649,7 +2643,7 @@ pub fn countObjectInputs(inputs: []const Input) usize { pub fn firstObjectInput(inputs: []const Input) ?Input.Object { for (inputs) |input| switch (input) { .object, .archive => |obj| return obj, - .res, .dso, .dso_exact => continue, + .res, .dso => continue, }; return null; } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index d8934d79d89cff328b413aaec7ef173be957adff..250dbd388658148ff196567923b5e0de593f72eb 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -3905,7 +3905,7 @@ pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void { const comp = coff.base.comp; const io = comp.io; - const path = input.path() orelse unreachable; + const path = input.path(); const gop = try coff.inputs.getOrPut(comp.gpa, path); if (gop.found_existing) return; errdefer _ = coff.inputs.swapRemove(path); @@ -3935,7 +3935,6 @@ pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void { coff.loadDll(dso.path, &fr) catch |err| return coff.failLoadInput(err, &fr, dso.path); }, - .dso_exact => unreachable, } } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index f04d7582bd6dac07669861f0d920c86efa6d6d1c..4c9a91d25ebcac10fd8186f33b2e22d5ad6ed44f 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -722,7 +722,6 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { const argv = &self.dump_argv_list; switch (input) { .res => unreachable, - .dso_exact => |dso_exact| try argv.appendSlice(gpa, &.{ "-l", dso_exact.name }), .object, .archive => |obj| try argv.append(gpa, try obj.path.toString(comp.arena)), .dso => |dso| try argv.append(gpa, try dso.path.toString(comp.arena)), } @@ -730,7 +729,6 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { switch (input) { .res => unreachable, - .dso_exact => @panic("TODO"), .object => |obj| try parseObject(self, obj), .archive => |obj| if (self.base.isStaticLib()) { // Ignore static library inputs when generating a static library. @@ -1120,6 +1118,8 @@ fn parseDso( const handle = dso.file; + if (dso.exact_name != null) @panic("TODO"); + const stat: Stat = .init(try handle.stat(io)); var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target); defer header.deinit(gpa); diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 569df037df4e861707959ff914ea74e3bb2d0b08..155f5aeb18a74f6a55543eeb8dd53fd25f240af9 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -6228,36 +6228,29 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void }, .res => unreachable, .dso => |dso| { + if (dso.exact_name) |exact_name| { + // This means the path field is still valid, but the NEEDED entry must be set to + // exactly the `exact_name` string. + std.debug.panic("load dso_exact {q}", .{exact_name}); + if (elf.shndx.dynamic != .UNDEF) { + try elf.needed.put(elf.base.comp.gpa, try elf.string(.dynstr, exact_name), {}); + } + } try elf.needed.ensureUnusedCapacity(elf.base.comp.gpa, 1); var fr = dso.file.reader(io, &buf); elf.loadDso(dso.path, &fr) catch |err| switch (err) { else => |e| return e, - error.EndOfStream => return diags.failParse( - dso.path, - "unexpected eof", - .{}, - ), + error.EndOfStream => return diags.failParse(dso.path, "unexpected eof", .{}), error.AccessDenied, error.Unexpected, error.Unseekable => |e| return diags.fail( - "failed to read \"{f}\": {t}", - .{ dso.path.fmtEscapeString(), e }, + "failed to read {qf}: {t}", + .{ dso.path, e }, ), error.ReadFailed => switch (fr.err.?) { error.Canceled => |e| return e, - else => |e| return diags.fail( - "failed to read \"{f}\": {t}", - .{ dso.path.fmtEscapeString(), e }, - ), + else => |e| return diags.fail("failed to read {qf}: {t}", .{ dso.path, e }), }, }; }, - .dso_exact => |dso_exact| { - log.debug("load dso_exact '{f}'", .{std.zig.fmtString(dso_exact.name)}); - if (elf.shndx.dynamic != .UNDEF) { - try elf.needed.put(elf.base.comp.gpa, try elf.string(.dynstr, dso_exact.name), {}); - } - // TODO: we need to get a resolved file path from the frontend, because we need to read - // the shared object to discover symbol types. - }, } } fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void { diff --git a/src/link/Lld.zig b/src/link/Lld.zig index f715ea6147f2cb584adca502e9cfb958f9e24724..8ed1f43086fe355ddf5481287c4d1ca41f2c78a4 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -309,9 +309,9 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) link.Error!void { try object_files.ensureUnusedCapacity(arena, comp.link_inputs.len); for (comp.link_inputs) |input| switch (input) { - .dso, .dso_exact, .archive => {}, // static archives should not contain shared libraries or other static archives + .dso, .archive => {}, // static archives should not contain shared libraries or other static archives .res, .object => { - const path = try input.path().?.toStringZ(arena); + const path = try input.path().toStringZ(arena); object_files.appendAssumeCapacity(path); }, }; @@ -551,7 +551,6 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { 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)); }, @@ -1114,10 +1113,6 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { } 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) { @@ -1161,7 +1156,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { for (base.comp.link_inputs) |link_input| switch (link_input) { .res => unreachable, // Windows-only - .object, .archive, .dso_exact => continue, + .object, .archive => continue, .dso => |dso| { const lib_as_needed = !dso.needed; switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) { @@ -1176,11 +1171,25 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { }, } - // 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 (dso.exact_name) |exact_name| { + // LLD does not have a way to specify full path to the DSO while also specifying + // the exact value for NEEDED, so we have to use a combination of -L and -l args. + assert(mem.endsWith(u8, dso.path.sub_path, exact_name)); + const truncated_path: Cache.Path = .{ + .root_dir = dso.path.root_dir, + .sub_path = dso.path.sub_path[0 .. dso.path.sub_path.len - exact_name.len], + }; + try argv.ensureUnusedCapacity(3); + argv.appendAssumeCapacity("-L"); + argv.appendAssumeCapacity(try truncated_path.toString(arena)); + argv.appendAssumeCapacity(try arena.print("-l:{s}", .{exact_name})); + } else { + // 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)); + } }, }; @@ -1588,7 +1597,6 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { .dso => |dso| { try argv.append(try dso.path.toString(arena)); }, - .dso_exact => unreachable, .res => unreachable, }; if (whole_archive) { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index c32301b826482026993bfb7e8763357cd57dbf06..4df893a7e02e0942dd51897dd072c62646be25fb 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -372,7 +372,6 @@ pub fn flush( for (comp.link_inputs) |link_input| switch (link_input) { .dso => continue, // handled below .object, .archive => positionals.appendAssumeCapacity(link_input), - .dso_exact => @panic("TODO"), .res => unreachable, }; @@ -406,7 +405,7 @@ pub fn flush( for (positionals.items) |link_input| { self.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); + diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err}); } var system_libs = std.array_list.Managed(SystemLib).init(gpa); @@ -447,11 +446,11 @@ pub fn flush( }; for (comp.link_inputs) |link_input| switch (link_input) { - .object, .archive, .dso_exact => continue, + .object, .archive => continue, .res => unreachable, .dso => { self.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path().?, "failed to parse input file: {s}", .{@errorName(err)}); + diags.addParseError(link_input.path(), "failed to parse input file: {t}", .{err}); }, }; @@ -655,8 +654,13 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { for (comp.link_inputs) |link_input| switch (link_input) { .object, .archive => |obj| try argv.append(try obj.path.toString(arena)), .res => |res| try argv.append(try res.path.toString(arena)), - .dso => |dso| try argv.append(try dso.path.toString(arena)), - .dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }), + .dso => |dso| { + if (dso.exact_name) |exact_name| { + try argv.appendSlice(&.{ "-l", try arena.print(":{s}", .{exact_name}) }); + } else { + try argv.append(try dso.path.toString(arena)); + } + }, }; for (comp.c_objects.items) |c_object| { @@ -748,7 +752,6 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { if (obj.must_link) try argv.append("-force_load"); // TODO: verify this try argv.append(try obj.path.toString(arena)); }, - .dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }), }; for (comp.c_objects.items) |c_object| { @@ -775,15 +778,21 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { } for (comp.link_inputs) |link_input| switch (link_input) { - .object, .archive, .dso_exact => continue, // handled above + .object, .archive => continue, // handled above .res => unreachable, // windows only .dso => |dso| { + try argv.ensureUnusedCapacity(2); if (dso.needed) { - try argv.appendSlice(&.{ "-needed-l", try dso.path.toString(arena) }); + argv.appendAssumeCapacity("-needed-l"); } else if (dso.weak) { - try argv.appendSlice(&.{ "-weak-l", try dso.path.toString(arena) }); + argv.appendAssumeCapacity("-weak-l"); } else { - try argv.appendSlice(&.{ "-l", try dso.path.toString(arena) }); + argv.appendAssumeCapacity("-l"); + } + if (dso.exact_name) |exact_name| { + argv.appendAssumeCapacity(try arena.print(":{s}", .{exact_name})); + } else { + argv.appendAssumeCapacity(try dso.path.toString(arena)); } }, }; @@ -870,7 +879,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { const comp = self.base.comp; const io = comp.io; - const path, const file = input.pathAndFile().?; + const path, const file = input.pathAndFile(); // TODO don't classify now, it's too late. The input file has already been classified log.debug("classifying input file {f}", .{path}); @@ -4478,7 +4487,6 @@ const SystemLib = struct { fn fromLinkInput(link_input: link.Input) SystemLib { return switch (link_input) { - .dso_exact => unreachable, .res => unreachable, .object, .archive => |obj| .{ .path = obj.path, diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 793cfe5f0c2659a7df45a6ea69dfccc0f101db08..38420414382928237d93b16b5c8648e4c91fb6f5 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -19,7 +19,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all // debug info segments/sections (this is apparently by design by Apple), we copy // the *only* input file over. - const path = positionals.items[0].path().?; + const path = positionals.items[0].path(); const in_file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) }); const stat = in_file.stat(io) catch |err| @@ -31,7 +31,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat for (positionals.items) |link_input| { macho_file.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); + diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err}); } if (diags.hasErrors()) return error.AlreadyReported; @@ -102,7 +102,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? for (positionals.items) |link_input| { macho_file.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)}); + diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err}); } if (diags.hasErrors()) return error.AlreadyReported; diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 4532b75a647013c1f15f19aebceae5a094b510ab..30868f14272601c048c01e495e239d9819c32a85 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3788,7 +3788,6 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void { const argv = &wasm.dump_argv_list; switch (input) { .res => unreachable, - .dso_exact => unreachable, .dso => unreachable, .object, .archive => |obj| { try argv.append(gpa, try obj.path.toString(comp.arena)); @@ -3798,7 +3797,6 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void { switch (input) { .res => unreachable, - .dso_exact => unreachable, .dso => unreachable, .object => |obj| try parseObject(wasm, obj), .archive => |obj| try parseArchive(wasm, obj), diff --git a/src/main.zig b/src/main.zig index bc2354b3a8c3755e7efb1d4ed1afe3e77a38cf2e..095a563ca2b605924aa3c41eeb9285fedf582897 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4277,10 +4277,10 @@ fn createModule( &create_module.link_inputs, create_module.lib_directories.items, color, - ) catch |err| fatal("failed to resolve link inputs: {s}", .{@errorName(err)}); + ) catch |err| fatal("failed to resolve link inputs: {t}", .{err}); if (!create_module.opts.any_dyn_libs) for (create_module.link_inputs.items) |item| switch (item) { - .dso, .dso_exact => { + .dso => { create_module.opts.any_dyn_libs = true; break; }, -- 2.54.0