zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

blob eef6e827 (90443B) - Raw


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