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