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