1const builtin = @import("builtin");
2const build_options = @import("build_options");
3
4const std = @import("std");
5const Io = std.Io;
6const assert = std.debug.assert;
7const fs = std.fs;
8const mem = std.mem;
9const log = std.log.scoped(.link);
10const Allocator = std.mem.Allocator;
11const Cache = std.Build.Cache;
12const Path = std.Build.Cache.Path;
13const Directory = std.Build.Cache.Directory;
14const Compilation = @import("Compilation.zig");
15const LibCInstallation = std.zig.LibCInstallation;
16
17const trace = @import("tracy.zig").trace;
18const wasi_libc = @import("libs/wasi_libc.zig");
19
20const Zcu = @import("Zcu.zig");
21const InternPool = @import("InternPool.zig");
22const Type = @import("Type.zig");
23const Value = @import("Value.zig");
24const dev = @import("dev.zig");
25const target_util = @import("target.zig");
26const codegen = @import("codegen.zig");
27const crash_report = @import("crash_report.zig");
28
29pub const LdScript = @import("link/LdScript.zig");
30pub const Queue = @import("link/Queue.zig");
31pub const ConstPool = @import("link/ConstPool.zig");
32
33pub const aarch64 = @import("link/aarch64.zig");
34pub const loongarch = @import("link/loongarch.zig");
35
36pub const Error = Allocator.Error || Io.Cancelable || error{
37 /// An error message has already been stored in persistent state on `Compilation` or `Zcu`, for
38 /// instance in `Compilation.link_diags`.
39 AlreadyReported,
40};
41
42pub const Diags = struct {
43 /// Stored here so that function definitions can distinguish between
44 /// needing an allocator for things besides error reporting.
45 gpa: Allocator,
46 io: Io,
47 mutex: Io.Mutex,
48 msgs: std.ArrayList(Msg),
49 flags: Flags,
50 lld: std.ArrayList(Lld),
51
52 pub const SourceLocation = union(enum) {
53 none,
54 wasm: File.Wasm.SourceLocation,
55 };
56
57 pub const Flags = packed struct {
58 no_entry_point_found: bool = false,
59 missing_libc: bool = false,
60 alloc_failure_occurred: bool = false,
61
62 const Int = blk: {
63 const bits = @typeInfo(@This()).@"struct".field_names.len;
64 break :blk @Int(.unsigned, bits);
65 };
66
67 pub fn anySet(ef: Flags) bool {
68 return @as(Int, @bitCast(ef)) > 0;
69 }
70 };
71
72 pub const Lld = struct {
73 /// Allocated with gpa.
74 msg: []const u8,
75 context_lines: []const []const u8 = &.{},
76
77 pub fn deinit(self: *Lld, gpa: Allocator) void {
78 for (self.context_lines) |line| gpa.free(line);
79 gpa.free(self.context_lines);
80 gpa.free(self.msg);
81 self.* = undefined;
82 }
83 };
84
85 pub const Msg = struct {
86 source_location: SourceLocation = .none,
87 msg: []const u8,
88 notes: []Msg = &.{},
89
90 fn string(
91 msg: *const Msg,
92 bundle: *std.zig.ErrorBundle.Wip,
93 base: ?*File,
94 ) Allocator.Error!std.zig.ErrorBundle.String {
95 return switch (msg.source_location) {
96 .none => try bundle.addString(msg.msg),
97 .wasm => |sl| {
98 dev.check(.wasm_linker);
99 const wasm = base.?.cast(.wasm).?;
100 return sl.string(msg.msg, bundle, wasm);
101 },
102 };
103 }
104
105 pub fn deinit(self: *Msg, gpa: Allocator) void {
106 for (self.notes) |*note| note.deinit(gpa);
107 gpa.free(self.notes);
108 gpa.free(self.msg);
109 }
110 };
111
112 pub const ErrorWithNotes = struct {
113 diags: *Diags,
114 /// Allocated index in diags.msgs array.
115 index: usize,
116 /// Next available note slot.
117 note_slot: usize = 0,
118
119 pub fn addMsg(
120 err: ErrorWithNotes,
121 comptime format: []const u8,
122 args: anytype,
123 ) Allocator.Error!void {
124 const gpa = err.diags.gpa;
125 const err_msg = &err.diags.msgs.items[err.index];
126 err_msg.msg = try std.fmt.allocPrint(gpa, format, args);
127 }
128
129 pub fn addNote(err: *ErrorWithNotes, comptime format: []const u8, args: anytype) void {
130 const gpa = err.diags.gpa;
131 const msg = std.fmt.allocPrint(gpa, format, args) catch return err.diags.setAllocFailure();
132 const err_msg = &err.diags.msgs.items[err.index];
133 assert(err.note_slot < err_msg.notes.len);
134 err_msg.notes[err.note_slot] = .{ .msg = msg };
135 err.note_slot += 1;
136 }
137 };
138
139 pub fn init(gpa: Allocator, io: Io) Diags {
140 return .{
141 .gpa = gpa,
142 .io = io,
143 .mutex = .init,
144 .msgs = .empty,
145 .flags = .{},
146 .lld = .empty,
147 };
148 }
149
150 pub fn deinit(diags: *Diags) void {
151 const gpa = diags.gpa;
152
153 for (diags.msgs.items) |*item| item.deinit(gpa);
154 diags.msgs.deinit(gpa);
155
156 for (diags.lld.items) |*item| item.deinit(gpa);
157 diags.lld.deinit(gpa);
158
159 diags.* = undefined;
160 }
161
162 pub fn hasErrors(diags: *Diags) bool {
163 return diags.msgs.items.len > 0 or diags.flags.anySet();
164 }
165
166 pub fn lockAndParseLldStderr(diags: *Diags, prefix: []const u8, stderr: []const u8) void {
167 const io = diags.io;
168
169 diags.mutex.lockUncancelable(io);
170 defer diags.mutex.unlock(io);
171
172 diags.parseLldStderr(prefix, stderr) catch diags.setAllocFailure();
173 }
174
175 fn parseLldStderr(
176 diags: *Diags,
177 prefix: []const u8,
178 stderr: []const u8,
179 ) Allocator.Error!void {
180 const gpa = diags.gpa;
181
182 var context_lines: std.ArrayList([]const u8) = .empty;
183 defer context_lines.deinit(gpa);
184
185 var current_err: ?*Lld = null;
186 var lines = mem.splitSequence(u8, stderr, if (builtin.os.tag == .windows) "\r\n" else "\n");
187 while (lines.next()) |line| {
188 if (line.len > prefix.len + ":".len and
189 mem.eql(u8, line[0..prefix.len], prefix) and line[prefix.len] == ':')
190 {
191 if (current_err) |err| {
192 err.context_lines = try context_lines.toOwnedSlice(gpa);
193 }
194
195 var split = mem.splitSequence(u8, line, "error: ");
196 _ = split.first();
197
198 try diags.lld.ensureUnusedCapacity(gpa, 1);
199
200 const duped_msg = try std.fmt.allocPrint(gpa, "{s}: {s}", .{ prefix, split.rest() });
201
202 current_err = diags.lld.addOneAssumeCapacity();
203 current_err.?.* = .{ .msg = duped_msg };
204 } else if (current_err != null) {
205 const context_prefix = ">>> ";
206 var trimmed = mem.trimEnd(u8, line, &std.ascii.whitespace);
207 if (mem.startsWith(u8, trimmed, context_prefix)) {
208 trimmed = trimmed[context_prefix.len..];
209 }
210
211 if (trimmed.len > 0) {
212 try context_lines.ensureUnusedCapacity(gpa, 1);
213 context_lines.appendAssumeCapacity(try gpa.dupe(u8, trimmed));
214 }
215 }
216 }
217
218 if (current_err) |err| {
219 err.context_lines = try context_lines.toOwnedSlice(gpa);
220 }
221 }
222
223 pub fn fail(diags: *Diags, comptime format: []const u8, args: anytype) error{AlreadyReported} {
224 @branchHint(.cold);
225 addError(diags, format, args);
226 return error.AlreadyReported;
227 }
228
229 pub fn failSourceLocation(diags: *Diags, sl: SourceLocation, comptime format: []const u8, args: anytype) error{AlreadyReported} {
230 @branchHint(.cold);
231 addErrorSourceLocation(diags, sl, format, args);
232 return error.AlreadyReported;
233 }
234
235 pub fn addError(diags: *Diags, comptime format: []const u8, args: anytype) void {
236 @branchHint(.cold);
237 return addErrorSourceLocation(diags, .none, format, args);
238 }
239
240 pub fn addErrorSourceLocation(diags: *Diags, sl: SourceLocation, comptime format: []const u8, args: anytype) void {
241 @branchHint(.cold);
242 const gpa = diags.gpa;
243 const io = diags.io;
244 const eu_main_msg = std.fmt.allocPrint(gpa, format, args);
245 diags.mutex.lockUncancelable(io);
246 defer diags.mutex.unlock(io);
247 addErrorLockedFallible(diags, sl, eu_main_msg) catch |err| switch (err) {
248 error.OutOfMemory => diags.setAllocFailureLocked(),
249 };
250 }
251
252 fn addErrorLockedFallible(diags: *Diags, sl: SourceLocation, eu_main_msg: Allocator.Error![]u8) Allocator.Error!void {
253 const gpa = diags.gpa;
254 const main_msg = try eu_main_msg;
255 errdefer gpa.free(main_msg);
256 try diags.msgs.append(gpa, .{
257 .msg = main_msg,
258 .source_location = sl,
259 });
260 }
261
262 pub fn addErrorWithNotes(diags: *Diags, note_count: usize) Allocator.Error!ErrorWithNotes {
263 @branchHint(.cold);
264 const gpa = diags.gpa;
265 const io = diags.io;
266 diags.mutex.lockUncancelable(io);
267 defer diags.mutex.unlock(io);
268 try diags.msgs.ensureUnusedCapacity(gpa, 1);
269 return addErrorWithNotesAssumeCapacity(diags, note_count);
270 }
271
272 pub fn addErrorWithNotesAssumeCapacity(diags: *Diags, note_count: usize) Allocator.Error!ErrorWithNotes {
273 @branchHint(.cold);
274 const gpa = diags.gpa;
275 const index = diags.msgs.items.len;
276 const err = diags.msgs.addOneAssumeCapacity();
277 err.* = .{
278 .msg = undefined,
279 .notes = try gpa.alloc(Msg, note_count),
280 };
281 return .{
282 .diags = diags,
283 .index = index,
284 };
285 }
286
287 pub fn addMissingLibraryError(
288 diags: *Diags,
289 checked_paths: []const []const u8,
290 comptime format: []const u8,
291 args: anytype,
292 ) void {
293 @branchHint(.cold);
294 const gpa = diags.gpa;
295 const io = diags.io;
296 const eu_main_msg = std.fmt.allocPrint(gpa, format, args);
297 diags.mutex.lockUncancelable(io);
298 defer diags.mutex.unlock(io);
299 addMissingLibraryErrorLockedFallible(diags, checked_paths, eu_main_msg) catch |err| switch (err) {
300 error.OutOfMemory => diags.setAllocFailureLocked(),
301 };
302 }
303
304 fn addMissingLibraryErrorLockedFallible(
305 diags: *Diags,
306 checked_paths: []const []const u8,
307 eu_main_msg: Allocator.Error![]u8,
308 ) Allocator.Error!void {
309 const gpa = diags.gpa;
310 const main_msg = try eu_main_msg;
311 errdefer gpa.free(main_msg);
312 try diags.msgs.ensureUnusedCapacity(gpa, 1);
313 const notes = try gpa.alloc(Msg, checked_paths.len);
314 errdefer gpa.free(notes);
315 for (checked_paths, notes) |path, *note| {
316 note.* = .{ .msg = try std.fmt.allocPrint(gpa, "tried {s}", .{path}) };
317 }
318 diags.msgs.appendAssumeCapacity(.{
319 .msg = main_msg,
320 .notes = notes,
321 });
322 }
323
324 pub fn addParseError(
325 diags: *Diags,
326 path: Path,
327 comptime format: []const u8,
328 args: anytype,
329 ) void {
330 @branchHint(.cold);
331 const gpa = diags.gpa;
332 const io = diags.io;
333 const eu_main_msg = std.fmt.allocPrint(gpa, format, args);
334 diags.mutex.lockUncancelable(io);
335 defer diags.mutex.unlock(io);
336 addParseErrorLockedFallible(diags, path, eu_main_msg) catch |err| switch (err) {
337 error.OutOfMemory => diags.setAllocFailureLocked(),
338 };
339 }
340
341 fn addParseErrorLockedFallible(diags: *Diags, path: Path, m: Allocator.Error![]u8) Allocator.Error!void {
342 const gpa = diags.gpa;
343 const main_msg = try m;
344 errdefer gpa.free(main_msg);
345 try diags.msgs.ensureUnusedCapacity(gpa, 1);
346 const note = try std.fmt.allocPrint(gpa, "while parsing {f}", .{path});
347 errdefer gpa.free(note);
348 const notes = try gpa.create([1]Msg);
349 errdefer gpa.destroy(notes);
350 notes.* = .{.{ .msg = note }};
351 diags.msgs.appendAssumeCapacity(.{
352 .msg = main_msg,
353 .notes = notes,
354 });
355 }
356
357 pub fn failParse(
358 diags: *Diags,
359 path: Path,
360 comptime format: []const u8,
361 args: anytype,
362 ) error{AlreadyReported} {
363 @branchHint(.cold);
364 addParseError(diags, path, format, args);
365 return error.AlreadyReported;
366 }
367
368 pub fn setAllocFailure(diags: *Diags) void {
369 @branchHint(.cold);
370 const io = diags.io;
371 diags.mutex.lockUncancelable(io);
372 defer diags.mutex.unlock(io);
373 setAllocFailureLocked(diags);
374 }
375
376 fn setAllocFailureLocked(diags: *Diags) void {
377 log.debug("memory allocation failure", .{});
378 diags.flags.alloc_failure_occurred = true;
379 }
380
381 pub fn addMessagesToBundle(diags: *const Diags, bundle: *std.zig.ErrorBundle.Wip, base: ?*File) Allocator.Error!void {
382 for (diags.msgs.items) |link_err| {
383 try bundle.addRootErrorMessage(.{
384 .msg = try link_err.string(bundle, base),
385 .notes_len = @intCast(link_err.notes.len),
386 });
387 const notes_start = try bundle.reserveNotes(@intCast(link_err.notes.len));
388 for (link_err.notes, 0..) |note, i| {
389 bundle.extra.items[notes_start + i] = @backingInt(try bundle.addErrorMessage(.{
390 .msg = try note.string(bundle, base),
391 }));
392 }
393 }
394 }
395};
396
397pub const producer_string = if (builtin.is_test) "zig test" else "zig " ++ build_options.version;
398
399pub const File = struct {
400 tag: Tag,
401
402 /// The owner of this output File.
403 comp: *Compilation,
404 emit: Path,
405
406 file: ?Io.File,
407 gc_sections: bool,
408 print_gc_sections: bool,
409 build_id: std.zig.BuildId,
410 allow_shlib_undefined: bool,
411 stack_size: u64,
412 post_prelink: bool = false,
413
414 /// Prevents other processes from clobbering files in the output directory
415 /// of this linking operation.
416 lock: ?Cache.Lock = null,
417 child_pid: ?std.process.Child.Id = null,
418
419 pub const OpenOptions = struct {
420 symbol_count_hint: u64 = 32,
421 program_code_size_hint: u64 = 256 * 1024,
422
423 /// This may depend on what symbols are found during the linking process.
424 entry: Entry,
425 /// Virtual address of the entry point procedure relative to image base.
426 entry_addr: ?u64,
427 stack_size: ?u64,
428 image_base: ?u64,
429 emit_relocs: bool,
430 z_nodelete: bool,
431 z_notext: bool,
432 z_defs: bool,
433 z_origin: bool,
434 z_nocopyreloc: bool,
435 z_now: bool,
436 z_relro: bool,
437 z_common_page_size: ?u64,
438 z_max_page_size: ?u64,
439 tsaware: bool,
440 nxcompat: bool,
441 dynamicbase: bool,
442 compress_debug_sections: std.zig.CompressDebugSections,
443 bind_global_refs_locally: bool,
444 import_symbols: bool,
445 import_table: bool,
446 export_table: bool,
447 growable_table: bool,
448 initial_memory: ?u64,
449 max_memory: ?u64,
450 object_host_name: ?[]const u8,
451 export_symbol_names: []const []const u8,
452 global_base: ?u64,
453 build_id: std.zig.BuildId,
454 hash_style: Lld.Elf.HashStyle,
455 sort_section: ?Lld.Elf.SortSection,
456 major_subsystem_version: ?u16,
457 minor_subsystem_version: ?u16,
458 gc_sections: ?bool,
459 repro: bool,
460 allow_shlib_undefined: ?bool,
461 allow_undefined_version: bool,
462 enable_new_dtags: ?bool,
463 subsystem: ?std.zig.Subsystem,
464 linker_script: ?Path,
465 version_script: ?Path,
466 soname: ?[]const u8,
467 print_gc_sections: bool,
468 print_icf_sections: bool,
469 print_map: bool,
470 nmagic: bool,
471 fatal_warnings: bool,
472
473 /// Use a wrapper function for symbol. Any undefined reference to symbol
474 /// will be resolved to __wrap_symbol. Any undefined reference to
475 /// __real_symbol will be resolved to symbol. This can be used to provide a
476 /// wrapper for a system function. The wrapper function should be called
477 /// __wrap_symbol. If it wishes to call the system function, it should call
478 /// __real_symbol.
479 symbol_wrap_set: std.array_hash_map.String(void),
480
481 compatibility_version: ?std.SemanticVersion,
482
483 // TODO: remove this. libraries are resolved by the frontend.
484 lib_directories: []const Directory,
485 framework_dirs: []const []const u8,
486 rpath_list: []const []const u8,
487
488 /// Zig compiler development linker flags.
489 /// Enable dumping of linker's state.
490 enable_link_snapshots: bool,
491
492 /// Darwin-specific linker flags:
493 /// Install name for the dylib
494 install_name: ?[]const u8,
495 /// Path to entitlements file
496 entitlements: ?Path,
497 /// size of the __PAGEZERO segment
498 pagezero_size: ?u64,
499 /// Set minimum space for future expansion of the load commands
500 headerpad_size: ?u32,
501 /// Set enough space as if all paths were MATPATHLEN
502 headerpad_max_install_names: bool,
503 /// Remove dylibs that are unreachable by the entry point or exported symbols
504 dead_strip_dylibs: bool,
505 frameworks: []const MachO.Framework,
506 darwin_sdk_layout: ?MachO.SdkLayout,
507 /// Force load all members of static archives that implement an
508 /// Objective-C class or category
509 force_load_objc: bool,
510 /// Whether local symbols should be discarded from the symbol table.
511 discard_local_symbols: bool,
512
513 /// Windows-specific linker flags:
514 /// PDB source path prefix to instruct the linker how to resolve relative
515 /// paths when consolidating CodeView streams into a single PDB file.
516 pdb_source_path: ?[]const u8,
517 /// PDB output path
518 pdb_out_path: ?[]const u8,
519 /// .def file to specify when linking
520 module_definition_file: ?[]const u8,
521
522 pub const Entry = union(enum) {
523 default,
524 disabled,
525 enabled,
526 named: []const u8,
527 };
528 };
529
530 pub const OpenError = @typeInfo(@typeInfo(@TypeOf(open)).@"fn".return_type.?).error_union.error_set;
531
532 /// Attempts incremental linking, if the file already exists. If
533 /// incremental linking fails, falls back to truncating the file and
534 /// rewriting it. A malicious file is detected as incremental link failure
535 /// and does not cause Illegal Behavior. This operation is not atomic.
536 /// `arena` is used for allocations with the same lifetime as the created File.
537 pub fn open(
538 arena: Allocator,
539 comp: *Compilation,
540 emit: Path,
541 options: OpenOptions,
542 ) !*File {
543 if (comp.config.use_lld) {
544 dev.check(.lld_linker);
545 assert(comp.zcu == null or comp.config.use_llvm);
546 // LLD does not support incremental linking.
547 const lld: *Lld = try .createEmpty(arena, comp, emit, options);
548 return &lld.base;
549 }
550 switch (Tag.fromObjectFormat(comp.root_mod.resolved_target.result.ofmt, comp.config.use_new_linker)) {
551 .plan9 => return error.UnsupportedObjectFormat,
552 inline else => |tag| {
553 dev.check(tag.devFeature());
554 const ptr = try tag.Type().open(arena, comp, emit, options);
555 return &ptr.base;
556 },
557 .lld => unreachable, // not known from ofmt
558 }
559 }
560
561 pub fn createEmpty(
562 arena: Allocator,
563 comp: *Compilation,
564 emit: Path,
565 options: OpenOptions,
566 ) !*File {
567 if (comp.config.use_lld) {
568 dev.check(.lld_linker);
569 assert(comp.zcu == null or comp.config.use_llvm);
570 const lld: *Lld = try .createEmpty(arena, comp, emit, options);
571 return &lld.base;
572 }
573 switch (Tag.fromObjectFormat(comp.root_mod.resolved_target.result.ofmt, comp.config.use_new_linker)) {
574 .plan9 => return error.UnsupportedObjectFormat,
575 inline else => |tag| {
576 dev.check(tag.devFeature());
577 const ptr = try tag.Type().createEmpty(arena, comp, emit, options);
578 return &ptr.base;
579 },
580 .lld => unreachable, // not known from ofmt
581 }
582 }
583
584 pub fn cast(base: *File, comptime tag: Tag) if (dev.env.supports(tag.devFeature())) ?*tag.Type() else ?noreturn {
585 return if (dev.env.supports(tag.devFeature()) and base.tag == tag) @fieldParentPtr("base", base) else null;
586 }
587
588 pub fn startProgress(base: *File, prog_node: std.Progress.Node) void {
589 switch (base.tag) {
590 else => {},
591 inline .elf2, .coff2 => |tag| {
592 dev.check(tag.devFeature());
593 return @as(*tag.Type(), @fieldParentPtr("base", base)).startProgress(prog_node);
594 },
595 }
596 }
597
598 pub fn endProgress(base: *File) void {
599 switch (base.tag) {
600 else => {},
601 inline .elf2, .coff2 => |tag| {
602 dev.check(tag.devFeature());
603 return @as(*tag.Type(), @fieldParentPtr("base", base)).endProgress();
604 },
605 }
606 }
607
608 pub fn makeWritable(base: *File) !void {
609 dev.check(.make_writable);
610 const comp = base.comp;
611 const gpa = comp.gpa;
612 const io = comp.io;
613 switch (base.tag) {
614 .lld => assert(base.file == null),
615 .elf, .macho, .wasm => {
616 dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker });
617 if (base.file != null) return;
618 const emit = base.emit;
619 if (base.child_pid) |pid| {
620 if (builtin.os.tag == .windows) {
621 return error.HotSwapUnavailableOnHostOperatingSystem;
622 } else {
623 // If we try to open the output file in write mode while it is running,
624 // it will return ETXTBSY. So instead, we copy the file, atomically rename it
625 // over top of the exe path, and then proceed normally. This changes the inode,
626 // avoiding the error.
627 const random_integer = r: {
628 var x: u32 = undefined;
629 io.random(@ptrCast(&x));
630 break :r x;
631 };
632 const tmp_sub_path = try std.fmt.allocPrint(gpa, "{s}-{x}", .{
633 emit.sub_path, random_integer,
634 });
635 defer gpa.free(tmp_sub_path);
636 try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, io, .{});
637 try emit.root_dir.handle.rename(tmp_sub_path, emit.root_dir.handle, emit.sub_path, io);
638 switch (builtin.os.tag) {
639 .linux => std.posix.ptrace(std.os.linux.PTRACE.ATTACH, pid, 0, 0) catch |err| {
640 log.warn("ptrace failure: {t}", .{err});
641 },
642 .maccatalyst, .macos => {
643 const macho_file = base.cast(.macho).?;
644 macho_file.ptraceAttach(pid) catch |err| {
645 log.warn("attaching failed with error: {t}", .{err});
646 };
647 },
648 .windows => unreachable,
649 else => return error.HotSwapUnavailableOnHostOperatingSystem,
650 }
651 }
652 }
653 base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write });
654 },
655 .elf2, .coff2 => if (base.file == null) {
656 dev.checkAny(&.{ .elf2_linker, .coff2_linker });
657 const mf = if (base.cast(.elf2)) |elf|
658 &elf.mf
659 else if (base.cast(.coff2)) |coff|
660 &coff.mf
661 else
662 unreachable;
663 mf.memory_map.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{
664 .mode = .read_write,
665 });
666 base.file = mf.memory_map.file;
667 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));
668 },
669 .c, .spirv => if (base.file == null) {
670 dev.checkAny(&.{ .c_linker, .spirv_linker });
671 base.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{
672 .mode = .write_only,
673 });
674 },
675 .plan9 => unreachable,
676 .spork8 => dev.check(.spork8_linker),
677 }
678 }
679
680 /// Some linkers create a separate file for debug info, which we might need to temporarily close
681 /// when moving the compilation result directory due to the host OS not allowing moving a
682 /// file/directory while a handle remains open.
683 /// Returns `true` if a debug info file was closed. In that case, `reopenDebugInfo` may be called.
684 pub fn closeDebugInfo(base: *File) bool {
685 const macho = base.cast(.macho) orelse return false;
686 return macho.closeDebugInfo();
687 }
688
689 pub fn reopenDebugInfo(base: *File) !void {
690 const macho = base.cast(.macho).?;
691 return macho.reopenDebugInfo();
692 }
693
694 pub fn makeExecutable(base: *File) !void {
695 dev.check(.make_executable);
696 const comp = base.comp;
697 const io = comp.io;
698 switch (comp.config.output_mode) {
699 .Obj => return,
700 .Lib => switch (comp.config.link_mode) {
701 .static => return,
702 .dynamic => {},
703 },
704 .Exe => {},
705 }
706 switch (base.tag) {
707 .lld => assert(base.file == null),
708 .elf => if (base.file) |f| {
709 dev.check(.elf_linker);
710 f.close(io);
711 base.file = null;
712
713 if (base.child_pid) |pid| {
714 switch (builtin.os.tag) {
715 .linux => std.posix.ptrace(std.os.linux.PTRACE.DETACH, pid, 0, 0) catch |err| {
716 log.warn("ptrace failure: {s}", .{@errorName(err)});
717 },
718 else => return error.HotSwapUnavailableOnHostOperatingSystem,
719 }
720 }
721 },
722 .macho, .wasm => if (base.file) |f| {
723 dev.checkAny(&.{ .coff_linker, .macho_linker, .plan9_linker, .wasm_linker });
724 f.close(io);
725 base.file = null;
726
727 if (base.child_pid) |pid| {
728 switch (builtin.os.tag) {
729 .maccatalyst, .macos => {
730 const macho_file = base.cast(.macho).?;
731 macho_file.ptraceDetach(pid) catch |err| {
732 log.warn("detaching failed with error: {s}", .{@errorName(err)});
733 };
734 },
735 else => return error.HotSwapUnavailableOnHostOperatingSystem,
736 }
737 }
738 },
739 .elf2, .coff2 => if (base.file) |f| {
740 const mf = if (base.cast(.elf2)) |elf|
741 &elf.mf
742 else if (base.cast(.coff2)) |coff|
743 &coff.mf
744 else
745 unreachable;
746 mf.unmap();
747 assert(mf.memory_map.file.handle == f.handle);
748 mf.memory_map.file.close(io);
749 mf.memory_map.file = undefined;
750 base.file = null;
751 },
752 .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }),
753 .plan9 => unreachable,
754 .spork8 => dev.check(.spork8_linker),
755 }
756 }
757
758 pub const DebugInfoOutput = union(enum) {
759 dwarf: *Dwarf.WipNav,
760 none,
761 };
762 pub const UpdateDebugInfoError = Dwarf.UpdateError;
763
764 /// Opaque identifier for a function currently being emitted.
765 ///
766 /// The function may be an interned function with a NAV, or it may be a lazy function.
767 ///
768 /// This type exists for type-safe interaction between codegen and link.
769 pub const AtomId = enum(u32) { _ };
770
771 /// Opaque identifier for some symbol in the output binary.
772 ///
773 /// This type exists for type-safe interaction between codegen and link.
774 pub const SymbolId = enum(u32) { _ };
775
776 /// Called from within CodeGen to retrieve the symbol index of a global symbol.
777 /// If no symbol exists yet with this name, a new undefined global symbol will
778 /// be created. This symbol may get resolved once all relocatables are (re-)linked.
779 /// Optionally, it is possible to specify where to expect the symbol defined if it
780 /// is an import.
781 pub fn getGlobalSymbol(base: *File, name: []const u8, lib_name: ?[]const u8) Error!SymbolId {
782 log.debug("getGlobalSymbol '{s}' (expected in '{?s}')", .{ name, lib_name });
783 switch (base.tag) {
784 .lld => unreachable,
785 .spirv => unreachable,
786 .c => unreachable,
787 inline else => |tag| {
788 dev.check(tag.devFeature());
789 return @as(*tag.Type(), @fieldParentPtr("base", base)).getGlobalSymbol(name, lib_name);
790 },
791 }
792 }
793
794 /// Asserts that the ZCU is not using the LLVM backend.
795 fn updateNav(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) Error!void {
796 assert(base.comp.zcu.?.llvm_object == null);
797 const nav = pt.zcu.intern_pool.getNav(nav_index);
798 assert(nav.resolved.?.value != .none);
799
800 switch (base.tag) {
801 .lld => unreachable,
802 .plan9 => unreachable,
803 inline else => |tag| {
804 dev.check(tag.devFeature());
805 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateNav(pt, nav_index);
806 },
807 }
808 }
809
810 /// Never called when LLVM is codegenning the ZCU.
811 fn updateContainerType(base: *File, pt: Zcu.PerThread, ty: InternPool.Index, success: bool) Error!void {
812 assert(base.comp.zcu.?.llvm_object == null);
813 switch (base.tag) {
814 .lld => unreachable,
815 else => {},
816 inline .elf, .c => |tag| {
817 dev.check(tag.devFeature());
818 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateContainerType(pt, ty, success);
819 },
820 }
821 }
822
823 /// Never called when LLVM is codegenning the ZCU.
824 fn clearContainerType(base: *File, pt: Zcu.PerThread, ty: InternPool.Index) Error!void {
825 assert(base.comp.zcu.?.llvm_object == null);
826 switch (base.tag) {
827 .lld => unreachable,
828 else => {},
829 inline .elf => |tag| {
830 dev.check(tag.devFeature());
831 return @as(*tag.Type(), @fieldParentPtr("base", base)).clearContainerType(pt, ty);
832 },
833 }
834 }
835
836 /// The active tag of `mir` is determined by the backend used for the module this function is in.
837 /// Never called when LLVM is codegenning the ZCU.
838 fn updateFunc(
839 base: *File,
840 pt: Zcu.PerThread,
841 func_index: InternPool.Index,
842 /// This is owned by the caller, but the callee is permitted to mutate it provided
843 /// that `mir.deinit` remains legal for the caller. For instance, the callee can
844 /// take ownership of an embedded slice and replace it with `&.{}` in `mir`.
845 mir: *codegen.AnyMir,
846 ) Error!void {
847 assert(base.comp.zcu.?.llvm_object == null);
848 switch (base.tag) {
849 .lld => unreachable,
850 .plan9 => unreachable,
851 inline else => |tag| {
852 dev.check(tag.devFeature());
853 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateFunc(pt, func_index, mir);
854 },
855 }
856 }
857
858 /// On an incremental update, fixup the line number of all `Nav`s at the given `TrackedInst`, because
859 /// its line number has changed. The ZIR instruction `ti_id` has tag `.declaration`.
860 /// Never called when LLVM is codegenning the ZCU.
861 fn updateLineNumber(base: *File, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) Error!void {
862 assert(base.comp.zcu.?.llvm_object == null);
863 {
864 const ti = ti_id.resolveFull(&pt.zcu.intern_pool).?;
865 const file = pt.zcu.fileByIndex(ti.file);
866 const inst = file.zir.?.instructions.get(@backingInt(ti.inst));
867 assert(inst.tag == .declaration);
868 }
869
870 switch (base.tag) {
871 .lld => unreachable,
872 .spirv => {},
873 .plan9 => unreachable,
874 .elf2, .coff2 => {},
875 inline else => |tag| {
876 dev.check(tag.devFeature());
877 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateLineNumber(pt, ti_id);
878 },
879 }
880 }
881
882 pub fn releaseLock(base: *File) void {
883 const comp = base.comp;
884 const io = comp.io;
885 if (base.lock) |*lock| {
886 lock.release(io);
887 base.lock = null;
888 }
889 }
890
891 pub fn toOwnedLock(self: *File) Cache.Lock {
892 const lock = self.lock.?;
893 self.lock = null;
894 return lock;
895 }
896
897 pub fn destroy(base: *File) void {
898 const io = base.comp.io;
899 base.releaseLock();
900 if (base.file) |f| f.close(io);
901 switch (base.tag) {
902 .plan9 => unreachable,
903 inline else => |tag| {
904 dev.check(tag.devFeature());
905 @as(*tag.Type(), @fieldParentPtr("base", base)).deinit();
906 },
907 }
908 }
909
910 pub fn idle(base: *File, tid: Zcu.PerThread.Id) Error!bool {
911 switch (base.tag) {
912 else => return false,
913 inline .elf2, .coff2 => |tag| {
914 dev.check(tag.devFeature());
915 return @as(*tag.Type(), @fieldParentPtr("base", base)).idle(tid);
916 },
917 }
918 }
919
920 pub fn updateErrorData(base: *File, pt: Zcu.PerThread) Error!void {
921 switch (base.tag) {
922 else => {},
923 inline .elf2, .coff2 => |tag| {
924 dev.check(tag.devFeature());
925 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateErrorData(pt);
926 },
927 }
928 }
929
930 /// Commit pending changes and write headers. Takes into account final output mode.
931 /// `arena` has the lifetime of the call to `Compilation.update`.
932 pub fn flush(base: *File, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) Error!void {
933 crash_report.LinkerOp.start(base, tid);
934 defer crash_report.LinkerOp.stop(base, tid);
935
936 const comp = base.comp;
937 const io = comp.io;
938 if (comp.clang_preprocessor_mode == .yes or comp.clang_preprocessor_mode == .pch) {
939 dev.check(.clang_command);
940 const emit = base.emit;
941 // TODO: avoid extra link step when it's just 1 object file (the `zig cc -c` case)
942 // Until then, we do `lld -r -o output.o input.o` even though the output is the same
943 // as the input. For the preprocessing case (`zig cc -E -o foo`) we copy the file
944 // to the final location. See also the corresponding TODO in Coff linking.
945 assert(comp.c_objects.items.len == 1);
946 const the_key = comp.c_objects.items[0];
947 const cached_pp_file_path = the_key.status.success.object_path;
948 Io.Dir.copyFile(
949 cached_pp_file_path.root_dir.handle,
950 cached_pp_file_path.sub_path,
951 emit.root_dir.handle,
952 emit.sub_path,
953 io,
954 .{},
955 ) catch |err| {
956 const diags = &base.comp.link_diags;
957 return diags.fail("failed to copy '{f}' to '{f}': {t}", .{
958 std.fmt.alt(@as(Path, cached_pp_file_path), .formatEscapeChar),
959 std.fmt.alt(@as(Path, emit), .formatEscapeChar),
960 err,
961 });
962 };
963 return;
964 }
965 assert(base.post_prelink);
966 switch (base.tag) {
967 .plan9 => unreachable,
968 inline else => |tag| {
969 dev.check(tag.devFeature());
970 return @as(*tag.Type(), @fieldParentPtr("base", base)).flush(arena, tid, prog_node);
971 },
972 }
973 }
974
975 /// This is called once per update, before `flush`.
976 ///
977 /// `export_indices` contains the index of every export from the ZCU which should be performed
978 /// on this update. "Removal" of exports is signaled implicitly by the export being in this
979 /// slice on one update but not the next.
980 ///
981 /// Never called when LLVM is codegenning the ZCU.
982 pub fn updateExports(
983 base: *File,
984 pt: Zcu.PerThread,
985 export_indices: []const Zcu.Export.Index,
986 ) Error!void {
987 assert(base.comp.zcu.?.llvm_object == null);
988
989 crash_report.LinkerOp.start(base, pt.tid);
990 defer crash_report.LinkerOp.stop(base, pt.tid);
991
992 switch (base.tag) {
993 .lld => unreachable,
994 .plan9 => unreachable,
995 inline else => |tag| {
996 dev.check(tag.devFeature());
997 return @as(*tag.Type(), @fieldParentPtr("base", base)).updateExports(pt, export_indices);
998 },
999 }
1000 }
1001
1002 pub const RelocInfo = struct {
1003 parent: Parent,
1004 offset: u64,
1005 addend: u32,
1006
1007 pub const Parent = union(enum) {
1008 none,
1009 atom_index: AtomId,
1010 debug_output: DebugInfoOutput,
1011 };
1012 };
1013
1014 /// Get allocated `Nav`'s address in virtual memory.
1015 /// The linker is passed information about the containing atom, `parent_atom_index`, and offset within it's
1016 /// memory buffer, `offset`, so that it can make a note of potential relocation sites, should the
1017 /// `Nav`'s address was not yet resolved, or the containing atom gets moved in virtual memory.
1018 /// May be called before or after updateFunc/updateNav therefore it is up to the linker to allocate
1019 /// the block/atom.
1020 /// Never called when LLVM is codegenning the ZCU.
1021 pub fn getNavVAddr(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: RelocInfo) Error!u64 {
1022 assert(base.comp.zcu.?.llvm_object == null);
1023
1024 switch (base.tag) {
1025 .lld => unreachable,
1026 .c => unreachable,
1027 .spirv => unreachable,
1028 .wasm => unreachable,
1029 .plan9 => unreachable,
1030 .spork8 => unreachable,
1031 inline else => |tag| {
1032 dev.check(tag.devFeature());
1033 return @as(*tag.Type(), @fieldParentPtr("base", base)).getNavVAddr(pt, nav_index, reloc_info);
1034 },
1035 }
1036 }
1037
1038 /// Never called when LLVM is codegenning the ZCU.
1039 pub fn lowerUav(
1040 base: *File,
1041 pt: Zcu.PerThread,
1042 decl_val: InternPool.Index,
1043 decl_align: InternPool.Alignment,
1044 ) Error!SymbolId {
1045 assert(base.comp.zcu.?.llvm_object == null);
1046
1047 switch (base.tag) {
1048 .lld => unreachable,
1049 .c => unreachable,
1050 .spirv => unreachable,
1051 .wasm => unreachable,
1052 .plan9 => unreachable,
1053 .spork8 => unreachable,
1054 inline else => |tag| {
1055 dev.check(tag.devFeature());
1056 return @as(*tag.Type(), @fieldParentPtr("base", base)).lowerUav(pt, decl_val, decl_align);
1057 },
1058 }
1059 }
1060
1061 /// Never called when LLVM is codegenning the ZCU.
1062 pub fn getUavVAddr(base: *File, decl_val: InternPool.Index, reloc_info: RelocInfo) Error!u64 {
1063 assert(base.comp.zcu.?.llvm_object == null);
1064
1065 switch (base.tag) {
1066 .lld => unreachable,
1067 .c => unreachable,
1068 .spirv => unreachable,
1069 .wasm => unreachable,
1070 .plan9 => unreachable,
1071 .spork8 => unreachable,
1072 inline else => |tag| {
1073 dev.check(tag.devFeature());
1074 return @as(*tag.Type(), @fieldParentPtr("base", base)).getUavVAddr(decl_val, reloc_info);
1075 },
1076 }
1077 }
1078
1079 pub const DumpResult = enum {
1080 unimplemented,
1081 needs_extensions,
1082 disabled,
1083 enabled,
1084 };
1085
1086 pub fn dump(base: *File, w: *Io.Writer, tid: Zcu.PerThread.Id) !DumpResult {
1087 if (!build_options.enable_debug_extensions) return .not_built;
1088 switch (base.tag) {
1089 .elf,
1090 .macho,
1091 .c,
1092 .wasm,
1093 .spirv,
1094 .plan9,
1095 .lld,
1096 .spork8,
1097 => return .unimplemented,
1098 inline else => |tag| {
1099 dev.check(tag.devFeature());
1100 return @as(*tag.Type(), @fieldParentPtr("base", base)).dump(w, tid);
1101 },
1102 }
1103 }
1104
1105 /// Opens a path as an object file and parses it into the linker.
1106 fn openLoadObject(base: *File, path: Path) anyerror!void {
1107 if (base.tag == .lld) return;
1108 const io = base.comp.io;
1109 const diags = &base.comp.link_diags;
1110 const input = try openObjectInput(io, diags, path);
1111 errdefer input.object.file.close(io);
1112 try loadInput(base, input);
1113 }
1114
1115 /// Opens a path as a static library and parses it into the linker.
1116 fn openLoadArchive(base: *File, path: Path, must_link: bool) anyerror!void {
1117 if (base.tag == .lld) return;
1118 const io = base.comp.io;
1119 const archive = try openObject(io, path, must_link, false);
1120 errdefer archive.file.close(io);
1121 try loadInput(base, .{ .archive = archive });
1122 }
1123
1124 /// Opens a path as a static library and parses it into the linker. Allows GNU ld scripts.
1125 fn openLoadArchiveQuery(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void {
1126 if (base.tag == .lld) return;
1127 const io = base.comp.io;
1128 const archive = try openObject(io, path, query.must_link, query.hidden);
1129 errdefer archive.file.close(io);
1130 loadInput(base, .{ .archive = archive }) catch |err| switch (err) {
1131 error.BadMagic, error.UnexpectedEndOfFile => {
1132 if (base.tag != .elf and base.tag != .elf2) return err;
1133 try loadGnuLdScript(base, path, query, archive.file);
1134 archive.file.close(io);
1135 return;
1136 },
1137 else => return err,
1138 };
1139 }
1140
1141 /// Opens a path as a shared library and parses it into the linker.
1142 /// Handles GNU ld scripts.
1143 fn openLoadDso(base: *File, path: Path, query: UnresolvedInput.Query) anyerror!void {
1144 if (base.tag == .lld) return;
1145 const io = base.comp.io;
1146 const dso = try openDso(io, path, query.needed, query.weak, query.reexport);
1147 errdefer dso.file.close(io);
1148 loadInput(base, .{ .dso = dso }) catch |err| switch (err) {
1149 error.BadMagic, error.UnexpectedEndOfFile => {
1150 if (base.tag != .elf and base.tag != .elf2) return err;
1151 try loadGnuLdScript(base, path, query, dso.file);
1152 dso.file.close(io);
1153 return;
1154 },
1155 else => return err,
1156 };
1157 }
1158
1159 fn loadGnuLdScript(base: *File, path: Path, parent_query: UnresolvedInput.Query, file: Io.File) anyerror!void {
1160 const comp = base.comp;
1161 const io = comp.io;
1162 const diags = &comp.link_diags;
1163 const gpa = comp.gpa;
1164 const stat = try file.stat(io);
1165 const size = std.math.cast(u32, stat.size) orelse return error.FileTooBig;
1166 const buf = try gpa.alloc(u8, size);
1167 defer gpa.free(buf);
1168 const n = try file.readPositionalAll(io, buf, 0);
1169 if (buf.len != n) return error.UnexpectedEndOfFile;
1170 var ld_script = try LdScript.parse(gpa, diags, path, buf);
1171 defer ld_script.deinit(gpa);
1172 for (ld_script.args) |arg| {
1173 const query: UnresolvedInput.Query = .{
1174 .needed = arg.needed or parent_query.needed,
1175 .weak = parent_query.weak,
1176 .reexport = parent_query.reexport,
1177 .preferred_mode = parent_query.preferred_mode,
1178 .search_strategy = parent_query.search_strategy,
1179 .allow_so_scripts = parent_query.allow_so_scripts,
1180 };
1181 if (mem.startsWith(u8, arg.path, "-l")) {
1182 @panic("TODO");
1183 } else {
1184 if (fs.path.isAbsolute(arg.path)) {
1185 const new_path = Path.initCwd(path: {
1186 comp.mutex.lockUncancelable(io);
1187 defer comp.mutex.unlock(io);
1188 break :path try comp.arena.dupe(u8, arg.path);
1189 });
1190 switch (Compilation.classifyFileExt(arg.path)) {
1191 .shared_library => try openLoadDso(base, new_path, query),
1192 .object => try openLoadObject(base, new_path),
1193 .static_library => try openLoadArchiveQuery(base, new_path, query),
1194 else => diags.addParseError(path, "GNU ld script references file with unrecognized extension: {s}", .{arg.path}),
1195 }
1196 } else {
1197 @panic("TODO");
1198 }
1199 }
1200 }
1201 }
1202
1203 pub fn loadInput(base: *File, input: Input) anyerror!void {
1204 if (base.tag == .lld) return;
1205 assert(!base.post_prelink);
1206
1207 switch (base.tag) {
1208 inline .coff2, .elf, .elf2, .wasm, .spirv => |tag| {
1209 dev.check(tag.devFeature());
1210 return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input);
1211 },
1212 else => {},
1213 }
1214 }
1215
1216 /// Called when all linker inputs have been sent via `loadInput`. After
1217 /// this, `loadInput` will not be called anymore.
1218 pub fn prelink(base: *File) Error!void {
1219 // The guard on this assertion is a temporary hack to make the LLVM backend with LLD work with
1220 // `-fincremental`. This works only because `File.Lld` does nothing in prelink.
1221 // Related: https://codeberg.org/ziglang/zig/issues/32081
1222 if (base.tag != .lld) {
1223 assert(!base.post_prelink);
1224 }
1225
1226 switch (base.tag) {
1227 inline .elf2, .coff2, .wasm, .c => |tag| {
1228 dev.check(tag.devFeature());
1229 try @as(*tag.Type(), @fieldParentPtr("base", base)).prelink(base.comp.link_prog_node);
1230 },
1231 else => base.comp.link_prog_node.completeOne(),
1232 }
1233
1234 base.post_prelink = true;
1235 }
1236
1237 /// Legacy function for old linker code
1238 pub fn copyRangeAll(base: *File, old_offset: u64, new_offset: u64, size: u64) !void {
1239 const comp = base.comp;
1240 const io = comp.io;
1241 const file = base.file.?;
1242 return copyRangeAll2(io, file, file, old_offset, new_offset, size);
1243 }
1244
1245 /// Legacy function for old linker code
1246 pub fn copyRangeAll2(io: Io, src_file: Io.File, dst_file: Io.File, old_offset: u64, new_offset: u64, size: u64) !void {
1247 var write_buffer: [2048]u8 = undefined;
1248 var file_reader = src_file.reader(io, &.{});
1249 file_reader.pos = old_offset;
1250 var file_writer = dst_file.writer(io, &write_buffer);
1251 file_writer.pos = new_offset;
1252 const size_u = std.math.cast(usize, size) orelse return error.Overflow;
1253 const n = file_writer.interface.sendFileAll(&file_reader, .limited(size_u)) catch |err| switch (err) {
1254 error.ReadFailed => switch (file_reader.err.?) {
1255 error.ConnectionResetByPeer => return error.Unexpected, // not a socket
1256 error.SocketUnconnected => return error.Unexpected, // not a socket
1257 else => |e| return e,
1258 },
1259 error.WriteFailed => return file_writer.err.?,
1260 };
1261 assert(n == size_u);
1262 file_writer.interface.flush() catch |err| switch (err) {
1263 error.WriteFailed => return file_writer.err.?,
1264 };
1265 }
1266
1267 pub const Tag = enum {
1268 coff2,
1269 elf,
1270 elf2,
1271 macho,
1272 c,
1273 wasm,
1274 spirv,
1275 spork8,
1276 plan9,
1277 lld,
1278
1279 pub fn Type(comptime tag: Tag) type {
1280 return switch (tag) {
1281 .coff2 => Coff2,
1282 .elf => Elf,
1283 .elf2 => Elf2,
1284 .macho => MachO,
1285 .c => C,
1286 .wasm => Wasm,
1287 .spirv => SpirV,
1288 .lld => Lld,
1289 .plan9 => comptime unreachable,
1290 .spork8 => Spork8,
1291 };
1292 }
1293
1294 fn fromObjectFormat(ofmt: std.Target.ObjectFormat, use_new_linker: bool) Tag {
1295 return switch (ofmt) {
1296 .coff => .coff2,
1297 .elf => if (use_new_linker) .elf2 else .elf,
1298 .macho => .macho,
1299 .wasm => .wasm,
1300 .plan9 => .plan9,
1301 .c => .c,
1302 .spirv => .spirv,
1303 .hex => @panic("TODO implement hex object format"),
1304 // This may seem surprising at first, but with a little massaging, the spork8 linker
1305 // could and probably should be generalized into a "raw linker" which is used to output
1306 // bare machine code for any architecture for which a corresponding backend exists.
1307 .raw => .spork8,
1308 };
1309 }
1310
1311 pub fn devFeature(tag: Tag) dev.Feature {
1312 return @field(dev.Feature, @tagName(tag) ++ "_linker");
1313 }
1314 };
1315
1316 pub const LazySymbol = struct {
1317 pub const Kind = enum { code, const_data };
1318
1319 kind: Kind,
1320 ty: InternPool.Index,
1321 };
1322
1323 pub fn determinePermissions(
1324 output_mode: std.lang.OutputMode,
1325 link_mode: std.lang.LinkMode,
1326 ) Io.File.Permissions {
1327 // On common systems with a 0o022 umask, 0o777 will still result in a file created
1328 // with 0o755 permissions, but it works appropriately if the system is configured
1329 // more leniently. As another data point, C's fopen seems to open files with the
1330 // 666 mode.
1331 const executable_mode: Io.File.Permissions = if (builtin.target.os.tag == .windows or std.posix.mode_t == u0)
1332 .default_file
1333 else
1334 .fromMode(0o777);
1335
1336 switch (output_mode) {
1337 .Lib => return switch (link_mode) {
1338 .dynamic => executable_mode,
1339 .static => .default_file,
1340 },
1341 .Exe => return executable_mode,
1342 .Obj => return .default_file,
1343 }
1344 }
1345
1346 pub fn isStatic(self: File) bool {
1347 return self.comp.config.link_mode == .static;
1348 }
1349
1350 pub fn isObject(self: File) bool {
1351 const output_mode = self.comp.config.output_mode;
1352 return output_mode == .Obj;
1353 }
1354
1355 pub fn isExe(self: File) bool {
1356 const output_mode = self.comp.config.output_mode;
1357 return output_mode == .Exe;
1358 }
1359
1360 pub fn isStaticLib(self: File) bool {
1361 const output_mode = self.comp.config.output_mode;
1362 return output_mode == .Lib and self.isStatic();
1363 }
1364
1365 pub fn isRelocatable(self: File) bool {
1366 return self.isObject() or self.isStaticLib();
1367 }
1368
1369 pub fn isDynLib(self: File) bool {
1370 const output_mode = self.comp.config.output_mode;
1371 return output_mode == .Lib and !self.isStatic();
1372 }
1373
1374 pub fn cgFail(
1375 base: *File,
1376 nav_index: InternPool.Nav.Index,
1377 comptime format: []const u8,
1378 args: anytype,
1379 ) Zcu.CodegenFailError {
1380 @branchHint(.cold);
1381 return base.comp.zcu.?.codegenFail(nav_index, format, args);
1382 }
1383
1384 pub const Lld = @import("link/Lld.zig");
1385 pub const C = @import("link/C.zig");
1386 pub const Coff2 = @import("link/Coff.zig");
1387 pub const Spork8 = @import("link/Spork8.zig");
1388 pub const Elf = @import("link/Elf.zig");
1389 pub const Elf2 = @import("link/Elf2.zig");
1390 pub const MachO = @import("link/MachO.zig");
1391 pub const SpirV = @import("link/SpirV.zig");
1392 pub const Wasm = @import("link/Wasm.zig");
1393 pub const Dwarf = @import("link/Dwarf.zig");
1394};
1395
1396pub const PrelinkTask = union(enum) {
1397 /// Loads the objects, shared objects, and archives that are already
1398 /// known from the command line.
1399 load_explicitly_provided,
1400 /// Loads the shared objects and archives by resolving
1401 /// `target_util.libcFullLinkFlags()` against the host libc
1402 /// installation.
1403 load_host_libc,
1404 /// Tells the linker to load an object file by path.
1405 load_object: Path,
1406 /// Tells the linker to load a static library by path.
1407 load_archive: struct {
1408 path: Path,
1409 must_link: bool,
1410 },
1411 /// Tells the linker to load a shared library, possibly one that is a
1412 /// GNU ld script.
1413 load_dso: Path,
1414};
1415pub const ZcuTask = union(enum) {
1416 /// Write the constant value for a Decl to the output file.
1417 link_nav: InternPool.Nav.Index,
1418 /// Write the machine code for a function to the output file.
1419 link_func: Zcu.CodegenTaskPool.Index,
1420 /// This struct/union/enum type has finished type resolution (successfully or otherwise), so the
1421 /// linker can now lower debug information for this type (and any structural types which depend
1422 /// on it, such as `?T`, `struct { T }`, `[2]T`, etc).
1423 debug_update_container_type: struct {
1424 ty: InternPool.Index,
1425 success: bool,
1426 },
1427 debug_update_line_number: InternPool.TrackedInst.Index,
1428};
1429
1430pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
1431 const io = comp.io;
1432 const diags = &comp.link_diags;
1433 const base = comp.bin_file orelse {
1434 comp.link_prog_node.completeOne();
1435 return;
1436 };
1437
1438 // The guard on this assertion is a temporary hack to make the LLVM backend with LLD work with
1439 // `-fincremental`. This works only because `File.Lld` does nothing in prelink.
1440 // Related: https://codeberg.org/ziglang/zig/issues/32081
1441 if (base.tag != .lld) {
1442 assert(!base.post_prelink);
1443 }
1444
1445 var timer = comp.startTimer();
1446 defer if (timer.finish(io)) |ns| {
1447 comp.mutex.lockUncancelable(io);
1448 defer comp.mutex.unlock(io);
1449 comp.time_report.?.stats.cpu_ns_link += ns;
1450 };
1451
1452 switch (task) {
1453 .load_explicitly_provided => {
1454 const prog_node = comp.link_prog_node.start("Parse Inputs", comp.link_inputs.len);
1455 defer prog_node.end();
1456 for (comp.link_inputs) |input| {
1457 base.loadInput(input) catch |err| switch (err) {
1458 error.AlreadyReported => return, // error reported via diags
1459 else => |e| switch (input) {
1460 .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {s}", .{@errorName(e)}),
1461 .object => |obj| diags.addParseError(obj.path, "failed to parse object: {s}", .{@errorName(e)}),
1462 .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {s}", .{@errorName(e)}),
1463 .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {s}", .{@errorName(e)}),
1464 .dso_exact => diags.addError("failed to handle dso_exact: {s}", .{@errorName(e)}),
1465 },
1466 };
1467 prog_node.completeOne();
1468 }
1469 },
1470 .load_host_libc => {
1471 const prog_node = comp.link_prog_node.start("Parse Host libc", 0);
1472 defer prog_node.end();
1473
1474 const target = &comp.root_mod.resolved_target.result;
1475 const flags = target_util.libcFullLinkFlags(target);
1476 const libc_installation = comp.libc_installation.?;
1477 const crt_dir = libc_installation.crt_dir.?;
1478 const sep = std.fs.path.sep_str;
1479 for (flags) |flag| {
1480 assert(mem.startsWith(u8, flag, "-l"));
1481 const lib_name = flag["-l".len..];
1482 switch (comp.config.link_mode) {
1483 .dynamic => {
1484 const dso_path = Path.initCwd(
1485 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1486 crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix(),
1487 }) catch return diags.setAllocFailure(),
1488 );
1489 base.openLoadDso(dso_path, .{
1490 .preferred_mode = .dynamic,
1491 .search_strategy = .paths_first,
1492 }) catch |err| switch (err) {
1493 error.FileNotFound => {
1494 // Also try static.
1495 const archive_path = Path.initCwd(
1496 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1497 crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
1498 }) catch return diags.setAllocFailure(),
1499 );
1500 base.openLoadArchiveQuery(archive_path, .{
1501 .preferred_mode = .dynamic,
1502 .search_strategy = .paths_first,
1503 }) catch |archive_err| switch (archive_err) {
1504 error.AlreadyReported => return, // error reported via diags
1505 else => |e| diags.addParseError(dso_path, "failed to parse archive {f}: {s}", .{ archive_path, @errorName(e) }),
1506 };
1507 },
1508 error.AlreadyReported => return, // error reported via diags
1509 else => |e| diags.addParseError(dso_path, "failed to parse shared library: {s}", .{@errorName(e)}),
1510 };
1511 },
1512 .static => {
1513 const path = Path.initCwd(
1514 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{
1515 crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(),
1516 }) catch return diags.setAllocFailure(),
1517 );
1518 // glibc sometimes makes even archive files GNU ld scripts.
1519 base.openLoadArchiveQuery(path, .{
1520 .preferred_mode = .static,
1521 .search_strategy = .no_fallback,
1522 }) catch |err| switch (err) {
1523 error.AlreadyReported => return, // error reported via diags
1524 else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
1525 };
1526 },
1527 }
1528 }
1529
1530 if (target.os.tag == .windows and target.abi == .msvc) {
1531 const inputs: []const struct {
1532 dir: enum { crt, msvc_lib, kernel32_lib },
1533 name: []const u8,
1534 } = switch (comp.config.link_mode) {
1535 .dynamic => &.{
1536 .{ .dir = .msvc_lib, .name = "msvcrt.lib" },
1537 .{ .dir = .msvc_lib, .name = "vcruntime.lib" },
1538 .{ .dir = .msvc_lib, .name = "legacy_stdio_definitions.lib" },
1539 .{ .dir = .crt, .name = "ucrt.lib" },
1540 .{ .dir = .kernel32_lib, .name = "kernel32.lib" },
1541 .{ .dir = .kernel32_lib, .name = "ntdll.lib" },
1542 },
1543 .static => &.{
1544 .{ .dir = .msvc_lib, .name = "libcmt.lib" },
1545 .{ .dir = .msvc_lib, .name = "libvcruntime.lib" },
1546 .{ .dir = .msvc_lib, .name = "legacy_stdio_definitions.lib" },
1547 .{ .dir = .crt, .name = "libucrt.lib" },
1548 .{ .dir = .kernel32_lib, .name = "kernel32.lib" },
1549 .{ .dir = .kernel32_lib, .name = "ntdll.lib" },
1550 },
1551 };
1552
1553 for (inputs) |lib| {
1554 const path = Path.initCwd(
1555 std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}", .{
1556 switch (lib.dir) {
1557 .crt => crt_dir,
1558 .msvc_lib => libc_installation.msvc_lib_dir.?,
1559 .kernel32_lib => libc_installation.kernel32_lib_dir.?,
1560 },
1561 lib.name,
1562 }) catch return diags.setAllocFailure(),
1563 );
1564 if (std.mem.endsWith(u8, lib.name, "lib")) {
1565 base.openLoadArchive(path, false) catch |err| switch (err) {
1566 error.LinkFailure => return, // error reported via diags
1567 else => |e| diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(e)}),
1568 };
1569 } else {
1570 base.openLoadObject(path) catch |err| switch (err) {
1571 error.LinkFailure => return, // error reported via diags
1572 else => |e| diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}),
1573 };
1574 }
1575 }
1576 }
1577 },
1578 .load_object => |path| {
1579 const prog_node = comp.link_prog_node.start("Parse Object", 0);
1580 defer prog_node.end();
1581 base.openLoadObject(path) catch |err| switch (err) {
1582 error.AlreadyReported => return, // error reported via diags
1583 else => |e| diags.addParseError(path, "failed to parse object: {s}", .{@errorName(e)}),
1584 };
1585 },
1586 .load_archive => |load_archive| {
1587 const prog_node = comp.link_prog_node.start("Parse Archive", 0);
1588 defer prog_node.end();
1589 base.openLoadArchive(load_archive.path, load_archive.must_link) catch |err| switch (err) {
1590 error.AlreadyReported => return, // error reported via link_diags
1591 else => |e| diags.addParseError(load_archive.path, "failed to parse archive: {s}", .{@errorName(e)}),
1592 };
1593 },
1594 .load_dso => |path| {
1595 const prog_node = comp.link_prog_node.start("Parse Shared Library", 0);
1596 defer prog_node.end();
1597 base.openLoadDso(path, .{
1598 .preferred_mode = .dynamic,
1599 .search_strategy = .paths_first,
1600 }) catch |err| switch (err) {
1601 error.AlreadyReported => return, // error reported via link_diags
1602 else => |e| diags.addParseError(path, "failed to parse shared library: {s}", .{@errorName(e)}),
1603 };
1604 },
1605 }
1606}
1607pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void {
1608 const io = comp.io;
1609 const diags = &comp.link_diags;
1610 const zcu = comp.zcu.?;
1611 const ip = &zcu.intern_pool;
1612 const active = zcu.activate(tid);
1613 defer active.deactivate();
1614 const pt = active.pt;
1615
1616 var timer = comp.startTimer();
1617
1618 const maybe_nav: ?InternPool.Nav.Index = switch (task) {
1619 .link_nav => |nav_index| nav: {
1620 const fqn_slice = ip.getNav(nav_index).fqn.toSlice(ip);
1621 const nav_prog_node = comp.link_prog_node.start(fqn_slice, 0);
1622 defer nav_prog_node.end();
1623 if (zcu.llvm_object) |llvm_object| {
1624 llvm_object.updateNav(pt, nav_index) catch |err| switch (err) {
1625 error.OutOfMemory => diags.setAllocFailure(),
1626 };
1627 } else if (comp.bin_file) |lf| {
1628 lf.updateNav(pt, nav_index) catch |err| switch (err) {
1629 error.Canceled => io.recancel(),
1630 error.AlreadyReported => return,
1631 error.OutOfMemory => diags.setAllocFailure(),
1632 };
1633 }
1634 break :nav nav_index;
1635 },
1636 .link_func => |codegen_task| nav: {
1637 timer.pause(io);
1638 const func, var mir = codegen_task.wait(&zcu.codegen_task_pool, zcu) catch |err| switch (err) {
1639 error.Canceled, error.AlreadyReported => {
1640 comp.link_prog_node.completeOne();
1641 return;
1642 },
1643 };
1644 defer mir.deinit(zcu);
1645 timer.@"resume"(io);
1646
1647 const nav = zcu.funcInfo(func).owner_nav;
1648 const fqn_slice = ip.getNav(nav).fqn.toSlice(ip);
1649
1650 const nav_prog_node = comp.link_prog_node.start(fqn_slice, 0);
1651 defer nav_prog_node.end();
1652
1653 assert(zcu.llvm_object == null); // LLVM codegen doesn't produce MIR
1654 if (comp.bin_file) |lf| {
1655 lf.updateFunc(pt, func, &mir) catch |err| switch (err) {
1656 error.Canceled => io.recancel(),
1657 error.AlreadyReported => return,
1658 error.OutOfMemory => return diags.setAllocFailure(),
1659 };
1660 }
1661 break :nav ip.indexToKey(func).func.owner_nav;
1662 },
1663 .debug_update_container_type => |container_update| nav: {
1664 const name = Type.fromInterned(container_update.ty).containerTypeName(ip).toSlice(ip);
1665 const ty_prog_node = comp.link_prog_node.start(name, 0);
1666 defer ty_prog_node.end();
1667 if (zcu.llvm_object) |llvm_object| {
1668 llvm_object.updateContainerType(pt, container_update.ty, container_update.success) catch |err| switch (err) {
1669 error.OutOfMemory => diags.setAllocFailure(),
1670 };
1671 } else {
1672 if (comp.bin_file) |lf| {
1673 lf.updateContainerType(pt, container_update.ty, container_update.success) catch |err| switch (err) {
1674 error.OutOfMemory => diags.setAllocFailure(),
1675 error.Canceled => io.recancel(),
1676 error.AlreadyReported => {},
1677 };
1678 }
1679 }
1680 break :nav null;
1681 },
1682 .debug_update_line_number => |ti| nav: {
1683 const nav_prog_node = comp.link_prog_node.start("Update line number", 0);
1684 defer nav_prog_node.end();
1685 if (pt.zcu.llvm_object == null) {
1686 if (comp.bin_file) |lf| {
1687 lf.updateLineNumber(pt, ti) catch |err| switch (err) {
1688 error.OutOfMemory => diags.setAllocFailure(),
1689 else => |e| log.err("update line number failed: {s}", .{@errorName(e)}),
1690 };
1691 }
1692 }
1693 break :nav null;
1694 },
1695 };
1696
1697 if (timer.finish(io)) |ns_link| report_time: {
1698 comp.mutex.lockUncancelable(io);
1699 defer comp.mutex.unlock(io);
1700 const tr = &zcu.comp.time_report.?;
1701 tr.stats.cpu_ns_link += ns_link;
1702 if (maybe_nav) |nav| {
1703 const zir_decl = ip.getNav(nav).srcInst(ip);
1704 const gop = tr.decl_link_ns.getOrPut(zcu.gpa, zir_decl) catch |err| switch (err) {
1705 error.OutOfMemory => {
1706 zcu.comp.setAllocFailure();
1707 break :report_time;
1708 },
1709 };
1710 if (!gop.found_existing) gop.value_ptr.* = 0;
1711 gop.value_ptr.* += ns_link;
1712 }
1713 }
1714}
1715pub fn doIdleTask(comp: *Compilation, tid: Zcu.PerThread.Id) Error!bool {
1716 return if (comp.bin_file) |lf| lf.idle(tid) else false;
1717}
1718/// After the main pipeline is done, but before flush, the compilation may need to link one final
1719/// `Nav` into the binary: the `builtin.test_functions` value. Since the link thread isn't running
1720/// by then, we expose this function which can be called directly.
1721pub fn linkTestFunctionsNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) void {
1722 const zcu = pt.zcu;
1723 const comp = zcu.comp;
1724 const diags = &comp.link_diags;
1725 if (zcu.llvm_object) |llvm_object| {
1726 llvm_object.updateNav(pt, nav_index) catch |err| switch (err) {
1727 error.OutOfMemory => diags.setAllocFailure(),
1728 };
1729 } else if (comp.bin_file) |lf| {
1730 lf.updateNav(pt, nav_index) catch |err| switch (err) {
1731 error.Canceled => comp.io.recancel(),
1732 error.AlreadyReported => return,
1733 error.OutOfMemory => diags.setAllocFailure(),
1734 };
1735 }
1736}
1737pub fn updateErrorData(pt: Zcu.PerThread) void {
1738 const comp = pt.zcu.comp;
1739 if (comp.bin_file) |lf| lf.updateErrorData(pt) catch |err| switch (err) {
1740 error.OutOfMemory => comp.link_diags.setAllocFailure(),
1741 error.Canceled => comp.io.recancel(),
1742 error.AlreadyReported => {},
1743 };
1744}
1745
1746/// Provided by the CLI, processed into `LinkInput` instances at the start of
1747/// the compilation pipeline.
1748pub const UnresolvedInput = union(enum) {
1749 /// A library name that could potentially be dynamic or static depending on
1750 /// query parameters, resolved according to library directories.
1751 /// This could potentially resolve to a GNU ld script, resulting in more
1752 /// library dependencies.
1753 name_query: NameQuery,
1754 /// When a file path is provided, query info is still needed because the
1755 /// path may point to a .so file which may actually be a GNU ld script that
1756 /// references library names which need to be resolved.
1757 path_query: PathQuery,
1758 /// Strings that come from GNU ld scripts. Is it a filename? Is it a path?
1759 /// Who knows! Fuck around and find out.
1760 ambiguous_name: NameQuery,
1761 /// Put exactly this string in the dynamic section, no rpath.
1762 dso_exact: Input.DsoExact,
1763
1764 pub const NameQuery = struct {
1765 name: []const u8,
1766 query: Query,
1767 };
1768
1769 pub const PathQuery = struct {
1770 path: Path,
1771 query: Query,
1772 };
1773
1774 pub const Query = struct {
1775 needed: bool = false,
1776 weak: bool = false,
1777 reexport: bool = false,
1778 must_link: bool = false,
1779 hidden: bool = false,
1780 allow_so_scripts: bool = false,
1781 preferred_mode: std.lang.LinkMode,
1782 search_strategy: SearchStrategy,
1783
1784 fn fallbackMode(q: Query) std.lang.LinkMode {
1785 assert(q.search_strategy != .no_fallback);
1786 return switch (q.preferred_mode) {
1787 .dynamic => .static,
1788 .static => .dynamic,
1789 };
1790 }
1791 };
1792
1793 pub const SearchStrategy = enum {
1794 paths_first,
1795 mode_first,
1796 no_fallback,
1797 };
1798};
1799
1800pub const Input = union(enum) {
1801 object: Object,
1802 archive: Object,
1803 res: Res,
1804 /// May not be a GNU ld script. Those are resolved when converting from
1805 /// `UnresolvedInput` to `Input` values.
1806 dso: Dso,
1807 dso_exact: DsoExact,
1808
1809 pub const Object = struct {
1810 path: Path,
1811 file: Io.File,
1812 must_link: bool,
1813 hidden: bool,
1814 };
1815
1816 pub const Res = struct {
1817 path: Path,
1818 file: Io.File,
1819 };
1820
1821 pub const Dso = struct {
1822 path: Path,
1823 file: Io.File,
1824 needed: bool,
1825 weak: bool,
1826 reexport: bool,
1827 };
1828
1829 pub const DsoExact = struct {
1830 /// Includes the ":" prefix. This is intended to be put into the DSO
1831 /// section verbatim with no corresponding rpaths.
1832 name: []const u8,
1833 };
1834
1835 /// Returns `null` in the case of `dso_exact`.
1836 pub fn path(input: Input) ?Path {
1837 return switch (input) {
1838 .object, .archive => |obj| obj.path,
1839 inline .res, .dso => |x| x.path,
1840 .dso_exact => null,
1841 };
1842 }
1843
1844 /// Returns `null` in the case of `dso_exact`.
1845 pub fn pathAndFile(input: Input) ?struct { Path, Io.File } {
1846 return switch (input) {
1847 .object, .archive => |obj| .{ obj.path, obj.file },
1848 inline .res, .dso => |x| .{ x.path, x.file },
1849 .dso_exact => null,
1850 };
1851 }
1852
1853 pub fn taskName(input: Input) []const u8 {
1854 return switch (input) {
1855 .object, .archive => |obj| obj.path.basename(),
1856 inline .res, .dso => |x| x.path.basename(),
1857 .dso_exact => "dso_exact",
1858 };
1859 }
1860};
1861
1862pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void {
1863 for (link_inputs) |link_input| {
1864 man.hash.add(@as(@typeInfo(Input).@"union".tag_type.?, link_input));
1865 switch (link_input) {
1866 .object, .archive => |obj| {
1867 _ = try man.addOpenedFile(obj.path, obj.file, null);
1868 man.hash.add(obj.must_link);
1869 man.hash.add(obj.hidden);
1870 },
1871 .res => |res| {
1872 _ = try man.addOpenedFile(res.path, res.file, null);
1873 },
1874 .dso => |dso| {
1875 _ = try man.addOpenedFile(dso.path, dso.file, null);
1876 man.hash.add(dso.needed);
1877 man.hash.add(dso.weak);
1878 man.hash.add(dso.reexport);
1879 },
1880 .dso_exact => |dso_exact| {
1881 man.hash.addBytes(dso_exact.name);
1882 },
1883 }
1884 }
1885}
1886
1887pub fn resolveInputs(
1888 gpa: Allocator,
1889 arena: Allocator,
1890 io: Io,
1891 target: *const std.Target,
1892 /// This function mutates this array but does not take ownership.
1893 /// Allocated with `gpa`.
1894 unresolved_inputs: *std.ArrayList(UnresolvedInput),
1895 /// Allocated with `gpa`.
1896 resolved_inputs: *std.ArrayList(Input),
1897 lib_directories: []const Cache.Directory,
1898 color: std.zig.Color,
1899) Allocator.Error!void {
1900 var checked_paths: std.ArrayList(u8) = .empty;
1901 defer checked_paths.deinit(gpa);
1902
1903 var ld_script_bytes: std.ArrayList(u8) = .empty;
1904 defer ld_script_bytes.deinit(gpa);
1905
1906 var archive_dedup: ArchiveDedupMap = .empty;
1907 defer archive_dedup.deinit(gpa);
1908
1909 var failed_libs: std.ArrayList(struct {
1910 name: []const u8,
1911 strategy: UnresolvedInput.SearchStrategy,
1912 checked_paths: []const u8,
1913 preferred_mode: std.lang.LinkMode,
1914 }) = .empty;
1915
1916 // Convert external system libs into a stack so that items can be
1917 // pushed to it.
1918 //
1919 // This is necessary because shared objects might turn out to be
1920 // "linker scripts" that in fact resolve to one or more other
1921 // external system libs, including parameters such as "needed".
1922 //
1923 // Unfortunately, such files need to be detected immediately, so
1924 // that this library search logic can be applied to them.
1925 mem.reverse(UnresolvedInput, unresolved_inputs.items);
1926
1927 syslib: while (unresolved_inputs.pop()) |unresolved_input| {
1928 switch (unresolved_input) {
1929 .name_query => |name_query| {
1930 const query = name_query.query;
1931
1932 // Checked in the first pass above while looking for libc libraries.
1933 assert(!fs.path.isAbsolute(name_query.name));
1934
1935 checked_paths.clearRetainingCapacity();
1936
1937 switch (query.search_strategy) {
1938 .mode_first, .no_fallback => {
1939 // check for preferred mode
1940 for (lib_directories) |lib_directory| switch (try resolveLibInput(
1941 gpa,
1942 arena,
1943 io,
1944 unresolved_inputs,
1945 resolved_inputs,
1946 &checked_paths,
1947 &ld_script_bytes,
1948 &archive_dedup,
1949 lib_directory,
1950 name_query,
1951 target,
1952 query.preferred_mode,
1953 color,
1954 )) {
1955 .ok => continue :syslib,
1956 .no_match => {},
1957 };
1958 // check for fallback mode
1959 if (query.search_strategy == .no_fallback) {
1960 try failed_libs.append(arena, .{
1961 .name = name_query.name,
1962 .strategy = query.search_strategy,
1963 .checked_paths = try arena.dupe(u8, checked_paths.items),
1964 .preferred_mode = query.preferred_mode,
1965 });
1966 continue :syslib;
1967 }
1968 for (lib_directories) |lib_directory| switch (try resolveLibInput(
1969 gpa,
1970 arena,
1971 io,
1972 unresolved_inputs,
1973 resolved_inputs,
1974 &checked_paths,
1975 &ld_script_bytes,
1976 &archive_dedup,
1977 lib_directory,
1978 name_query,
1979 target,
1980 query.fallbackMode(),
1981 color,
1982 )) {
1983 .ok => continue :syslib,
1984 .no_match => {},
1985 };
1986 try failed_libs.append(arena, .{
1987 .name = name_query.name,
1988 .strategy = query.search_strategy,
1989 .checked_paths = try arena.dupe(u8, checked_paths.items),
1990 .preferred_mode = query.preferred_mode,
1991 });
1992 continue :syslib;
1993 },
1994 .paths_first => {
1995 for (lib_directories) |lib_directory| {
1996 // check for preferred mode
1997 switch (try resolveLibInput(
1998 gpa,
1999 arena,
2000 io,
2001 unresolved_inputs,
2002 resolved_inputs,
2003 &checked_paths,
2004 &ld_script_bytes,
2005 &archive_dedup,
2006 lib_directory,
2007 name_query,
2008 target,
2009 query.preferred_mode,
2010 color,
2011 )) {
2012 .ok => continue :syslib,
2013 .no_match => {},
2014 }
2015
2016 // check for fallback mode
2017 switch (try resolveLibInput(
2018 gpa,
2019 arena,
2020 io,
2021 unresolved_inputs,
2022 resolved_inputs,
2023 &checked_paths,
2024 &ld_script_bytes,
2025 &archive_dedup,
2026 lib_directory,
2027 name_query,
2028 target,
2029 query.fallbackMode(),
2030 color,
2031 )) {
2032 .ok => continue :syslib,
2033 .no_match => {},
2034 }
2035 }
2036 try failed_libs.append(arena, .{
2037 .name = name_query.name,
2038 .strategy = query.search_strategy,
2039 .checked_paths = try arena.dupe(u8, checked_paths.items),
2040 .preferred_mode = query.preferred_mode,
2041 });
2042 continue :syslib;
2043 },
2044 }
2045 },
2046 .ambiguous_name => |an| {
2047 // First check the path relative to the current working directory.
2048 // If the file is a library and is not found there, check the library search paths as well.
2049 // This is consistent with the behavior of GNU ld.
2050 if (try resolvePathInput(
2051 gpa,
2052 arena,
2053 io,
2054 unresolved_inputs,
2055 resolved_inputs,
2056 &ld_script_bytes,
2057 &archive_dedup,
2058 target,
2059 .{
2060 .path = Path.initCwd(an.name),
2061 .query = an.query,
2062 },
2063 color,
2064 )) |lib_result| {
2065 switch (lib_result) {
2066 .ok => continue :syslib,
2067 .no_match => {
2068 for (lib_directories) |lib_directory| {
2069 switch ((try resolvePathInput(
2070 gpa,
2071 arena,
2072 io,
2073 unresolved_inputs,
2074 resolved_inputs,
2075 &ld_script_bytes,
2076 &archive_dedup,
2077 target,
2078 .{
2079 .path = .{
2080 .root_dir = lib_directory,
2081 .sub_path = an.name,
2082 },
2083 .query = an.query,
2084 },
2085 color,
2086 )).?) {
2087 .ok => continue :syslib,
2088 .no_match => {},
2089 }
2090 }
2091 fatal("{s}: file listed in linker script not found", .{an.name});
2092 },
2093 }
2094 }
2095 continue;
2096 },
2097 .path_query => |pq| {
2098 if (try resolvePathInput(
2099 gpa,
2100 arena,
2101 io,
2102 unresolved_inputs,
2103 resolved_inputs,
2104 &ld_script_bytes,
2105 &archive_dedup,
2106 target,
2107 pq,
2108 color,
2109 )) |lib_result| {
2110 switch (lib_result) {
2111 .ok => {},
2112 .no_match => fatal("{f}: file not found", .{pq.path}),
2113 }
2114 }
2115 continue;
2116 },
2117 .dso_exact => |dso_exact| {
2118 try resolved_inputs.append(gpa, .{ .dso_exact = dso_exact });
2119 continue;
2120 },
2121 }
2122 comptime unreachable;
2123 }
2124
2125 if (failed_libs.items.len > 0) {
2126 for (failed_libs.items) |f| {
2127 const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths;
2128 std.log.err("unable to find {s} system library '{s}' using strategy '{s}'. searched paths:{s}", .{
2129 @tagName(f.preferred_mode), f.name, @tagName(f.strategy), searched_paths,
2130 });
2131 }
2132 std.process.exit(1);
2133 }
2134}
2135
2136const ResolveLibInputResult = enum { ok, no_match };
2137const fatal = std.process.fatal;
2138
2139fn resolveLibInput(
2140 gpa: Allocator,
2141 arena: Allocator,
2142 io: Io,
2143 /// Allocated via `gpa`.
2144 unresolved_inputs: *std.ArrayList(UnresolvedInput),
2145 /// Allocated via `gpa`.
2146 resolved_inputs: *std.ArrayList(Input),
2147 /// Allocated via `gpa`.
2148 checked_paths: *std.ArrayList(u8),
2149 /// Allocated via `gpa`.
2150 ld_script_bytes: *std.ArrayList(u8),
2151 /// Allocated via `gpa`.
2152 archive_dedup: *ArchiveDedupMap,
2153 lib_directory: Directory,
2154 name_query: UnresolvedInput.NameQuery,
2155 target: *const std.Target,
2156 link_mode: std.lang.LinkMode,
2157 color: std.zig.Color,
2158) Allocator.Error!ResolveLibInputResult {
2159 try resolved_inputs.ensureUnusedCapacity(gpa, 1);
2160 try archive_dedup.ensureUnusedCapacity(gpa, 1);
2161
2162 const lib_name = name_query.name;
2163
2164 if (target.os.tag.isDarwin() and link_mode == .dynamic) tbd: {
2165 // Prefer .tbd over .dylib.
2166 const test_path: Path = .{
2167 .root_dir = lib_directory,
2168 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.tbd", .{lib_name}),
2169 };
2170 try checked_paths.print(gpa, "\n {f}", .{test_path});
2171 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
2172 error.FileNotFound => break :tbd,
2173 else => |e| fatal("unable to search for tbd library '{f}': {s}", .{ test_path, @errorName(e) }),
2174 };
2175 errdefer file.close(io);
2176 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);
2177 }
2178
2179 {
2180 const test_path: Path = .{
2181 .root_dir = lib_directory,
2182 .sub_path = try std.fmt.allocPrint(arena, "{s}{s}{s}", .{
2183 target.libPrefix(), lib_name,
2184 switch (link_mode) {
2185 .static => target.staticLibSuffix(),
2186 .dynamic => target.dynamicLibSuffix(),
2187 },
2188 }),
2189 };
2190 try checked_paths.print(gpa, "\n {f}", .{test_path});
2191 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{
2192 .path = test_path,
2193 .query = name_query.query,
2194 }, link_mode, color)) {
2195 .no_match => {},
2196 .ok => return .ok,
2197 }
2198 }
2199
2200 // In the case of Darwin, the main check will be .dylib, so here we
2201 // additionally check for .so files.
2202 if (target.os.tag.isDarwin() and link_mode == .dynamic) so: {
2203 const test_path: Path = .{
2204 .root_dir = lib_directory,
2205 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib_name}),
2206 };
2207 try checked_paths.print(gpa, "\n {f}", .{test_path});
2208 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
2209 error.FileNotFound => break :so,
2210 else => |e| fatal("unable to search for so library '{f}': {s}", .{
2211 test_path, @errorName(e),
2212 }),
2213 };
2214 errdefer file.close(io);
2215 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);
2216 }
2217
2218 // In the case of MinGW, the main check will be .lib but we also need to
2219 // look for `libfoo.a`.
2220 if (target.isMinGW() and link_mode == .static) mingw: {
2221 const test_path: Path = .{
2222 .root_dir = lib_directory,
2223 .sub_path = try std.fmt.allocPrint(arena, "lib{s}.a", .{lib_name}),
2224 };
2225 try checked_paths.print(gpa, "\n {f}", .{test_path});
2226 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
2227 error.FileNotFound => break :mingw,
2228 else => |e| fatal("unable to search for static library '{f}': {s}", .{ test_path, @errorName(e) }),
2229 };
2230 errdefer file.close(io);
2231 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, name_query.query);
2232 }
2233
2234 // In the case of OpenBSD, dynamic libraries are always versioned, without
2235 // unversioned symlinks. OpenBSD patches LLD to select the highest-versioned
2236 // shared library, and this code is intended to match that upstream behavior.
2237 if (target.isOpenBSDLibC() and link_mode == .dynamic) versioned: {
2238 const prefix = try std.fmt.allocPrint(arena, "lib{s}.so.", .{lib_name});
2239
2240 var dir = lib_directory.handle.openDir(io, ".", .{ .iterate = true }) catch |err| switch (err) {
2241 error.NotDir, error.FileNotFound => break :versioned,
2242 else => |e| fatal("unable to search for shared library '{s}.*': {s}", .{ prefix, @errorName(e) }),
2243 };
2244 defer dir.close(io);
2245
2246 var best_match_major: u32 = 0;
2247 var best_match_minor: u32 = 0;
2248 var best_match: ?[]const u8 = null;
2249
2250 var iter = dir.iterate();
2251 while (iter.next(io) catch |err| {
2252 fatal("unable to scan library directory '{s}'", .{@errorName(err)});
2253 }) |entry| {
2254 if (entry.kind != .file) continue;
2255 if (!std.mem.startsWith(u8, entry.name, prefix)) continue;
2256
2257 const rest = entry.name[prefix.len..];
2258 var sit = std.mem.splitScalar(u8, rest, '.');
2259 const major_str = sit.next() orelse continue;
2260 const minor_str = sit.next() orelse continue;
2261 if (sit.next() != null) continue;
2262 const major = std.fmt.parseInt(u32, major_str, 10) catch continue;
2263 const minor = std.fmt.parseInt(u32, minor_str, 10) catch continue;
2264
2265 if (major > best_match_major or (major == best_match_major and minor >= best_match_minor)) {
2266 best_match_major = major;
2267 best_match_minor = minor;
2268 best_match = try arena.dupe(u8, entry.name);
2269 }
2270 }
2271
2272 if (best_match) |found| {
2273 const test_path: Path = .{
2274 .root_dir = lib_directory,
2275 .sub_path = found,
2276 };
2277 try checked_paths.print(gpa, "\n {f}", .{test_path});
2278 switch (try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, .{
2279 .path = test_path,
2280 .query = name_query.query,
2281 }, link_mode, color)) {
2282 .no_match => {},
2283 .ok => return .ok,
2284 }
2285 }
2286 }
2287
2288 return .no_match;
2289}
2290
2291/// Deduplicates static archive link inputs based on their path. This is done for efficiency, so
2292/// that linker implementations do not need to open and scan the archive just to determine that they
2293/// need not extract any objects. At the time of writing, it also helps avoid "multiple definitions
2294/// of symbol" errors in incomplete linker implementations.
2295///
2296/// Key is index into `resolved_inputs` of an `Input.archive`.
2297///
2298/// Accessed through `ArchiveDedupAdapter`.
2299///
2300const ArchiveDedupMap = std.array_hash_map.Custom(u32, void, void, true);
2301/// Adapter for accessing `ArchiveDedupMap` with an effective key type of `Path`.
2302const ArchiveDedupAdapter = struct {
2303 resolved_inputs: []const Input,
2304 pub fn hash(ctx: ArchiveDedupAdapter, path: Path) u32 {
2305 _ = ctx;
2306 return Path.TableAdapter.hash(.{}, path);
2307 }
2308 pub fn eql(ctx: ArchiveDedupAdapter, a_path: Path, b_input_index: u32, _: usize) bool {
2309 const b_path = ctx.resolved_inputs[b_input_index].archive.path;
2310 return a_path.eql(b_path);
2311 }
2312};
2313
2314fn finishResolveLibInput(
2315 io: Io,
2316 resolved_inputs: *std.ArrayList(Input),
2317 archive_dedup: *ArchiveDedupMap,
2318 path: Path,
2319 file: Io.File,
2320 link_mode: std.lang.LinkMode,
2321 query: UnresolvedInput.Query,
2322) ResolveLibInputResult {
2323 switch (link_mode) {
2324 .static => {
2325 const ctx: ArchiveDedupAdapter = .{ .resolved_inputs = resolved_inputs.items };
2326 const gop = archive_dedup.getOrPutAssumeCapacityAdapted(path, ctx);
2327 if (gop.found_existing) {
2328 // Ignore duplicate archive input
2329 file.close(io);
2330 return .ok;
2331 }
2332 gop.key_ptr.* = @intCast(resolved_inputs.items.len);
2333 resolved_inputs.appendAssumeCapacity(.{ .archive = .{
2334 .path = path,
2335 .file = file,
2336 .must_link = query.must_link,
2337 .hidden = query.hidden,
2338 } });
2339 },
2340 .dynamic => resolved_inputs.appendAssumeCapacity(.{ .dso = .{
2341 .path = path,
2342 .file = file,
2343 .needed = query.needed,
2344 .weak = query.weak,
2345 .reexport = query.reexport,
2346 } }),
2347 }
2348 return .ok;
2349}
2350
2351fn resolvePathInput(
2352 gpa: Allocator,
2353 arena: Allocator,
2354 io: Io,
2355 /// Allocated with `gpa`.
2356 unresolved_inputs: *std.ArrayList(UnresolvedInput),
2357 /// Allocated with `gpa`.
2358 resolved_inputs: *std.ArrayList(Input),
2359 /// Allocated via `gpa`.
2360 ld_script_bytes: *std.ArrayList(u8),
2361 /// Allocated via `gpa`.
2362 archive_dedup: *ArchiveDedupMap,
2363 target: *const std.Target,
2364 pq: UnresolvedInput.PathQuery,
2365 color: std.zig.Color,
2366) Allocator.Error!?ResolveLibInputResult {
2367 switch (Compilation.classifyFileExt(pq.path.sub_path)) {
2368 .static_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .static, color),
2369 .shared_library => return try resolvePathInputLib(gpa, arena, io, unresolved_inputs, resolved_inputs, ld_script_bytes, archive_dedup, target, pq, .dynamic, color),
2370 .object => {
2371 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err|
2372 fatal("failed to open object {f}: {s}", .{ pq.path, @errorName(err) });
2373 errdefer file.close(io);
2374 try resolved_inputs.append(gpa, .{ .object = .{
2375 .path = pq.path,
2376 .file = file,
2377 .must_link = pq.query.must_link,
2378 .hidden = pq.query.hidden,
2379 } });
2380 return null;
2381 },
2382 .res => {
2383 var file = pq.path.root_dir.handle.openFile(io, pq.path.sub_path, .{}) catch |err|
2384 fatal("failed to open windows resource {f}: {s}", .{ pq.path, @errorName(err) });
2385 errdefer file.close(io);
2386 try resolved_inputs.append(gpa, .{ .res = .{
2387 .path = pq.path,
2388 .file = file,
2389 } });
2390 return null;
2391 },
2392 else => fatal("{f}: unrecognized file extension", .{pq.path}),
2393 }
2394}
2395
2396fn resolvePathInputLib(
2397 gpa: Allocator,
2398 arena: Allocator,
2399 io: Io,
2400 /// Allocated with `gpa`.
2401 unresolved_inputs: *std.ArrayList(UnresolvedInput),
2402 /// Allocated with `gpa`.
2403 resolved_inputs: *std.ArrayList(Input),
2404 /// Allocated via `gpa`.
2405 ld_script_bytes: *std.ArrayList(u8),
2406 /// Allocated via `gpa`.
2407 archive_dedup: *ArchiveDedupMap,
2408 target: *const std.Target,
2409 pq: UnresolvedInput.PathQuery,
2410 link_mode: std.lang.LinkMode,
2411 color: std.zig.Color,
2412) Allocator.Error!ResolveLibInputResult {
2413 try resolved_inputs.ensureUnusedCapacity(gpa, 1);
2414 try archive_dedup.ensureUnusedCapacity(gpa, 1);
2415
2416 const test_path: Path = pq.path;
2417 // In the case of shared libraries, they might actually be "linker scripts"
2418 // that contain references to other libraries.
2419 if (pq.query.allow_so_scripts and target.ofmt == .elf and switch (Compilation.classifyFileExt(test_path.sub_path)) {
2420 .static_library, .shared_library => true,
2421 else => false,
2422 }) {
2423 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
2424 error.FileNotFound => return .no_match,
2425 else => |e| fatal("unable to search for {t} library '{f}': {t}", .{
2426 link_mode, std.fmt.alt(test_path, .formatEscapeChar), e,
2427 }),
2428 };
2429 errdefer file.close(io);
2430 try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len));
2431 const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err|
2432 fatal("failed to read '{f}': {t}", .{ std.fmt.alt(test_path, .formatEscapeChar), err });
2433 const buf = ld_script_bytes.items[0..n];
2434 if (mem.startsWith(u8, buf, std.elf.MAGIC) or
2435 mem.startsWith(u8, buf, std.elf.ARMAG) or
2436 mem.startsWith(u8, buf, std.elf.ARMAG_THIN))
2437 {
2438 // Appears to be an ELF or archive file.
2439 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query);
2440 }
2441 const stat = file.stat(io) catch |err|
2442 fatal("failed to stat {f}: {t}", .{ test_path, err });
2443 const size = std.math.cast(u32, stat.size) orelse
2444 fatal("{f}: linker script too big", .{test_path});
2445 try ld_script_bytes.resize(gpa, size);
2446 const buf2 = ld_script_bytes.items[n..];
2447 const n2 = file.readPositionalAll(io, buf2, n) catch |err|
2448 fatal("failed to read {f}: {t}", .{ test_path, err });
2449 if (n2 != buf2.len) fatal("failed to read {f}: unexpected end of file", .{test_path});
2450
2451 // This `Io` is only used for a mutex, and we know we aren't doing anything async/concurrent.
2452 var threaded: Io.Threaded = .init_single_threaded;
2453 defer threaded.deinit();
2454 var diags: Diags = .init(gpa, threaded.io());
2455 defer diags.deinit();
2456
2457 const ld_script_result = LdScript.parse(gpa, &diags, test_path, ld_script_bytes.items);
2458 if (diags.hasErrors()) {
2459 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
2460 try wip_errors.init(gpa);
2461 defer wip_errors.deinit();
2462
2463 try diags.addMessagesToBundle(&wip_errors, null);
2464
2465 var error_bundle = try wip_errors.toOwnedBundle("");
2466 defer error_bundle.deinit(gpa);
2467
2468 error_bundle.renderToStderr(io, .{}, color) catch {};
2469 std.process.exit(1);
2470 }
2471
2472 var ld_script = ld_script_result catch |err|
2473 fatal("{f}: failed to parse linker script: {t}", .{ test_path, err });
2474 defer ld_script.deinit(gpa);
2475
2476 try unresolved_inputs.ensureUnusedCapacity(gpa, ld_script.args.len);
2477 for (ld_script.args) |arg| {
2478 const query: UnresolvedInput.Query = .{
2479 .needed = arg.needed or pq.query.needed,
2480 .weak = pq.query.weak,
2481 .reexport = pq.query.reexport,
2482 .preferred_mode = pq.query.preferred_mode,
2483 .search_strategy = pq.query.search_strategy,
2484 .allow_so_scripts = pq.query.allow_so_scripts,
2485 };
2486 if (mem.startsWith(u8, arg.path, "-l")) {
2487 unresolved_inputs.appendAssumeCapacity(.{ .name_query = .{
2488 .name = try arena.dupe(u8, arg.path["-l".len..]),
2489 .query = query,
2490 } });
2491 } else {
2492 unresolved_inputs.appendAssumeCapacity(.{ .ambiguous_name = .{
2493 .name = try arena.dupe(u8, arg.path),
2494 .query = query,
2495 } });
2496 }
2497 }
2498 file.close(io);
2499 return .ok;
2500 }
2501
2502 var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) {
2503 error.FileNotFound => return .no_match,
2504 else => |e| fatal("unable to search for {s} library {f}: {s}", .{
2505 @tagName(link_mode), test_path, @errorName(e),
2506 }),
2507 };
2508 errdefer file.close(io);
2509 return finishResolveLibInput(io, resolved_inputs, archive_dedup, test_path, file, link_mode, pq.query);
2510}
2511
2512pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object {
2513 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
2514 errdefer file.close(io);
2515 return .{
2516 .path = path,
2517 .file = file,
2518 .must_link = must_link,
2519 .hidden = hidden,
2520 };
2521}
2522
2523pub fn openDso(io: Io, path: Path, needed: bool, weak: bool, reexport: bool) !Input.Dso {
2524 var file = try path.root_dir.handle.openFile(io, path.sub_path, .{});
2525 errdefer file.close(io);
2526 return .{
2527 .path = path,
2528 .file = file,
2529 .needed = needed,
2530 .weak = weak,
2531 .reexport = reexport,
2532 };
2533}
2534
2535pub fn openObjectInput(io: Io, diags: *Diags, path: Path) error{AlreadyReported}!Input {
2536 return .{ .object = openObject(io, path, false, false) catch |err| {
2537 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });
2538 } };
2539}
2540
2541pub fn openArchiveInput(io: Io, diags: *Diags, path: Path, must_link: bool, hidden: bool) error{AlreadyReported}!Input {
2542 return .{ .archive = openObject(io, path, must_link, hidden) catch |err| {
2543 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });
2544 } };
2545}
2546
2547pub fn openDsoInput(io: Io, diags: *Diags, path: Path, needed: bool, weak: bool, reexport: bool) error{AlreadyReported}!Input {
2548 return .{ .dso = openDso(io, path, needed, weak, reexport) catch |err| {
2549 return diags.failParse(path, "failed to open {f}: {s}", .{ path, @errorName(err) });
2550 } };
2551}
2552
2553/// Returns true if and only if there is at least one input of type object,
2554/// archive, or Windows resource file.
2555pub fn anyObjectInputs(inputs: []const Input) bool {
2556 return countObjectInputs(inputs) != 0;
2557}
2558
2559/// Returns the number of inputs of type object, archive, or Windows resource file.
2560pub fn countObjectInputs(inputs: []const Input) usize {
2561 var count: usize = 0;
2562 for (inputs) |input| switch (input) {
2563 .dso, .dso_exact => continue,
2564 .res, .object, .archive => count += 1,
2565 };
2566 return count;
2567}
2568
2569/// Returns the first input of type object or archive.
2570pub fn firstObjectInput(inputs: []const Input) ?Input.Object {
2571 for (inputs) |input| switch (input) {
2572 .object, .archive => |obj| return obj,
2573 .res, .dso, .dso_exact => continue,
2574 };
2575 return null;
2576}