authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-21 00:22:22-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-09-21 13:08:49-07:00
logcf33f1003fd02abaf91078725d9c972dd51fc5e1
tree4005c517ac60d12213b095ec60def90ea5f6230a
parentecd95943a9a174284a5e90f208df4d508aafd098

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

10 files changed, 87 insertions(+), 86 deletions(-)

src/Compilation.zig+3-2
......@@ -2891,12 +2891,13 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE
28912891 }
28922892 }
28932893
2894 for (comp.link_inputs) |input| if (input.path()) |path| {
2894 for (comp.link_inputs) |input| {
2895 const path = input.path();
28952896 try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{
28962897 path.root_dir.path orelse ".",
28972898 path.sub_path,
28982899 }));
2899 };
2900 }
29002901
29012902 // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work.
29022903 // Add a Job for each Win32 resource file.
src/link.zig+21-27
......@@ -1506,7 +1506,6 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
15061506 .object => |obj| diags.addParseError(obj.path, "failed to parse object: {t}", .{e}),
15071507 .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {t}", .{e}),
15081508 .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {t}", .{e}),
1509 .dso_exact => diags.addError("failed to handle dso_exact: {t}", .{e}),
15101509 },
15111510 };
15121511 prog_node.completeOne();
......@@ -1821,8 +1820,6 @@ pub const UnresolvedInput = union(enum) {
18211820 /// Strings that come from GNU ld scripts. Is it a filename? Is it a path?
18221821 /// Who knows! Fuck around and find out.
18231822 ambiguous_name: NameQuery,
1824 /// Put exactly this string in the dynamic section, no rpath.
1825 dso_exact: Input.DsoExact,
18261823
18271824 pub const NameQuery = struct {
18281825 name: []const u8,
......@@ -1875,7 +1872,6 @@ pub const Input = union(enum) {
18751872 /// May not be a GNU ld script. Those are resolved when converting from
18761873 /// `UnresolvedInput` to `Input` values.
18771874 dso: Dso,
1878 dso_exact: DsoExact,
18791875
18801876 pub const Object = struct {
18811877 path: Path,
......@@ -1895,6 +1891,9 @@ pub const Input = union(enum) {
18951891 needed: bool,
18961892 weak: bool,
18971893 reexport: bool,
1894 /// Does not include any ":" prefix. Indicates that this string, exactly, should go into the NEEDED
1895 /// entry, without affecting any rpaths.
1896 exact_name: ?[]const u8,
18981897 };
18991898
19001899 pub const DsoExact = struct {
......@@ -1903,21 +1902,17 @@ pub const Input = union(enum) {
19031902 name: []const u8,
19041903 };
19051904
1906 /// Returns `null` in the case of `dso_exact`.
1907 pub fn path(input: Input) ?Path {
1905 pub fn path(input: Input) Path {
19081906 return switch (input) {
19091907 .object, .archive => |obj| obj.path,
19101908 inline .res, .dso => |x| x.path,
1911 .dso_exact => null,
19121909 };
19131910 }
19141911
1915 /// Returns `null` in the case of `dso_exact`.
1916 pub fn pathAndFile(input: Input) ?struct { Path, Io.File } {
1912 pub fn pathAndFile(input: Input) struct { Path, Io.File } {
19171913 return switch (input) {
19181914 .object, .archive => |obj| .{ obj.path, obj.file },
19191915 inline .res, .dso => |x| .{ x.path, x.file },
1920 .dso_exact => null,
19211916 };
19221917 }
19231918
......@@ -1925,7 +1920,6 @@ pub const Input = union(enum) {
19251920 return switch (input) {
19261921 .object, .archive => |obj| obj.path.basename(),
19271922 inline .res, .dso => |x| x.path.basename(),
1928 .dso_exact => "dso_exact",
19291923 };
19301924 }
19311925};
......@@ -1956,9 +1950,7 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {
19561950 man.hash.add(dso.needed);
19571951 man.hash.add(dso.weak);
19581952 man.hash.add(dso.reexport);
1959 },
1960 .dso_exact => |dso_exact| {
1961 man.hash.addBytes(dso_exact.name);
1953 man.hash.addOptionalBytes(dso.exact_name);
19621954 },
19631955 }
19641956 }
......@@ -2194,10 +2186,6 @@ pub fn resolveInputs(
21942186 }
21952187 continue;
21962188 },
2197 .dso_exact => |dso_exact| {
2198 try resolved_inputs.append(gpa, .{ .dso_exact = dso_exact });
2199 continue;
2200 },
22012189 }
22022190 comptime unreachable;
22032191 }
......@@ -2418,13 +2406,18 @@ fn finishResolveLibInput(
24182406 .hidden = query.hidden,
24192407 } });
24202408 },
2421 .dynamic => resolved_inputs.appendAssumeCapacity(.{ .dso = .{
2422 .path = path,
2423 .file = file,
2424 .needed = query.needed,
2425 .weak = query.weak,
2426 .reexport = query.reexport,
2427 } }),
2409 .dynamic => resolved_inputs.appendAssumeCapacity(.{
2410 .dso = .{
2411 .path = path,
2412 .file = file,
2413 .needed = query.needed,
2414 .weak = query.weak,
2415 .reexport = query.reexport,
2416 // Here we assume that `Path.sub_path` is usable as `exact_name`, however the code after this point
2417 // has no business making that assumption.
2418 .exact_name = if (query.name_done) path.sub_path else null,
2419 },
2420 }),
24282421 }
24292422 return .ok;
24302423}
......@@ -2608,6 +2601,7 @@ pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !In
26082601 .needed = needed,
26092602 .weak = weak,
26102603 .reexport = reexport,
2604 .exact_name = null,
26112605 };
26122606}
26132607
......@@ -2639,7 +2633,7 @@ pub fn anyObjectInputs(inputs: []const Input) bool {
26392633pub fn countObjectInputs(inputs: []const Input) usize {
26402634 var count: usize = 0;
26412635 for (inputs) |input| switch (input) {
2642 .dso, .dso_exact => continue,
2636 .dso => continue,
26432637 .res, .object, .archive => count += 1,
26442638 };
26452639 return count;
......@@ -2649,7 +2643,7 @@ pub fn countObjectInputs(inputs: []const Input) usize {
26492643pub fn firstObjectInput(inputs: []const Input) ?Input.Object {
26502644 for (inputs) |input| switch (input) {
26512645 .object, .archive => |obj| return obj,
2652 .res, .dso, .dso_exact => continue,
2646 .res, .dso => continue,
26532647 };
26542648 return null;
26552649}
src/link/Coff.zig+1-2
......@@ -3905,7 +3905,7 @@ pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void {
39053905 const comp = coff.base.comp;
39063906 const io = comp.io;
39073907
3908 const path = input.path() orelse unreachable;
3908 const path = input.path();
39093909 const gop = try coff.inputs.getOrPut(comp.gpa, path);
39103910 if (gop.found_existing) return;
39113911 errdefer _ = coff.inputs.swapRemove(path);
......@@ -3935,7 +3935,6 @@ pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void {
39353935 coff.loadDll(dso.path, &fr) catch |err|
39363936 return coff.failLoadInput(err, &fr, dso.path);
39373937 },
3938 .dso_exact => unreachable,
39393938 }
39403939}
39413940
src/link/Elf.zig+2-2
......@@ -722,7 +722,6 @@ pub fn loadInput(self: *Elf, input: link.Input) !void {
722722 const argv = &self.dump_argv_list;
723723 switch (input) {
724724 .res => unreachable,
725 .dso_exact => |dso_exact| try argv.appendSlice(gpa, &.{ "-l", dso_exact.name }),
726725 .object, .archive => |obj| try argv.append(gpa, try obj.path.toString(comp.arena)),
727726 .dso => |dso| try argv.append(gpa, try dso.path.toString(comp.arena)),
728727 }
......@@ -730,7 +729,6 @@ pub fn loadInput(self: *Elf, input: link.Input) !void {
730729
731730 switch (input) {
732731 .res => unreachable,
733 .dso_exact => @panic("TODO"),
734732 .object => |obj| try parseObject(self, obj),
735733 .archive => |obj| if (self.base.isStaticLib()) {
736734 // Ignore static library inputs when generating a static library.
......@@ -1120,6 +1118,8 @@ fn parseDso(
11201118
11211119 const handle = dso.file;
11221120
1121 if (dso.exact_name != null) @panic("TODO");
1122
11231123 const stat: Stat = .init(try handle.stat(io));
11241124 var header = try SharedObject.parseHeader(gpa, io, diags, dso.path, handle, stat, target);
11251125 defer header.deinit(gpa);
src/link/Elf2.zig+12-19
......@@ -6228,36 +6228,29 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void
62286228 },
62296229 .res => unreachable,
62306230 .dso => |dso| {
6231 if (dso.exact_name) |exact_name| {
6232 // This means the path field is still valid, but the NEEDED entry must be set to
6233 // exactly the `exact_name` string.
6234 std.debug.panic("load dso_exact {q}", .{exact_name});
6235 if (elf.shndx.dynamic != .UNDEF) {
6236 try elf.needed.put(elf.base.comp.gpa, try elf.string(.dynstr, exact_name), {});
6237 }
6238 }
62316239 try elf.needed.ensureUnusedCapacity(elf.base.comp.gpa, 1);
62326240 var fr = dso.file.reader(io, &buf);
62336241 elf.loadDso(dso.path, &fr) catch |err| switch (err) {
62346242 else => |e| return e,
6235 error.EndOfStream => return diags.failParse(
6236 dso.path,
6237 "unexpected eof",
6238 .{},
6239 ),
6243 error.EndOfStream => return diags.failParse(dso.path, "unexpected eof", .{}),
62406244 error.AccessDenied, error.Unexpected, error.Unseekable => |e| return diags.fail(
6241 "failed to read \"{f}\": {t}",
6242 .{ dso.path.fmtEscapeString(), e },
6245 "failed to read {qf}: {t}",
6246 .{ dso.path, e },
62436247 ),
62446248 error.ReadFailed => switch (fr.err.?) {
62456249 error.Canceled => |e| return e,
6246 else => |e| return diags.fail(
6247 "failed to read \"{f}\": {t}",
6248 .{ dso.path.fmtEscapeString(), e },
6249 ),
6250 else => |e| return diags.fail("failed to read {qf}: {t}", .{ dso.path, e }),
62506251 },
62516252 };
62526253 },
6253 .dso_exact => |dso_exact| {
6254 log.debug("load dso_exact '{f}'", .{std.zig.fmtString(dso_exact.name)});
6255 if (elf.shndx.dynamic != .UNDEF) {
6256 try elf.needed.put(elf.base.comp.gpa, try elf.string(.dynstr, dso_exact.name), {});
6257 }
6258 // TODO: we need to get a resolved file path from the frontend, because we need to read
6259 // the shared object to discover symbol types.
6260 },
62616254 }
62626255}
62636256fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void {
src/link/Lld.zig+22-14
......@@ -309,9 +309,9 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) link.Error!void {
309309
310310 try object_files.ensureUnusedCapacity(arena, comp.link_inputs.len);
311311 for (comp.link_inputs) |input| switch (input) {
312 .dso, .dso_exact, .archive => {}, // static archives should not contain shared libraries or other static archives
312 .dso, .archive => {}, // static archives should not contain shared libraries or other static archives
313313 .res, .object => {
314 const path = try input.path().?.toStringZ(arena);
314 const path = try input.path().toStringZ(arena);
315315 object_files.appendAssumeCapacity(path);
316316 },
317317 };
......@@ -551,7 +551,6 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
551551
552552 try argv.ensureUnusedCapacity(comp.link_inputs.len);
553553 for (comp.link_inputs) |link_input| switch (link_input) {
554 .dso_exact => unreachable, // not applicable to PE/COFF
555554 inline .dso, .res => |x| {
556555 argv.appendAssumeCapacity(try x.path.toString(arena));
557556 },
......@@ -1114,10 +1113,6 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
11141113 }
11151114 try argv.append(try obj.path.toString(arena));
11161115 },
1117 .dso_exact => |dso_exact| {
1118 assert(dso_exact.name[0] == ':');
1119 try argv.appendSlice(&.{ "-l", dso_exact.name });
1120 },
11211116 };
11221117
11231118 if (whole_archive) {
......@@ -1161,7 +1156,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
11611156
11621157 for (base.comp.link_inputs) |link_input| switch (link_input) {
11631158 .res => unreachable, // Windows-only
1164 .object, .archive, .dso_exact => continue,
1159 .object, .archive => continue,
11651160 .dso => |dso| {
11661161 const lib_as_needed = !dso.needed;
11671162 switch ((@as(u2, @intFromBool(lib_as_needed)) << 1) | @intFromBool(as_needed)) {
......@@ -1176,11 +1171,25 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
11761171 },
11771172 }
11781173
1179 // By this time, we depend on these libs being dynamically linked
1180 // libraries and not static libraries (the check for that needs to be earlier),
1181 // but they could be full paths to .so files, in which case we
1182 // want to avoid prepending "-l".
1183 argv.appendAssumeCapacity(try dso.path.toString(arena));
1174 if (dso.exact_name) |exact_name| {
1175 // LLD does not have a way to specify full path to the DSO while also specifying
1176 // the exact value for NEEDED, so we have to use a combination of -L and -l args.
1177 assert(mem.endsWith(u8, dso.path.sub_path, exact_name));
1178 const truncated_path: Cache.Path = .{
1179 .root_dir = dso.path.root_dir,
1180 .sub_path = dso.path.sub_path[0 .. dso.path.sub_path.len - exact_name.len],
1181 };
1182 try argv.ensureUnusedCapacity(3);
1183 argv.appendAssumeCapacity("-L");
1184 argv.appendAssumeCapacity(try truncated_path.toString(arena));
1185 argv.appendAssumeCapacity(try arena.print("-l:{s}", .{exact_name}));
1186 } else {
1187 // By this time, we depend on these libs being dynamically linked
1188 // libraries and not static libraries (the check for that needs to be earlier),
1189 // but they could be full paths to .so files, in which case we
1190 // want to avoid prepending "-l".
1191 argv.appendAssumeCapacity(try dso.path.toString(arena));
1192 }
11841193 },
11851194 };
11861195
......@@ -1588,7 +1597,6 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
15881597 .dso => |dso| {
15891598 try argv.append(try dso.path.toString(arena));
15901599 },
1591 .dso_exact => unreachable,
15921600 .res => unreachable,
15931601 };
15941602 if (whole_archive) {
src/link/MachO.zig+21-13
......@@ -372,7 +372,6 @@ pub fn flush(
372372 for (comp.link_inputs) |link_input| switch (link_input) {
373373 .dso => continue, // handled below
374374 .object, .archive => positionals.appendAssumeCapacity(link_input),
375 .dso_exact => @panic("TODO"),
376375 .res => unreachable,
377376 };
378377
......@@ -406,7 +405,7 @@ pub fn flush(
406405
407406 for (positionals.items) |link_input| {
408407 self.classifyInputFile(link_input) catch |err|
409 diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
408 diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err});
410409 }
411410
412411 var system_libs = std.array_list.Managed(SystemLib).init(gpa);
......@@ -447,11 +446,11 @@ pub fn flush(
447446 };
448447
449448 for (comp.link_inputs) |link_input| switch (link_input) {
450 .object, .archive, .dso_exact => continue,
449 .object, .archive => continue,
451450 .res => unreachable,
452451 .dso => {
453452 self.classifyInputFile(link_input) catch |err|
454 diags.addParseError(link_input.path().?, "failed to parse input file: {s}", .{@errorName(err)});
453 diags.addParseError(link_input.path(), "failed to parse input file: {t}", .{err});
455454 },
456455 };
457456
......@@ -655,8 +654,13 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
655654 for (comp.link_inputs) |link_input| switch (link_input) {
656655 .object, .archive => |obj| try argv.append(try obj.path.toString(arena)),
657656 .res => |res| try argv.append(try res.path.toString(arena)),
658 .dso => |dso| try argv.append(try dso.path.toString(arena)),
659 .dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }),
657 .dso => |dso| {
658 if (dso.exact_name) |exact_name| {
659 try argv.appendSlice(&.{ "-l", try arena.print(":{s}", .{exact_name}) });
660 } else {
661 try argv.append(try dso.path.toString(arena));
662 }
663 },
660664 };
661665
662666 for (comp.c_objects.items) |c_object| {
......@@ -748,7 +752,6 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
748752 if (obj.must_link) try argv.append("-force_load"); // TODO: verify this
749753 try argv.append(try obj.path.toString(arena));
750754 },
751 .dso_exact => |dso_exact| try argv.appendSlice(&.{ "-l", dso_exact.name }),
752755 };
753756
754757 for (comp.c_objects.items) |c_object| {
......@@ -775,15 +778,21 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
775778 }
776779
777780 for (comp.link_inputs) |link_input| switch (link_input) {
778 .object, .archive, .dso_exact => continue, // handled above
781 .object, .archive => continue, // handled above
779782 .res => unreachable, // windows only
780783 .dso => |dso| {
784 try argv.ensureUnusedCapacity(2);
781785 if (dso.needed) {
782 try argv.appendSlice(&.{ "-needed-l", try dso.path.toString(arena) });
786 argv.appendAssumeCapacity("-needed-l");
783787 } else if (dso.weak) {
784 try argv.appendSlice(&.{ "-weak-l", try dso.path.toString(arena) });
788 argv.appendAssumeCapacity("-weak-l");
789 } else {
790 argv.appendAssumeCapacity("-l");
791 }
792 if (dso.exact_name) |exact_name| {
793 argv.appendAssumeCapacity(try arena.print(":{s}", .{exact_name}));
785794 } else {
786 try argv.appendSlice(&.{ "-l", try dso.path.toString(arena) });
795 argv.appendAssumeCapacity(try dso.path.toString(arena));
787796 }
788797 },
789798 };
......@@ -870,7 +879,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void {
870879 const comp = self.base.comp;
871880 const io = comp.io;
872881
873 const path, const file = input.pathAndFile().?;
882 const path, const file = input.pathAndFile();
874883 // TODO don't classify now, it's too late. The input file has already been classified
875884 log.debug("classifying input file {f}", .{path});
876885
......@@ -4478,7 +4487,6 @@ const SystemLib = struct {
44784487
44794488 fn fromLinkInput(link_input: link.Input) SystemLib {
44804489 return switch (link_input) {
4481 .dso_exact => unreachable,
44824490 .res => unreachable,
44834491 .object, .archive => |obj| .{
44844492 .path = obj.path,
src/link/MachO/relocatable.zig+3-3
......@@ -19,7 +19,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
1919 // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all
2020 // debug info segments/sections (this is apparently by design by Apple), we copy
2121 // the *only* input file over.
22 const path = positionals.items[0].path().?;
22 const path = positionals.items[0].path();
2323 const in_file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err|
2424 return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) });
2525 const stat = in_file.stat(io) catch |err|
......@@ -31,7 +31,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat
3131
3232 for (positionals.items) |link_input| {
3333 macho_file.classifyInputFile(link_input) catch |err|
34 diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
34 diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err});
3535 }
3636
3737 if (diags.hasErrors()) return error.AlreadyReported;
......@@ -102,7 +102,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?
102102
103103 for (positionals.items) |link_input| {
104104 macho_file.classifyInputFile(link_input) catch |err|
105 diags.addParseError(link_input.path().?, "failed to read input file: {s}", .{@errorName(err)});
105 diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err});
106106 }
107107
108108 if (diags.hasErrors()) return error.AlreadyReported;
src/link/Wasm.zig-2
......@@ -3788,7 +3788,6 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void {
37883788 const argv = &wasm.dump_argv_list;
37893789 switch (input) {
37903790 .res => unreachable,
3791 .dso_exact => unreachable,
37923791 .dso => unreachable,
37933792 .object, .archive => |obj| {
37943793 try argv.append(gpa, try obj.path.toString(comp.arena));
......@@ -3798,7 +3797,6 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void {
37983797
37993798 switch (input) {
38003799 .res => unreachable,
3801 .dso_exact => unreachable,
38023800 .dso => unreachable,
38033801 .object => |obj| try parseObject(wasm, obj),
38043802 .archive => |obj| try parseArchive(wasm, obj),
src/main.zig+2-2
......@@ -4277,10 +4277,10 @@ fn createModule(
42774277 &create_module.link_inputs,
42784278 create_module.lib_directories.items,
42794279 color,
4280 ) catch |err| fatal("failed to resolve link inputs: {s}", .{@errorName(err)});
4280 ) catch |err| fatal("failed to resolve link inputs: {t}", .{err});
42814281
42824282 if (!create_module.opts.any_dyn_libs) for (create_module.link_inputs.items) |item| switch (item) {
4283 .dso, .dso_exact => {
4283 .dso => {
42844284 create_module.opts.any_dyn_libs = true;
42854285 break;
42864286 },