master
   1const std = @import("std");
   2const Allocator = std.mem.Allocator;
   3const mem = std.mem;
   4const log = std.log;
   5const fs = std.fs;
   6const path = fs.path;
   7const assert = std.debug.assert;
   8const Version = std.SemanticVersion;
   9const Path = std.Build.Cache.Path;
  10
  11const Compilation = @import("../Compilation.zig");
  12const build_options = @import("build_options");
  13const trace = @import("../tracy.zig").trace;
  14const Cache = std.Build.Cache;
  15const Module = @import("../Package/Module.zig");
  16const link = @import("../link.zig");
  17
  18pub const CrtFile = enum {
  19    scrt1_o,
  20};
  21
  22pub fn needsCrt0(output_mode: std.builtin.OutputMode) ?CrtFile {
  23    // For shared libraries and PIC executables, we should actually link in a variant of crt1 that
  24    // is built with `-DSHARED` so that it calls `__cxa_finalize` in an ELF destructor. However, we
  25    // currently make no effort to respect `__cxa_finalize` on any other targets, so for now, we're
  26    // not doing it here either.
  27    //
  28    // See: https://github.com/ziglang/zig/issues/23574#issuecomment-2869089897
  29    return switch (output_mode) {
  30        .Obj, .Lib => null,
  31        .Exe => .scrt1_o,
  32    };
  33}
  34
  35fn includePath(comp: *Compilation, arena: Allocator, sub_path: []const u8) ![]const u8 {
  36    return path.join(arena, &.{
  37        comp.dirs.zig_lib.path.?,
  38        "libc" ++ path.sep_str ++ "include",
  39        sub_path,
  40    });
  41}
  42
  43fn csuPath(comp: *Compilation, arena: Allocator, sub_path: []const u8) ![]const u8 {
  44    return path.join(arena, &.{
  45        comp.dirs.zig_lib.path.?,
  46        "libc" ++ path.sep_str ++ "freebsd" ++ path.sep_str ++ "lib" ++ path.sep_str ++ "csu",
  47        sub_path,
  48    });
  49}
  50
  51fn libcPath(comp: *Compilation, arena: Allocator, sub_path: []const u8) ![]const u8 {
  52    return path.join(arena, &.{
  53        comp.dirs.zig_lib.path.?,
  54        "libc" ++ path.sep_str ++ "freebsd" ++ path.sep_str ++ "lib" ++ path.sep_str ++ "libc",
  55        sub_path,
  56    });
  57}
  58
  59/// TODO replace anyerror with explicit error set, recording user-friendly errors with
  60/// lockAndSetMiscFailure and returning error.AlreadyReported. see libcxx.zig for example.
  61pub fn buildCrtFile(comp: *Compilation, crt_file: CrtFile, prog_node: std.Progress.Node) anyerror!void {
  62    if (!build_options.have_llvm) return error.ZigCompilerNotBuiltWithLLVMExtensions;
  63
  64    const gpa = comp.gpa;
  65    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
  66    defer arena_allocator.deinit();
  67    const arena = arena_allocator.allocator();
  68
  69    const target = &comp.root_mod.resolved_target.result;
  70
  71    // In all cases in this function, we add the C compiler flags to
  72    // cache_exempt_flags rather than extra_flags, because these arguments
  73    // depend on only properties that are already covered by the cache
  74    // manifest. Including these arguments in the cache could only possibly
  75    // waste computation and create false negatives.
  76
  77    switch (crt_file) {
  78        .scrt1_o => {
  79            var cflags = std.array_list.Managed([]const u8).init(arena);
  80            try cflags.appendSlice(&.{
  81                "-O2",
  82                "-fno-common",
  83                "-std=gnu99",
  84                "-w", // Disable all warnings.
  85            });
  86
  87            if (target.cpu.arch.isPowerPC64()) {
  88                try cflags.append("-mlongcall");
  89            }
  90
  91            var acflags = std.array_list.Managed([]const u8).init(arena);
  92            try acflags.appendSlice(&.{
  93                "-DLOCORE",
  94                // See `Compilation.addCCArgs`.
  95                try std.fmt.allocPrint(arena, "-D__FreeBSD_version={d}", .{target.os.version_range.semver.min.major * 100_000 + 500}),
  96            });
  97
  98            inline for (.{ &cflags, &acflags }) |flags| {
  99                try flags.appendSlice(&.{
 100                    "-DPIC",
 101                    "-DSTRIP_FBSDID",
 102                    "-I",
 103                    try includePath(comp, arena, try std.fmt.allocPrint(arena, "{s}-{s}-{s}", .{
 104                        std.zig.target.freebsdArchNameHeaders(target.cpu.arch),
 105                        @tagName(target.os.tag),
 106                        @tagName(target.abi),
 107                    })),
 108                    "-I",
 109                    try includePath(comp, arena, "generic-freebsd"),
 110                    "-I",
 111                    try csuPath(comp, arena, switch (target.cpu.arch) {
 112                        .arm => "arm",
 113                        .aarch64 => "aarch64",
 114                        .powerpc => "powerpc",
 115                        .powerpc64, .powerpc64le => "powerpc64",
 116                        .riscv64 => "riscv",
 117                        .x86 => "i386",
 118                        .x86_64 => "amd64",
 119                        else => unreachable,
 120                    }),
 121                    "-I",
 122                    try csuPath(comp, arena, "common"),
 123                    "-I",
 124                    try libcPath(comp, arena, "include"),
 125                    "-Qunused-arguments",
 126                });
 127            }
 128
 129            const sources = [_]struct {
 130                path: []const u8,
 131                flags: []const []const u8,
 132                condition: bool = true,
 133            }{
 134                .{
 135                    .path = "common" ++ path.sep_str ++ "crtbegin.c",
 136                    .flags = cflags.items,
 137                },
 138                .{
 139                    .path = "common" ++ path.sep_str ++ "crtbrand.S",
 140                    .flags = acflags.items,
 141                },
 142                .{
 143                    .path = "common" ++ path.sep_str ++ "crtend.c",
 144                    .flags = cflags.items,
 145                },
 146                .{
 147                    .path = "common" ++ path.sep_str ++ "feature_note.S",
 148                    .flags = acflags.items,
 149                },
 150                .{
 151                    .path = "common" ++ path.sep_str ++ "ignore_init_note.S",
 152                    .flags = acflags.items,
 153                },
 154
 155                .{
 156                    .path = "arm" ++ path.sep_str ++ "crt1_c.c",
 157                    .flags = cflags.items,
 158                    .condition = target.cpu.arch == .arm,
 159                },
 160                .{
 161                    .path = "arm" ++ path.sep_str ++ "crt1_s.S",
 162                    .flags = acflags.items,
 163                    .condition = target.cpu.arch == .arm,
 164                },
 165
 166                .{
 167                    .path = "aarch64" ++ path.sep_str ++ "crt1_c.c",
 168                    .flags = cflags.items,
 169                    .condition = target.cpu.arch == .aarch64,
 170                },
 171                .{
 172                    .path = "aarch64" ++ path.sep_str ++ "crt1_s.S",
 173                    .flags = acflags.items,
 174                    .condition = target.cpu.arch == .aarch64,
 175                },
 176
 177                .{
 178                    .path = "powerpc" ++ path.sep_str ++ "crt1_c.c",
 179                    .flags = cflags.items,
 180                    .condition = target.cpu.arch == .powerpc,
 181                },
 182                .{
 183                    .path = "powerpc" ++ path.sep_str ++ "crtsavres.S",
 184                    .flags = acflags.items,
 185                    .condition = target.cpu.arch == .powerpc,
 186                },
 187
 188                .{
 189                    .path = "powerpc64" ++ path.sep_str ++ "crt1_c.c",
 190                    .flags = cflags.items,
 191                    .condition = target.cpu.arch.isPowerPC64(),
 192                },
 193
 194                .{
 195                    .path = "riscv" ++ path.sep_str ++ "crt1_c.c",
 196                    .flags = cflags.items,
 197                    .condition = target.cpu.arch == .riscv64,
 198                },
 199                .{
 200                    .path = "riscv" ++ path.sep_str ++ "crt1_s.S",
 201                    .flags = acflags.items,
 202                    .condition = target.cpu.arch == .riscv64,
 203                },
 204
 205                .{
 206                    .path = "i386" ++ path.sep_str ++ "crt1_c.c",
 207                    .flags = cflags.items,
 208                    .condition = target.cpu.arch == .x86,
 209                },
 210                .{
 211                    .path = "i386" ++ path.sep_str ++ "crt1_s.S",
 212                    .flags = acflags.items,
 213                    .condition = target.cpu.arch == .x86,
 214                },
 215
 216                .{
 217                    .path = "amd64" ++ path.sep_str ++ "crt1_c.c",
 218                    .flags = cflags.items,
 219                    .condition = target.cpu.arch == .x86_64,
 220                },
 221                .{
 222                    .path = "amd64" ++ path.sep_str ++ "crt1_s.S",
 223                    .flags = acflags.items,
 224                    .condition = target.cpu.arch == .x86_64,
 225                },
 226            };
 227
 228            var files_buf: [sources.len]Compilation.CSourceFile = undefined;
 229            var files_index: usize = 0;
 230            for (sources) |file| {
 231                if (!file.condition) continue;
 232
 233                files_buf[files_index] = .{
 234                    .src_path = try csuPath(comp, arena, file.path),
 235                    .cache_exempt_flags = file.flags,
 236                    .owner = undefined,
 237                };
 238                files_index += 1;
 239            }
 240            const files = files_buf[0..files_index];
 241
 242            return comp.build_crt_file(
 243                if (comp.config.pie) "Scrt1" else "crt1",
 244                .Obj,
 245                .@"freebsd libc Scrt1.o",
 246                prog_node,
 247                files,
 248                .{
 249                    .omit_frame_pointer = false,
 250                    .pic = true,
 251                },
 252            );
 253        },
 254    }
 255}
 256
 257pub const Lib = struct {
 258    name: []const u8,
 259    sover: u8,
 260};
 261
 262pub const libs = [_]Lib{
 263    .{ .name = "m", .sover = 5 },
 264    .{ .name = "stdthreads", .sover = 0 },
 265    .{ .name = "thr", .sover = 3 },
 266    .{ .name = "c", .sover = 7 },
 267    .{ .name = "dl", .sover = 1 },
 268    .{ .name = "rt", .sover = 1 },
 269    .{ .name = "ld", .sover = 1 },
 270    .{ .name = "util", .sover = 9 },
 271    .{ .name = "execinfo", .sover = 1 },
 272};
 273
 274pub const ABI = struct {
 275    all_versions: []const Version, // all defined versions (one abilist from v2.0.0 up to current)
 276    all_targets: []const std.zig.target.ArchOsAbi,
 277    /// The bytes from the file verbatim, starting from the u16 number
 278    /// of function inclusions.
 279    inclusions: []const u8,
 280    arena_state: std.heap.ArenaAllocator.State,
 281
 282    pub fn destroy(abi: *ABI, gpa: Allocator) void {
 283        abi.arena_state.promote(gpa).deinit();
 284    }
 285};
 286
 287pub const LoadMetaDataError = error{
 288    /// The files that ship with the Zig compiler were unable to be read, or otherwise had malformed data.
 289    ZigInstallationCorrupt,
 290    OutOfMemory,
 291};
 292
 293pub const abilists_path = "libc" ++ path.sep_str ++ "freebsd" ++ path.sep_str ++ "abilists";
 294pub const abilists_max_size = 150 * 1024; // Bigger than this and something is definitely borked.
 295
 296/// This function will emit a log error when there is a problem with the zig
 297/// installation and then return `error.ZigInstallationCorrupt`.
 298pub fn loadMetaData(gpa: Allocator, contents: []const u8) LoadMetaDataError!*ABI {
 299    const tracy = trace(@src());
 300    defer tracy.end();
 301
 302    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
 303    errdefer arena_allocator.deinit();
 304    const arena = arena_allocator.allocator();
 305
 306    var index: usize = 0;
 307
 308    {
 309        const libs_len = contents[index];
 310        index += 1;
 311
 312        var i: u8 = 0;
 313        while (i < libs_len) : (i += 1) {
 314            const lib_name = mem.sliceTo(contents[index..], 0);
 315            index += lib_name.len + 1;
 316
 317            if (i >= libs.len or !mem.eql(u8, libs[i].name, lib_name)) {
 318                log.err("libc" ++ path.sep_str ++ "freebsd" ++ path.sep_str ++
 319                    "abilists: invalid library name or index ({d}): '{s}'", .{ i, lib_name });
 320                return error.ZigInstallationCorrupt;
 321            }
 322        }
 323    }
 324
 325    const versions = b: {
 326        const versions_len = contents[index];
 327        index += 1;
 328
 329        const versions = try arena.alloc(Version, versions_len);
 330        var i: u8 = 0;
 331        while (i < versions.len) : (i += 1) {
 332            versions[i] = .{
 333                .major = contents[index + 0],
 334                .minor = contents[index + 1],
 335                .patch = contents[index + 2],
 336            };
 337            index += 3;
 338        }
 339        break :b versions;
 340    };
 341
 342    const targets = b: {
 343        const targets_len = contents[index];
 344        index += 1;
 345
 346        const targets = try arena.alloc(std.zig.target.ArchOsAbi, targets_len);
 347        var i: u8 = 0;
 348        while (i < targets.len) : (i += 1) {
 349            const target_name = mem.sliceTo(contents[index..], 0);
 350            index += target_name.len + 1;
 351
 352            var component_it = mem.tokenizeScalar(u8, target_name, '-');
 353            const arch_name = component_it.next() orelse {
 354                log.err("abilists: expected arch name", .{});
 355                return error.ZigInstallationCorrupt;
 356            };
 357            const os_name = component_it.next() orelse {
 358                log.err("abilists: expected OS name", .{});
 359                return error.ZigInstallationCorrupt;
 360            };
 361            const abi_name = component_it.next() orelse {
 362                log.err("abilists: expected ABI name", .{});
 363                return error.ZigInstallationCorrupt;
 364            };
 365            const arch_tag = std.meta.stringToEnum(std.Target.Cpu.Arch, arch_name) orelse {
 366                log.err("abilists: unrecognized arch: '{s}'", .{arch_name});
 367                return error.ZigInstallationCorrupt;
 368            };
 369            if (!mem.eql(u8, os_name, "freebsd")) {
 370                log.err("abilists: expected OS 'freebsd', found '{s}'", .{os_name});
 371                return error.ZigInstallationCorrupt;
 372            }
 373            const abi_tag = std.meta.stringToEnum(std.Target.Abi, abi_name) orelse {
 374                log.err("abilists: unrecognized ABI: '{s}'", .{abi_name});
 375                return error.ZigInstallationCorrupt;
 376            };
 377
 378            targets[i] = .{
 379                .arch = arch_tag,
 380                .os = .freebsd,
 381                .abi = abi_tag,
 382            };
 383        }
 384        break :b targets;
 385    };
 386
 387    const abi = try arena.create(ABI);
 388    abi.* = .{
 389        .all_versions = versions,
 390        .all_targets = targets,
 391        .inclusions = contents[index..],
 392        .arena_state = arena_allocator.state,
 393    };
 394    return abi;
 395}
 396
 397pub const BuiltSharedObjects = struct {
 398    lock: Cache.Lock,
 399    dir_path: Path,
 400
 401    pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void {
 402        self.lock.release();
 403        gpa.free(self.dir_path.sub_path);
 404        self.* = undefined;
 405    }
 406};
 407
 408const all_map_basename = "all.map";
 409
 410fn wordDirective(target: *const std.Target) []const u8 {
 411    // Based on its description in the GNU `as` manual, you might assume that `.word` is sized
 412    // according to the target word size. But no; that would just make too much sense.
 413    return if (target.ptrBitWidth() == 64) ".quad" else ".long";
 414}
 415
 416/// TODO replace anyerror with explicit error set, recording user-friendly errors with
 417/// lockAndSetMiscFailure and returning error.AlreadyReported. see libcxx.zig for example.
 418pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anyerror!void {
 419    // See also glibc.zig which this code is based on.
 420
 421    const tracy = trace(@src());
 422    defer tracy.end();
 423
 424    if (!build_options.have_llvm) {
 425        return error.ZigCompilerNotBuiltWithLLVMExtensions;
 426    }
 427
 428    const gpa = comp.gpa;
 429    const io = comp.io;
 430
 431    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
 432    defer arena_allocator.deinit();
 433    const arena = arena_allocator.allocator();
 434
 435    const target = comp.getTarget();
 436    // FreeBSD 7 == FBSD_1.0, ..., FreeBSD 14 == FBSD_1.7
 437    const target_version: Version = .{ .major = 1, .minor = target.os.version_range.semver.min.major - 7, .patch = 0 };
 438
 439    // Use the global cache directory.
 440    var cache: Cache = .{
 441        .gpa = gpa,
 442        .io = io,
 443        .manifest_dir = try comp.dirs.global_cache.handle.makeOpenPath("h", .{}),
 444    };
 445    cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
 446    cache.addPrefix(comp.dirs.zig_lib);
 447    cache.addPrefix(comp.dirs.global_cache);
 448    defer cache.manifest_dir.close();
 449
 450    var man = cache.obtain();
 451    defer man.deinit();
 452    man.hash.addBytes(build_options.version);
 453    man.hash.add(target.cpu.arch);
 454    man.hash.add(target.abi);
 455    man.hash.add(target_version);
 456
 457    const full_abilists_path = try comp.dirs.zig_lib.join(arena, &.{abilists_path});
 458    const abilists_index = try man.addFile(full_abilists_path, abilists_max_size);
 459
 460    if (try man.hit()) {
 461        const digest = man.final();
 462
 463        return queueSharedObjects(comp, .{
 464            .lock = man.toOwnedLock(),
 465            .dir_path = .{
 466                .root_dir = comp.dirs.global_cache,
 467                .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest),
 468            },
 469        });
 470    }
 471
 472    const digest = man.final();
 473    const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
 474
 475    var o_directory: Cache.Directory = .{
 476        .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}),
 477        .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),
 478    };
 479    defer o_directory.handle.close();
 480
 481    const abilists_contents = man.files.keys()[abilists_index].contents.?;
 482    const metadata = try loadMetaData(gpa, abilists_contents);
 483    defer metadata.destroy(gpa);
 484
 485    const target_targ_index = for (metadata.all_targets, 0..) |targ, i| {
 486        if (targ.arch == target.cpu.arch and
 487            targ.os == target.os.tag and
 488            targ.abi == target.abi)
 489        {
 490            break i;
 491        }
 492    } else {
 493        unreachable; // std.zig.target.available_libcs prevents us from getting here
 494    };
 495
 496    const target_ver_index = for (metadata.all_versions, 0..) |ver, i| {
 497        switch (ver.order(target_version)) {
 498            .eq => break i,
 499            .lt => continue,
 500            .gt => {
 501                // TODO Expose via compile error mechanism instead of log.
 502                log.warn("invalid target FreeBSD libc version: {f}", .{target_version});
 503                return error.InvalidTargetLibCVersion;
 504            },
 505        }
 506    } else blk: {
 507        const latest_index = metadata.all_versions.len - 1;
 508        log.warn("zig cannot build new FreeBSD libc version {f}; providing instead {f}", .{
 509            target_version, metadata.all_versions[latest_index],
 510        });
 511        break :blk latest_index;
 512    };
 513
 514    {
 515        var map_contents = std.array_list.Managed(u8).init(arena);
 516        for (metadata.all_versions[0 .. target_ver_index + 1]) |ver| {
 517            try map_contents.print("FBSD_{d}.{d} {{ }};\n", .{ ver.major, ver.minor });
 518        }
 519        try o_directory.handle.writeFile(.{ .sub_path = all_map_basename, .data = map_contents.items });
 520        map_contents.deinit();
 521    }
 522
 523    var stubs_asm = std.array_list.Managed(u8).init(gpa);
 524    defer stubs_asm.deinit();
 525
 526    for (libs, 0..) |lib, lib_i| {
 527        stubs_asm.shrinkRetainingCapacity(0);
 528
 529        try stubs_asm.appendSlice(".text\n");
 530
 531        var sym_i: usize = 0;
 532        var sym_name_buf: std.Io.Writer.Allocating = .init(arena);
 533        var opt_symbol_name: ?[]const u8 = null;
 534        var versions = try std.DynamicBitSetUnmanaged.initEmpty(arena, metadata.all_versions.len);
 535        var weak_linkages = try std.DynamicBitSetUnmanaged.initEmpty(arena, metadata.all_versions.len);
 536
 537        var inc_reader: std.Io.Reader = .fixed(metadata.inclusions);
 538
 539        const fn_inclusions_len = try inc_reader.takeInt(u16, .little);
 540
 541        // Pick the default symbol version:
 542        // - If there are no versions, don't emit it
 543        // - Take the greatest one <= than the target one
 544        // - If none of them is <= than the
 545        //   specified one don't pick any default version
 546        var chosen_def_ver_index: usize = 255;
 547        var chosen_unversioned_ver_index: usize = 255;
 548
 549        while (sym_i < fn_inclusions_len) : (sym_i += 1) {
 550            const sym_name = opt_symbol_name orelse n: {
 551                sym_name_buf.clearRetainingCapacity();
 552                _ = try inc_reader.streamDelimiter(&sym_name_buf.writer, 0);
 553                assert(inc_reader.buffered()[0] == 0); // TODO change streamDelimiter API
 554                inc_reader.toss(1);
 555
 556                opt_symbol_name = sym_name_buf.written();
 557                versions.unsetAll();
 558                weak_linkages.unsetAll();
 559                chosen_def_ver_index = 255;
 560                chosen_unversioned_ver_index = 255;
 561
 562                break :n sym_name_buf.written();
 563            };
 564            {
 565                const targets = try inc_reader.takeLeb128(u64);
 566                var lib_index = try inc_reader.takeByte();
 567
 568                const is_unversioned = (lib_index & (1 << 5)) != 0;
 569                const is_weak = (lib_index & (1 << 6)) != 0;
 570                const is_terminal = (lib_index & (1 << 7)) != 0;
 571
 572                lib_index = @as(u5, @truncate(lib_index));
 573
 574                // Test whether the inclusion applies to our current library and target.
 575                const ok_lib_and_target =
 576                    (lib_index == lib_i) and
 577                    ((targets & (@as(u64, 1) << @as(u6, @intCast(target_targ_index)))) != 0);
 578
 579                while (true) {
 580                    const byte = try inc_reader.takeByte();
 581                    const last = (byte & 0b1000_0000) != 0;
 582                    const ver_i = @as(u7, @truncate(byte));
 583                    if (ok_lib_and_target and ver_i <= target_ver_index) {
 584                        if (is_unversioned) {
 585                            if (chosen_unversioned_ver_index == 255 or ver_i > chosen_unversioned_ver_index) {
 586                                chosen_unversioned_ver_index = ver_i;
 587                            }
 588                        } else {
 589                            if (chosen_def_ver_index == 255 or ver_i > chosen_def_ver_index) {
 590                                chosen_def_ver_index = ver_i;
 591                            }
 592
 593                            versions.set(ver_i);
 594                        }
 595
 596                        weak_linkages.setValue(ver_i, is_weak);
 597                    }
 598                    if (last) break;
 599                }
 600
 601                if (is_terminal) {
 602                    opt_symbol_name = null;
 603                } else continue;
 604            }
 605
 606            if (chosen_unversioned_ver_index != 255) {
 607                // Example:
 608                // .balign 4
 609                // .globl _Exit
 610                // .type _Exit, %function
 611                // _Exit: .long 0
 612                try stubs_asm.print(
 613                    \\.balign {d}
 614                    \\.{s} {s}
 615                    \\.type {s}, %function
 616                    \\{s}: {s} 0
 617                    \\
 618                , .{
 619                    target.ptrBitWidth() / 8,
 620                    if (weak_linkages.isSet(chosen_unversioned_ver_index)) "weak" else "globl",
 621                    sym_name,
 622                    sym_name,
 623                    sym_name,
 624                    wordDirective(target),
 625                });
 626            }
 627
 628            {
 629                var versions_iter = versions.iterator(.{});
 630                while (versions_iter.next()) |ver_index| {
 631                    // Example:
 632                    // .balign 4
 633                    // .globl _Exit_1_0
 634                    // .type _Exit_1_0, %function
 635                    // .symver _Exit_1_0, _Exit@@FBSD_1.0, remove
 636                    // _Exit_1_0: .long 0
 637                    const ver = metadata.all_versions[ver_index];
 638                    const sym_plus_ver = try std.fmt.allocPrint(
 639                        arena,
 640                        "{s}_FBSD_{d}_{d}",
 641                        .{ sym_name, ver.major, ver.minor },
 642                    );
 643
 644                    try stubs_asm.print(
 645                        \\.balign {d}
 646                        \\.{s} {s}
 647                        \\.type {s}, %function
 648                        \\.symver {s}, {s}{s}FBSD_{d}.{d}, remove
 649                        \\{s}: {s} 0
 650                        \\
 651                    , .{
 652                        target.ptrBitWidth() / 8,
 653                        if (weak_linkages.isSet(ver_index)) "weak" else "globl",
 654                        sym_plus_ver,
 655                        sym_plus_ver,
 656                        sym_plus_ver,
 657                        sym_name,
 658                        // Default symbol version definition vs normal symbol version definition
 659                        if (chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index) "@@" else "@",
 660                        ver.major,
 661                        ver.minor,
 662                        sym_plus_ver,
 663                        wordDirective(target),
 664                    });
 665                }
 666            }
 667        }
 668
 669        try stubs_asm.appendSlice(".data\n");
 670
 671        // FreeBSD's `libc.so.7` contains strong references to `__progname` and `environ` which are
 672        // defined in the statically-linked startup code. Those references cause the linker to put
 673        // the symbols in the dynamic symbol table. We need to create dummy references to them here
 674        // to get the same effect.
 675        if (std.mem.eql(u8, lib.name, "c")) {
 676            try stubs_asm.print(
 677                \\.balign {d}
 678                \\.globl __progname
 679                \\.globl environ
 680                \\{s} __progname
 681                \\{s} environ
 682                \\
 683            , .{
 684                target.ptrBitWidth() / 8,
 685                wordDirective(target),
 686                wordDirective(target),
 687            });
 688        }
 689
 690        const obj_inclusions_len = try inc_reader.takeInt(u16, .little);
 691
 692        var sizes = try arena.alloc(u16, metadata.all_versions.len);
 693
 694        sym_i = 0;
 695        opt_symbol_name = null;
 696
 697        while (sym_i < obj_inclusions_len) : (sym_i += 1) {
 698            const sym_name = opt_symbol_name orelse n: {
 699                sym_name_buf.clearRetainingCapacity();
 700                _ = try inc_reader.streamDelimiter(&sym_name_buf.writer, 0);
 701                assert(inc_reader.buffered()[0] == 0); // TODO change streamDelimiter API
 702                inc_reader.toss(1);
 703
 704                opt_symbol_name = sym_name_buf.written();
 705                versions.unsetAll();
 706                weak_linkages.unsetAll();
 707                chosen_def_ver_index = 255;
 708                chosen_unversioned_ver_index = 255;
 709
 710                break :n sym_name_buf.written();
 711            };
 712
 713            {
 714                const targets = try inc_reader.takeLeb128(u64);
 715                const size = try inc_reader.takeLeb128(u16);
 716                var lib_index = try inc_reader.takeByte();
 717
 718                const is_unversioned = (lib_index & (1 << 5)) != 0;
 719                const is_weak = (lib_index & (1 << 6)) != 0;
 720                const is_terminal = (lib_index & (1 << 7)) != 0;
 721
 722                lib_index = @as(u5, @truncate(lib_index));
 723
 724                // Test whether the inclusion applies to our current library and target.
 725                const ok_lib_and_target =
 726                    (lib_index == lib_i) and
 727                    ((targets & (@as(u64, 1) << @as(u6, @intCast(target_targ_index)))) != 0);
 728
 729                while (true) {
 730                    const byte = try inc_reader.takeByte();
 731                    const last = (byte & 0b1000_0000) != 0;
 732                    const ver_i = @as(u7, @truncate(byte));
 733                    if (ok_lib_and_target and ver_i <= target_ver_index) {
 734                        if (is_unversioned) {
 735                            if (chosen_unversioned_ver_index == 255 or ver_i > chosen_unversioned_ver_index) {
 736                                chosen_unversioned_ver_index = ver_i;
 737                            }
 738                        } else {
 739                            if (chosen_def_ver_index == 255 or ver_i > chosen_def_ver_index) {
 740                                chosen_def_ver_index = ver_i;
 741                            }
 742
 743                            versions.set(ver_i);
 744                        }
 745
 746                        sizes[ver_i] = size;
 747                        weak_linkages.setValue(ver_i, is_weak);
 748                    }
 749                    if (last) break;
 750                }
 751
 752                if (is_terminal) {
 753                    opt_symbol_name = null;
 754                } else continue;
 755            }
 756
 757            if (chosen_unversioned_ver_index != 255) {
 758                // Example:
 759                // .balign 4
 760                // .globl malloc_conf
 761                // .type malloc_conf, %object
 762                // .size malloc_conf, 4
 763                // malloc_conf: .fill 4, 1, 0
 764                try stubs_asm.print(
 765                    \\.balign {d}
 766                    \\.{s} {s}
 767                    \\.type {s}, %object
 768                    \\.size {s}, {d}
 769                    \\{s}: {s} 0
 770                    \\
 771                , .{
 772                    target.ptrBitWidth() / 8,
 773                    if (weak_linkages.isSet(chosen_unversioned_ver_index)) "weak" else "globl",
 774                    sym_name,
 775                    sym_name,
 776                    sym_name,
 777                    sizes[chosen_unversioned_ver_index],
 778                    sym_name,
 779                    wordDirective(target),
 780                });
 781            }
 782
 783            {
 784                var versions_iter = versions.iterator(.{});
 785                while (versions_iter.next()) |ver_index| {
 786                    // Example:
 787                    // .balign 4
 788                    // .globl malloc_conf_1_3
 789                    // .type malloc_conf_1_3, %object
 790                    // .size malloc_conf_1_3, 4
 791                    // .symver malloc_conf_1_3, malloc_conf@@FBSD_1.3
 792                    // malloc_conf_1_3: .fill 4, 1, 0
 793                    const ver = metadata.all_versions[ver_index];
 794                    const sym_plus_ver = try std.fmt.allocPrint(
 795                        arena,
 796                        "{s}_FBSD_{d}_{d}",
 797                        .{ sym_name, ver.major, ver.minor },
 798                    );
 799
 800                    try stubs_asm.print(
 801                        \\.balign {d}
 802                        \\.{s} {s}
 803                        \\.type {s}, %object
 804                        \\.size {s}, {d}
 805                        \\.symver {s}, {s}{s}FBSD_{d}.{d}
 806                        \\{s}: .fill {d}, 1, 0
 807                        \\
 808                    , .{
 809                        target.ptrBitWidth() / 8,
 810                        if (weak_linkages.isSet(ver_index)) "weak" else "globl",
 811                        sym_plus_ver,
 812                        sym_plus_ver,
 813                        sym_plus_ver,
 814                        sizes[ver_index],
 815                        sym_plus_ver,
 816                        sym_name,
 817                        // Default symbol version definition vs normal symbol version definition
 818                        if (chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index) "@@" else "@",
 819                        ver.major,
 820                        ver.minor,
 821                        sym_plus_ver,
 822                        sizes[ver_index],
 823                    });
 824                }
 825            }
 826        }
 827
 828        try stubs_asm.appendSlice(".tdata\n");
 829
 830        const tls_inclusions_len = try inc_reader.takeInt(u16, .little);
 831
 832        sym_i = 0;
 833        opt_symbol_name = null;
 834
 835        while (sym_i < tls_inclusions_len) : (sym_i += 1) {
 836            const sym_name = opt_symbol_name orelse n: {
 837                sym_name_buf.clearRetainingCapacity();
 838                _ = try inc_reader.streamDelimiter(&sym_name_buf.writer, 0);
 839                assert(inc_reader.buffered()[0] == 0); // TODO change streamDelimiter API
 840                inc_reader.toss(1);
 841
 842                opt_symbol_name = sym_name_buf.written();
 843                versions.unsetAll();
 844                weak_linkages.unsetAll();
 845                chosen_def_ver_index = 255;
 846                chosen_unversioned_ver_index = 255;
 847
 848                break :n sym_name_buf.written();
 849            };
 850
 851            {
 852                const targets = try inc_reader.takeLeb128(u64);
 853                const size = try inc_reader.takeLeb128(u16);
 854                var lib_index = try inc_reader.takeByte();
 855
 856                const is_unversioned = (lib_index & (1 << 5)) != 0;
 857                const is_weak = (lib_index & (1 << 6)) != 0;
 858                const is_terminal = (lib_index & (1 << 7)) != 0;
 859
 860                lib_index = @as(u5, @truncate(lib_index));
 861
 862                // Test whether the inclusion applies to our current library and target.
 863                const ok_lib_and_target =
 864                    (lib_index == lib_i) and
 865                    ((targets & (@as(u64, 1) << @as(u6, @intCast(target_targ_index)))) != 0);
 866
 867                while (true) {
 868                    const byte = try inc_reader.takeByte();
 869                    const last = (byte & 0b1000_0000) != 0;
 870                    const ver_i = @as(u7, @truncate(byte));
 871                    if (ok_lib_and_target and ver_i <= target_ver_index) {
 872                        if (is_unversioned) {
 873                            if (chosen_unversioned_ver_index == 255 or ver_i > chosen_unversioned_ver_index) {
 874                                chosen_unversioned_ver_index = ver_i;
 875                            }
 876                        } else {
 877                            if (chosen_def_ver_index == 255 or ver_i > chosen_def_ver_index) {
 878                                chosen_def_ver_index = ver_i;
 879                            }
 880
 881                            versions.set(ver_i);
 882                        }
 883
 884                        sizes[ver_i] = size;
 885                        weak_linkages.setValue(ver_i, is_weak);
 886                    }
 887                    if (last) break;
 888                }
 889
 890                if (is_terminal) {
 891                    opt_symbol_name = null;
 892                } else continue;
 893            }
 894
 895            if (chosen_unversioned_ver_index != 255) {
 896                // Example:
 897                // .balign 4
 898                // .globl _ThreadRuneLocale
 899                // .type _ThreadRuneLocale, %object
 900                // .size _ThreadRuneLocale, 4
 901                // _ThreadRuneLocale: .fill 4, 1, 0
 902                try stubs_asm.print(
 903                    \\.balign {d}
 904                    \\.{s} {s}
 905                    \\.type {s}, %tls_object
 906                    \\.size {s}, {d}
 907                    \\{s}: {s} 0
 908                    \\
 909                , .{
 910                    target.ptrBitWidth() / 8,
 911                    if (weak_linkages.isSet(chosen_unversioned_ver_index)) "weak" else "globl",
 912                    sym_name,
 913                    sym_name,
 914                    sym_name,
 915                    sizes[chosen_unversioned_ver_index],
 916                    sym_name,
 917                    wordDirective(target),
 918                });
 919            }
 920
 921            {
 922                var versions_iter = versions.iterator(.{});
 923                while (versions_iter.next()) |ver_index| {
 924                    // Example:
 925                    // .balign 4
 926                    // .globl _ThreadRuneLocale_1_3
 927                    // .type _ThreadRuneLocale_1_3, %tls_object
 928                    // .size _ThreadRuneLocale_1_3, 4
 929                    // .symver _ThreadRuneLocale_1_3, _ThreadRuneLocale@@FBSD_1.3
 930                    // _ThreadRuneLocale_1_3: .fill 4, 1, 0
 931                    const ver = metadata.all_versions[ver_index];
 932                    const sym_plus_ver = try std.fmt.allocPrint(
 933                        arena,
 934                        "{s}_FBSD_{d}_{d}",
 935                        .{ sym_name, ver.major, ver.minor },
 936                    );
 937
 938                    try stubs_asm.print(
 939                        \\.balign {d}
 940                        \\.{s} {s}
 941                        \\.type {s}, %tls_object
 942                        \\.size {s}, {d}
 943                        \\.symver {s}, {s}{s}FBSD_{d}.{d}
 944                        \\{s}: .fill {d}, 1, 0
 945                        \\
 946                    , .{
 947                        target.ptrBitWidth() / 8,
 948                        if (weak_linkages.isSet(ver_index)) "weak" else "globl",
 949                        sym_plus_ver,
 950                        sym_plus_ver,
 951                        sym_plus_ver,
 952                        sizes[ver_index],
 953                        sym_plus_ver,
 954                        sym_name,
 955                        // Default symbol version definition vs normal symbol version definition
 956                        if (chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index) "@@" else "@",
 957                        ver.major,
 958                        ver.minor,
 959                        sym_plus_ver,
 960                        sizes[ver_index],
 961                    });
 962                }
 963            }
 964        }
 965
 966        var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "stdthreads", etc.
 967        const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;
 968        try o_directory.handle.writeFile(.{ .sub_path = asm_file_basename, .data = stubs_asm.items });
 969        try buildSharedLib(comp, arena, o_directory, asm_file_basename, lib, prog_node);
 970    }
 971
 972    man.writeManifest() catch |err| {
 973        log.warn("failed to write cache manifest for FreeBSD libc stubs: {s}", .{@errorName(err)});
 974    };
 975
 976    return queueSharedObjects(comp, .{
 977        .lock = man.toOwnedLock(),
 978        .dir_path = .{
 979            .root_dir = comp.dirs.global_cache,
 980            .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest),
 981        },
 982    });
 983}
 984
 985fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void {
 986    assert(comp.freebsd_so_files == null);
 987    comp.freebsd_so_files = so_files;
 988
 989    var task_buffer: [libs.len]link.PrelinkTask = undefined;
 990    var task_buffer_i: usize = 0;
 991
 992    {
 993        comp.mutex.lock(); // protect comp.arena
 994        defer comp.mutex.unlock();
 995
 996        for (libs) |lib| {
 997            const so_path: Path = .{
 998                .root_dir = so_files.dir_path.root_dir,
 999                .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{
1000                    so_files.dir_path.sub_path, fs.path.sep, lib.name, lib.sover,
1001                }) catch return comp.setAllocFailure(),
1002            };
1003            task_buffer[task_buffer_i] = .{ .load_dso = so_path };
1004            task_buffer_i += 1;
1005        }
1006    }
1007
1008    comp.queuePrelinkTasks(task_buffer[0..task_buffer_i]);
1009}
1010
1011fn buildSharedLib(
1012    comp: *Compilation,
1013    arena: Allocator,
1014    bin_directory: Cache.Directory,
1015    asm_file_basename: []const u8,
1016    lib: Lib,
1017    prog_node: std.Progress.Node,
1018) !void {
1019    const tracy = trace(@src());
1020    defer tracy.end();
1021
1022    const io = comp.io;
1023    const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
1024    const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
1025    const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
1026    const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename;
1027    const map_file_path = try path.join(arena, &.{ bin_directory.path.?, all_map_basename });
1028
1029    const optimize_mode = comp.compilerRtOptMode();
1030    const strip = comp.compilerRtStrip();
1031    const config = try Compilation.Config.resolve(.{
1032        .output_mode = .Lib,
1033        .link_mode = .dynamic,
1034        .resolved_target = comp.root_mod.resolved_target,
1035        .is_test = false,
1036        .have_zcu = false,
1037        .emit_bin = true,
1038        .root_optimize_mode = optimize_mode,
1039        .root_strip = strip,
1040        .link_libc = false,
1041    });
1042
1043    const root_mod = try Module.create(arena, .{
1044        .paths = .{
1045            .root = .zig_lib_root,
1046            .root_src_path = "",
1047        },
1048        .fully_qualified_name = "root",
1049        .inherited = .{
1050            .resolved_target = comp.root_mod.resolved_target,
1051            .strip = strip,
1052            .stack_check = false,
1053            .stack_protector = 0,
1054            .sanitize_c = .off,
1055            .sanitize_thread = false,
1056            .red_zone = comp.root_mod.red_zone,
1057            .omit_frame_pointer = comp.root_mod.omit_frame_pointer,
1058            .valgrind = false,
1059            .optimize_mode = optimize_mode,
1060            .structured_cfg = comp.root_mod.structured_cfg,
1061        },
1062        .global = config,
1063        .cc_argv = &.{},
1064        .parent = null,
1065    });
1066
1067    const c_source_files = [1]Compilation.CSourceFile{
1068        .{
1069            .src_path = try path.join(arena, &.{ bin_directory.path.?, asm_file_basename }),
1070            .owner = root_mod,
1071        },
1072    };
1073
1074    const misc_task: Compilation.MiscTask = .@"freebsd libc shared object";
1075
1076    var sub_create_diag: Compilation.CreateDiagnostic = undefined;
1077    const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{
1078        .dirs = comp.dirs.withoutLocalCache(),
1079        .thread_pool = comp.thread_pool,
1080        .self_exe_path = comp.self_exe_path,
1081        // Because we manually cache the whole set of objects, we don't cache the individual objects
1082        // within it. In fact, we *can't* do that, because we need `emit_bin` to specify the path.
1083        .cache_mode = .none,
1084        .config = config,
1085        .root_mod = root_mod,
1086        .root_name = lib.name,
1087        .libc_installation = comp.libc_installation,
1088        .emit_bin = .{ .yes_path = try bin_directory.join(arena, &.{basename}) },
1089        .verbose_cc = comp.verbose_cc,
1090        .verbose_link = comp.verbose_link,
1091        .verbose_air = comp.verbose_air,
1092        .verbose_llvm_ir = comp.verbose_llvm_ir,
1093        .verbose_llvm_bc = comp.verbose_llvm_bc,
1094        .verbose_cimport = comp.verbose_cimport,
1095        .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
1096        .clang_passthrough_mode = comp.clang_passthrough_mode,
1097        .version = version,
1098        .version_script = map_file_path,
1099        .soname = soname,
1100        .c_source_files = &c_source_files,
1101        .skip_linker_dependencies = true,
1102    }) catch |err| switch (err) {
1103        error.CreateFail => {
1104            comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag });
1105            return error.AlreadyReported;
1106        },
1107        else => |e| return e,
1108    };
1109    defer sub_compilation.destroy();
1110
1111    try comp.updateSubCompilation(sub_compilation, misc_task, prog_node);
1112}