Commit d3a6236eef

Alex Rønne Petersen <alex@alexrp.com>
2025-05-05 07:23:28
compiler: Support building FreeBSD crt1.o/Scrt1.o and stub shared libraries.
Only works for FreeBSD 14+. Note that we still default to targeting FreeBSD 13. Contributes to #2876.
1 parent 0e3609b
src/libs/freebsd.zig
@@ -0,0 +1,1104 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const mem = std.mem;
+const log = std.log;
+const fs = std.fs;
+const path = fs.path;
+const assert = std.debug.assert;
+const Version = std.SemanticVersion;
+const Path = std.Build.Cache.Path;
+
+const Compilation = @import("../Compilation.zig");
+const build_options = @import("build_options");
+const trace = @import("../tracy.zig").trace;
+const Cache = std.Build.Cache;
+const Module = @import("../Package/Module.zig");
+const link = @import("../link.zig");
+
+pub const CrtFile = enum {
+    scrt1_o,
+};
+
+pub fn needsCrt0(output_mode: std.builtin.OutputMode) ?CrtFile {
+    // For shared libraries and PIC executables, we should actually link in a variant of crt1 that
+    // is built with `-DSHARED` so that it calls `__cxa_finalize` in an ELF destructor. However, we
+    // currently make no effort to respect `__cxa_finalize` on any other targets, so for now, we're
+    // not doing it here either.
+    //
+    // See: https://github.com/ziglang/zig/issues/23574#issuecomment-2869089897
+    return switch (output_mode) {
+        .Obj, .Lib => null,
+        .Exe => .scrt1_o,
+    };
+}
+
+fn includePath(comp: *Compilation, arena: Allocator, sub_path: []const u8) ![]const u8 {
+    return path.join(arena, &.{
+        comp.zig_lib_directory.path.?,
+        "libc" ++ path.sep_str ++ "include",
+        sub_path,
+    });
+}
+
+fn csuPath(comp: *Compilation, arena: Allocator, sub_path: []const u8) ![]const u8 {
+    return path.join(arena, &.{
+        comp.zig_lib_directory.path.?,
+        "libc" ++ path.sep_str ++ "freebsd" ++ path.sep_str ++ "lib" ++ path.sep_str ++ "csu",
+        sub_path,
+    });
+}
+
+fn libcPath(comp: *Compilation, arena: Allocator, sub_path: []const u8) ![]const u8 {
+    return path.join(arena, &.{
+        comp.zig_lib_directory.path.?,
+        "libc" ++ path.sep_str ++ "freebsd" ++ path.sep_str ++ "lib" ++ path.sep_str ++ "libc",
+        sub_path,
+    });
+}
+
+/// TODO replace anyerror with explicit error set, recording user-friendly errors with
+/// setMiscFailure and returning error.SubCompilationFailed. see libcxx.zig for example.
+pub fn buildCrtFile(comp: *Compilation, crt_file: CrtFile, prog_node: std.Progress.Node) anyerror!void {
+    if (!build_options.have_llvm) return error.ZigCompilerNotBuiltWithLLVMExtensions;
+
+    const gpa = comp.gpa;
+    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
+    defer arena_allocator.deinit();
+    const arena = arena_allocator.allocator();
+
+    const target = comp.root_mod.resolved_target.result;
+
+    // In all cases in this function, we add the C compiler flags to
+    // cache_exempt_flags rather than extra_flags, because these arguments
+    // depend on only properties that are already covered by the cache
+    // manifest. Including these arguments in the cache could only possibly
+    // waste computation and create false negatives.
+
+    switch (crt_file) {
+        .scrt1_o => {
+            var cflags = std.ArrayList([]const u8).init(arena);
+            try cflags.appendSlice(&.{
+                "-O2",
+                "-fno-common",
+                "-std=gnu99",
+                "-DPIC",
+                "-w", // Disable all warnings.
+            });
+
+            if (target.cpu.arch.isPowerPC64()) {
+                try cflags.append("-mlongcall");
+            }
+
+            var acflags = std.ArrayList([]const u8).init(arena);
+            try acflags.appendSlice(&.{
+                "-DLOCORE",
+                // See `Compilation.addCCArgs`.
+                try std.fmt.allocPrint(arena, "-D__FreeBSD_version={d}", .{target.os.version_range.semver.min.major * 100_000}),
+            });
+
+            inline for (.{ &cflags, &acflags }) |flags| {
+                try flags.appendSlice(&.{
+                    "-DSTRIP_FBSDID",
+                    "-I",
+                    try includePath(comp, arena, try std.fmt.allocPrint(arena, "{s}-{s}-{s}", .{
+                        std.zig.target.freebsdArchNameHeaders(target.cpu.arch),
+                        @tagName(target.os.tag),
+                        @tagName(target.abi),
+                    })),
+                    "-I",
+                    try includePath(comp, arena, "generic-freebsd"),
+                    "-I",
+                    try csuPath(comp, arena, switch (target.cpu.arch) {
+                        .arm, .thumb => "arm",
+                        .aarch64 => "aarch64",
+                        .powerpc => "powerpc",
+                        .powerpc64, .powerpc64le => "powerpc64",
+                        .riscv64 => "riscv",
+                        .x86 => "i386",
+                        .x86_64 => "amd64",
+                        else => unreachable,
+                    }),
+                    "-I",
+                    try csuPath(comp, arena, "common"),
+                    "-I",
+                    try libcPath(comp, arena, "include"),
+                    "-Qunused-arguments",
+                });
+            }
+
+            const sources = [_]struct {
+                path: []const u8,
+                flags: []const []const u8,
+                condition: bool = true,
+            }{
+                .{
+                    .path = "common" ++ path.sep_str ++ "crtbegin.c",
+                    .flags = cflags.items,
+                },
+                .{
+                    .path = "common" ++ path.sep_str ++ "crtbrand.S",
+                    .flags = acflags.items,
+                },
+                .{
+                    .path = "common" ++ path.sep_str ++ "crtend.c",
+                    .flags = cflags.items,
+                },
+                .{
+                    .path = "common" ++ path.sep_str ++ "feature_note.S",
+                    .flags = acflags.items,
+                },
+                .{
+                    .path = "common" ++ path.sep_str ++ "ignore_init_note.S",
+                    .flags = acflags.items,
+                },
+
+                .{
+                    .path = "arm" ++ path.sep_str ++ "crt1_c.c",
+                    .flags = cflags.items,
+                    .condition = target.cpu.arch == .arm or target.cpu.arch == .thumb,
+                },
+                .{
+                    .path = "arm" ++ path.sep_str ++ "crt1_s.S",
+                    .flags = acflags.items,
+                    .condition = target.cpu.arch == .arm or target.cpu.arch == .thumb,
+                },
+
+                .{
+                    .path = "aarch64" ++ path.sep_str ++ "crt1_c.c",
+                    .flags = cflags.items,
+                    .condition = target.cpu.arch == .aarch64,
+                },
+                .{
+                    .path = "aarch64" ++ path.sep_str ++ "crt1_s.S",
+                    .flags = acflags.items,
+                    .condition = target.cpu.arch == .aarch64,
+                },
+
+                .{
+                    .path = "powerpc" ++ path.sep_str ++ "crt1_c.c",
+                    .flags = cflags.items,
+                    .condition = target.cpu.arch == .powerpc,
+                },
+                .{
+                    .path = "powerpc" ++ path.sep_str ++ "crtsavres.S",
+                    .flags = acflags.items,
+                    .condition = target.cpu.arch == .powerpc,
+                },
+
+                .{
+                    .path = "powerpc64" ++ path.sep_str ++ "crt1_c.c",
+                    .flags = cflags.items,
+                    .condition = target.cpu.arch.isPowerPC64(),
+                },
+
+                .{
+                    .path = "riscv" ++ path.sep_str ++ "crt1_c.c",
+                    .flags = cflags.items,
+                    .condition = target.cpu.arch == .riscv64,
+                },
+                .{
+                    .path = "riscv" ++ path.sep_str ++ "crt1_s.S",
+                    .flags = acflags.items,
+                    .condition = target.cpu.arch == .riscv64,
+                },
+
+                .{
+                    .path = "i386" ++ path.sep_str ++ "crt1_c.c",
+                    .flags = cflags.items,
+                    .condition = target.cpu.arch == .x86,
+                },
+                .{
+                    .path = "i386" ++ path.sep_str ++ "crt1_s.S",
+                    .flags = acflags.items,
+                    .condition = target.cpu.arch == .x86,
+                },
+
+                .{
+                    .path = "amd64" ++ path.sep_str ++ "crt1_c.c",
+                    .flags = cflags.items,
+                    .condition = target.cpu.arch == .x86_64,
+                },
+                .{
+                    .path = "amd64" ++ path.sep_str ++ "crt1_s.S",
+                    .flags = acflags.items,
+                    .condition = target.cpu.arch == .x86_64,
+                },
+            };
+
+            var files_buf: [sources.len]Compilation.CSourceFile = undefined;
+            var files_index: usize = 0;
+            for (sources) |file| {
+                if (!file.condition) continue;
+
+                files_buf[files_index] = .{
+                    .src_path = try csuPath(comp, arena, file.path),
+                    .cache_exempt_flags = file.flags,
+                    .owner = undefined,
+                };
+                files_index += 1;
+            }
+            const files = files_buf[0..files_index];
+
+            return comp.build_crt_file(
+                if (comp.config.pie) "Scrt1" else "crt1",
+                .Obj,
+                .@"freebsd libc Scrt1.o",
+                prog_node,
+                files,
+                .{
+                    .omit_frame_pointer = false,
+                    .pic = true,
+                },
+            );
+        },
+    }
+}
+
+pub const Lib = struct {
+    name: []const u8,
+    sover: u8,
+};
+
+pub const libs = [_]Lib{
+    .{ .name = "m", .sover = 5 },
+    .{ .name = "stdthreads", .sover = 0 },
+    .{ .name = "thr", .sover = 3 },
+    .{ .name = "c", .sover = 7 },
+    .{ .name = "dl", .sover = 1 },
+    .{ .name = "rt", .sover = 1 },
+    .{ .name = "ld", .sover = 1 },
+    .{ .name = "util", .sover = 9 },
+    .{ .name = "execinfo", .sover = 1 },
+};
+
+pub const ABI = struct {
+    all_versions: []const Version, // all defined versions (one abilist from v2.0.0 up to current)
+    all_targets: []const std.zig.target.ArchOsAbi,
+    /// The bytes from the file verbatim, starting from the u16 number
+    /// of function inclusions.
+    inclusions: []const u8,
+    arena_state: std.heap.ArenaAllocator.State,
+
+    pub fn destroy(abi: *ABI, gpa: Allocator) void {
+        abi.arena_state.promote(gpa).deinit();
+    }
+};
+
+pub const LoadMetaDataError = error{
+    /// The files that ship with the Zig compiler were unable to be read, or otherwise had malformed data.
+    ZigInstallationCorrupt,
+    OutOfMemory,
+};
+
+pub const abilists_path = "libc" ++ path.sep_str ++ "freebsd" ++ path.sep_str ++ "abilists";
+pub const abilists_max_size = 150 * 1024; // Bigger than this and something is definitely borked.
+
+/// This function will emit a log error when there is a problem with the zig
+/// installation and then return `error.ZigInstallationCorrupt`.
+pub fn loadMetaData(gpa: Allocator, contents: []const u8) LoadMetaDataError!*ABI {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
+    errdefer arena_allocator.deinit();
+    const arena = arena_allocator.allocator();
+
+    var index: usize = 0;
+
+    {
+        const libs_len = contents[index];
+        index += 1;
+
+        var i: u8 = 0;
+        while (i < libs_len) : (i += 1) {
+            const lib_name = mem.sliceTo(contents[index..], 0);
+            index += lib_name.len + 1;
+
+            if (i >= libs.len or !mem.eql(u8, libs[i].name, lib_name)) {
+                log.err("libc" ++ path.sep_str ++ "freebsd" ++ path.sep_str ++
+                    "abilists: invalid library name or index ({d}): '{s}'", .{ i, lib_name });
+                return error.ZigInstallationCorrupt;
+            }
+        }
+    }
+
+    const versions = b: {
+        const versions_len = contents[index];
+        index += 1;
+
+        const versions = try arena.alloc(Version, versions_len);
+        var i: u8 = 0;
+        while (i < versions.len) : (i += 1) {
+            versions[i] = .{
+                .major = contents[index + 0],
+                .minor = contents[index + 1],
+                .patch = contents[index + 2],
+            };
+            index += 3;
+        }
+        break :b versions;
+    };
+
+    const targets = b: {
+        const targets_len = contents[index];
+        index += 1;
+
+        const targets = try arena.alloc(std.zig.target.ArchOsAbi, targets_len);
+        var i: u8 = 0;
+        while (i < targets.len) : (i += 1) {
+            const target_name = mem.sliceTo(contents[index..], 0);
+            index += target_name.len + 1;
+
+            var component_it = mem.tokenizeScalar(u8, target_name, '-');
+            const arch_name = component_it.next() orelse {
+                log.err("abilists: expected arch name", .{});
+                return error.ZigInstallationCorrupt;
+            };
+            const os_name = component_it.next() orelse {
+                log.err("abilists: expected OS name", .{});
+                return error.ZigInstallationCorrupt;
+            };
+            const abi_name = component_it.next() orelse {
+                log.err("abilists: expected ABI name", .{});
+                return error.ZigInstallationCorrupt;
+            };
+            const arch_tag = std.meta.stringToEnum(std.Target.Cpu.Arch, arch_name) orelse {
+                log.err("abilists: unrecognized arch: '{s}'", .{arch_name});
+                return error.ZigInstallationCorrupt;
+            };
+            if (!mem.eql(u8, os_name, "freebsd")) {
+                log.err("abilists: expected OS 'freebsd', found '{s}'", .{os_name});
+                return error.ZigInstallationCorrupt;
+            }
+            const abi_tag = std.meta.stringToEnum(std.Target.Abi, abi_name) orelse {
+                log.err("abilists: unrecognized ABI: '{s}'", .{abi_name});
+                return error.ZigInstallationCorrupt;
+            };
+
+            targets[i] = .{
+                .arch = arch_tag,
+                .os = .freebsd,
+                .abi = abi_tag,
+            };
+        }
+        break :b targets;
+    };
+
+    const abi = try arena.create(ABI);
+    abi.* = .{
+        .all_versions = versions,
+        .all_targets = targets,
+        .inclusions = contents[index..],
+        .arena_state = arena_allocator.state,
+    };
+    return abi;
+}
+
+pub const BuiltSharedObjects = struct {
+    lock: Cache.Lock,
+    dir_path: Path,
+
+    pub fn deinit(self: *BuiltSharedObjects, gpa: Allocator) void {
+        self.lock.release();
+        gpa.free(self.dir_path.sub_path);
+        self.* = undefined;
+    }
+};
+
+const all_map_basename = "all.map";
+
+fn wordDirective(target: std.Target) []const u8 {
+    // Based on its description in the GNU `as` manual, you might assume that `.word` is sized
+    // according to the target word size. But no; that would just make too much sense.
+    return if (target.ptrBitWidth() == 64) ".quad" else ".long";
+}
+
+/// TODO replace anyerror with explicit error set, recording user-friendly errors with
+/// setMiscFailure and returning error.SubCompilationFailed. see libcxx.zig for example.
+pub fn buildSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) anyerror!void {
+    // See also glibc.zig which this code is based on.
+
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    if (!build_options.have_llvm) {
+        return error.ZigCompilerNotBuiltWithLLVMExtensions;
+    }
+
+    const gpa = comp.gpa;
+
+    var arena_allocator = std.heap.ArenaAllocator.init(gpa);
+    defer arena_allocator.deinit();
+    const arena = arena_allocator.allocator();
+
+    const target = comp.getTarget();
+    // FreeBSD 7 == FBSD_1.0, ..., FreeBSD 14 == FBSD_1.7
+    const target_version: Version = .{ .major = 1, .minor = target.os.version_range.semver.min.major - 7, .patch = 0 };
+
+    // Use the global cache directory.
+    var cache: Cache = .{
+        .gpa = gpa,
+        .manifest_dir = try comp.global_cache_directory.handle.makeOpenPath("h", .{}),
+    };
+    cache.addPrefix(.{ .path = null, .handle = fs.cwd() });
+    cache.addPrefix(comp.zig_lib_directory);
+    cache.addPrefix(comp.global_cache_directory);
+    defer cache.manifest_dir.close();
+
+    var man = cache.obtain();
+    defer man.deinit();
+    man.hash.addBytes(build_options.version);
+    man.hash.add(target.cpu.arch);
+    man.hash.add(target.abi);
+    man.hash.add(target_version);
+
+    const full_abilists_path = try comp.zig_lib_directory.join(arena, &.{abilists_path});
+    const abilists_index = try man.addFile(full_abilists_path, abilists_max_size);
+
+    if (try man.hit()) {
+        const digest = man.final();
+
+        return queueSharedObjects(comp, .{
+            .lock = man.toOwnedLock(),
+            .dir_path = .{
+                .root_dir = comp.global_cache_directory,
+                .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest),
+            },
+        });
+    }
+
+    const digest = man.final();
+    const o_sub_path = try path.join(arena, &[_][]const u8{ "o", &digest });
+
+    var o_directory: Compilation.Directory = .{
+        .handle = try comp.global_cache_directory.handle.makeOpenPath(o_sub_path, .{}),
+        .path = try comp.global_cache_directory.join(arena, &.{o_sub_path}),
+    };
+    defer o_directory.handle.close();
+
+    const abilists_contents = man.files.keys()[abilists_index].contents.?;
+    const metadata = try loadMetaData(gpa, abilists_contents);
+    defer metadata.destroy(gpa);
+
+    const target_targ_index = for (metadata.all_targets, 0..) |targ, i| {
+        if (targ.arch == target.cpu.arch and
+            targ.os == target.os.tag and
+            targ.abi == target.abi)
+        {
+            break i;
+        }
+    } else {
+        unreachable; // std.zig.target.available_libcs prevents us from getting here
+    };
+
+    const target_ver_index = for (metadata.all_versions, 0..) |ver, i| {
+        switch (ver.order(target_version)) {
+            .eq => break i,
+            .lt => continue,
+            .gt => {
+                // TODO Expose via compile error mechanism instead of log.
+                log.warn("invalid target FreeBSD libc version: {}", .{target_version});
+                return error.InvalidTargetLibCVersion;
+            },
+        }
+    } else blk: {
+        const latest_index = metadata.all_versions.len - 1;
+        log.warn("zig cannot build new FreeBSD libc version {}; providing instead {}", .{
+            target_version, metadata.all_versions[latest_index],
+        });
+        break :blk latest_index;
+    };
+
+    {
+        var map_contents = std.ArrayList(u8).init(arena);
+        for (metadata.all_versions[0 .. target_ver_index + 1]) |ver| {
+            try map_contents.writer().print("FBSD_{d}.{d} {{ }};\n", .{ ver.major, ver.minor });
+        }
+        try o_directory.handle.writeFile(.{ .sub_path = all_map_basename, .data = map_contents.items });
+        map_contents.deinit();
+    }
+
+    var stubs_asm = std.ArrayList(u8).init(gpa);
+    defer stubs_asm.deinit();
+
+    for (libs, 0..) |lib, lib_i| {
+        stubs_asm.shrinkRetainingCapacity(0);
+
+        const stubs_writer = stubs_asm.writer();
+
+        try stubs_writer.writeAll(".text\n");
+
+        var sym_i: usize = 0;
+        var sym_name_buf = std.ArrayList(u8).init(arena);
+        var opt_symbol_name: ?[]const u8 = null;
+        var versions = try std.DynamicBitSetUnmanaged.initEmpty(arena, metadata.all_versions.len);
+        var weak_linkages = try std.DynamicBitSetUnmanaged.initEmpty(arena, metadata.all_versions.len);
+        var sym_unversioned = false;
+
+        var inc_fbs = std.io.fixedBufferStream(metadata.inclusions);
+        var inc_reader = inc_fbs.reader();
+
+        const fn_inclusions_len = try inc_reader.readInt(u16, .little);
+
+        while (sym_i < fn_inclusions_len) : (sym_i += 1) {
+            const sym_name = opt_symbol_name orelse n: {
+                sym_name_buf.clearRetainingCapacity();
+                try inc_reader.streamUntilDelimiter(sym_name_buf.writer(), 0, null);
+
+                opt_symbol_name = sym_name_buf.items;
+                versions.unsetAll();
+                weak_linkages.unsetAll();
+                sym_unversioned = false;
+
+                break :n sym_name_buf.items;
+            };
+
+            {
+                const targets = try std.leb.readUleb128(u64, inc_reader);
+                var lib_index = try inc_reader.readByte();
+
+                const is_unversioned = (lib_index & (1 << 5)) != 0;
+                const is_weak = (lib_index & (1 << 6)) != 0;
+                const is_terminal = (lib_index & (1 << 7)) != 0;
+
+                lib_index = @as(u5, @truncate(lib_index));
+
+                // Test whether the inclusion applies to our current library and target.
+                const ok_lib_and_target =
+                    (lib_index == lib_i) and
+                    ((targets & (@as(u64, 1) << @as(u6, @intCast(target_targ_index)))) != 0);
+
+                while (true) {
+                    const byte = try inc_reader.readByte();
+                    const last = (byte & 0b1000_0000) != 0;
+                    const ver_i = @as(u7, @truncate(byte));
+                    if (ok_lib_and_target and ver_i <= target_ver_index) {
+                        versions.set(ver_i);
+                        if (is_unversioned) sym_unversioned = true;
+                        if (is_weak) weak_linkages.set(ver_i);
+                    }
+                    if (last) break;
+                }
+
+                if (is_terminal) {
+                    opt_symbol_name = null;
+                } else continue;
+            }
+
+            // Pick the default symbol version:
+            // - If there are no versions, don't emit it
+            // - Take the greatest one <= than the target one
+            // - If none of them is <= than the
+            //   specified one don't pick any default version
+            var chosen_def_ver_index: usize = 255;
+            {
+                var versions_iter = versions.iterator(.{});
+                while (versions_iter.next()) |ver_i| {
+                    if (chosen_def_ver_index == 255 or ver_i > chosen_def_ver_index) {
+                        chosen_def_ver_index = ver_i;
+                    }
+                }
+            }
+
+            {
+                var versions_iter = versions.iterator(.{});
+                while (versions_iter.next()) |ver_index| {
+                    if (sym_unversioned) {
+                        // Example:
+                        // .balign 4
+                        // .globl _Exit
+                        // .type _Exit, %function
+                        // _Exit: .long 0
+                        try stubs_writer.print(
+                            \\.balign {d}
+                            \\.{s} {s}
+                            \\.type {s}, %function
+                            \\{s}: {s} 0
+                            \\
+                        , .{
+                            target.ptrBitWidth() / 8,
+                            if (weak_linkages.isSet(ver_index)) "weak" else "globl",
+                            sym_name,
+                            sym_name,
+                            sym_name,
+                            wordDirective(target),
+                        });
+                    }
+
+                    // Example:
+                    // .balign 4
+                    // .globl _Exit_1_0
+                    // .type _Exit_1_0, %function
+                    // .symver _Exit_1_0, _Exit@@FBSD_1.0, remove
+                    // _Exit_1_0: .long 0
+                    const ver = metadata.all_versions[ver_index];
+
+                    // Default symbol version definition vs normal symbol version definition
+                    const want_default = chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index;
+                    const at_sign_str: []const u8 = if (want_default) "@@" else "@";
+                    const sym_plus_ver = try std.fmt.allocPrint(
+                        arena,
+                        "{s}_FBSD_{d}_{d}",
+                        .{ sym_name, ver.major, ver.minor },
+                    );
+
+                    try stubs_writer.print(
+                        \\.balign {d}
+                        \\.{s} {s}
+                        \\.type {s}, %function
+                        \\.symver {s}, {s}{s}FBSD_{d}.{d}, remove
+                        \\{s}: {s} 0
+                        \\
+                    , .{
+                        target.ptrBitWidth() / 8,
+                        if (weak_linkages.isSet(ver_index)) "weak" else "globl",
+                        sym_plus_ver,
+                        sym_plus_ver,
+                        sym_plus_ver,
+                        sym_name,
+                        at_sign_str,
+                        ver.major,
+                        ver.minor,
+                        sym_plus_ver,
+                        wordDirective(target),
+                    });
+                }
+            }
+        }
+
+        try stubs_writer.writeAll(".data\n");
+
+        const obj_inclusions_len = try inc_reader.readInt(u16, .little);
+
+        var sizes = try arena.alloc(u16, metadata.all_versions.len);
+
+        sym_i = 0;
+        opt_symbol_name = null;
+        while (sym_i < obj_inclusions_len) : (sym_i += 1) {
+            const sym_name = opt_symbol_name orelse n: {
+                sym_name_buf.clearRetainingCapacity();
+                try inc_reader.streamUntilDelimiter(sym_name_buf.writer(), 0, null);
+
+                opt_symbol_name = sym_name_buf.items;
+                versions.unsetAll();
+                weak_linkages.unsetAll();
+                sym_unversioned = false;
+
+                break :n sym_name_buf.items;
+            };
+
+            {
+                const targets = try std.leb.readUleb128(u64, inc_reader);
+                const size = try std.leb.readUleb128(u16, inc_reader);
+                var lib_index = try inc_reader.readByte();
+
+                const is_unversioned = (lib_index & (1 << 5)) != 0;
+                const is_weak = (lib_index & (1 << 6)) != 0;
+                const is_terminal = (lib_index & (1 << 7)) != 0;
+
+                lib_index = @as(u5, @truncate(lib_index));
+
+                // Test whether the inclusion applies to our current library and target.
+                const ok_lib_and_target =
+                    (lib_index == lib_i) and
+                    ((targets & (@as(u64, 1) << @as(u6, @intCast(target_targ_index)))) != 0);
+
+                while (true) {
+                    const byte = try inc_reader.readByte();
+                    const last = (byte & 0b1000_0000) != 0;
+                    const ver_i = @as(u7, @truncate(byte));
+                    if (ok_lib_and_target and ver_i <= target_ver_index) {
+                        versions.set(ver_i);
+                        sizes[ver_i] = size;
+                        if (is_unversioned) sym_unversioned = true;
+                        if (is_weak) weak_linkages.set(ver_i);
+                    }
+                    if (last) break;
+                }
+
+                if (is_terminal) {
+                    opt_symbol_name = null;
+                } else continue;
+            }
+
+            // Pick the default symbol version:
+            // - If there are no versions, don't emit it
+            // - Take the greatest one <= than the target one
+            // - If none of them is <= than the
+            //   specified one don't pick any default version
+            var chosen_def_ver_index: usize = 255;
+            {
+                var versions_iter = versions.iterator(.{});
+                while (versions_iter.next()) |ver_i| {
+                    if (chosen_def_ver_index == 255 or ver_i > chosen_def_ver_index) {
+                        chosen_def_ver_index = ver_i;
+                    }
+                }
+            }
+
+            {
+                var versions_iter = versions.iterator(.{});
+                while (versions_iter.next()) |ver_index| {
+                    if (sym_unversioned) {
+                        // Example:
+                        // .balign 4
+                        // .globl malloc_conf
+                        // .type malloc_conf, %object
+                        // .size malloc_conf, 4
+                        // malloc_conf: .fill 4, 1, 0
+                        try stubs_writer.print(
+                            \\.balign {d}
+                            \\.{s} {s}
+                            \\.type {s}, %object
+                            \\.size {s}, {d}
+                            \\{s}: {s} 0
+                            \\
+                        , .{
+                            target.ptrBitWidth() / 8,
+                            if (weak_linkages.isSet(ver_index)) "weak" else "globl",
+                            sym_name,
+                            sym_name,
+                            sym_name,
+                            sizes[ver_index],
+                            sym_name,
+                            wordDirective(target),
+                        });
+                    }
+
+                    // Example:
+                    // .balign 4
+                    // .globl malloc_conf_1_3
+                    // .type malloc_conf_1_3, %object
+                    // .size malloc_conf_1_3, 4
+                    // .symver malloc_conf_1_3, malloc_conf@@FBSD_1.3
+                    // malloc_conf_1_3: .fill 4, 1, 0
+                    const ver = metadata.all_versions[ver_index];
+
+                    // Default symbol version definition vs normal symbol version definition
+                    const want_default = chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index;
+                    const at_sign_str: []const u8 = if (want_default) "@@" else "@";
+                    const sym_plus_ver = try std.fmt.allocPrint(
+                        arena,
+                        "{s}_FBSD_{d}_{d}",
+                        .{ sym_name, ver.major, ver.minor },
+                    );
+
+                    try stubs_asm.writer().print(
+                        \\.balign {d}
+                        \\.{s} {s}
+                        \\.type {s}, %object
+                        \\.size {s}, {d}
+                        \\.symver {s}, {s}{s}FBSD_{d}.{d}
+                        \\{s}: .fill {d}, 1, 0
+                        \\
+                    , .{
+                        target.ptrBitWidth() / 8,
+                        if (weak_linkages.isSet(ver_index)) "weak" else "globl",
+                        sym_plus_ver,
+                        sym_plus_ver,
+                        sym_plus_ver,
+                        sizes[ver_index],
+                        sym_plus_ver,
+                        sym_name,
+                        at_sign_str,
+                        ver.major,
+                        ver.minor,
+                        sym_plus_ver,
+                        sizes[ver_index],
+                    });
+                }
+            }
+        }
+
+        try stubs_writer.writeAll(".tdata\n");
+
+        const tls_inclusions_len = try inc_reader.readInt(u16, .little);
+
+        sym_i = 0;
+        opt_symbol_name = null;
+        while (sym_i < tls_inclusions_len) : (sym_i += 1) {
+            const sym_name = opt_symbol_name orelse n: {
+                sym_name_buf.clearRetainingCapacity();
+                try inc_reader.streamUntilDelimiter(sym_name_buf.writer(), 0, null);
+
+                opt_symbol_name = sym_name_buf.items;
+                versions.unsetAll();
+                weak_linkages.unsetAll();
+                sym_unversioned = false;
+
+                break :n sym_name_buf.items;
+            };
+
+            {
+                const targets = try std.leb.readUleb128(u64, inc_reader);
+                const size = try std.leb.readUleb128(u16, inc_reader);
+                var lib_index = try inc_reader.readByte();
+
+                const is_unversioned = (lib_index & (1 << 5)) != 0;
+                const is_weak = (lib_index & (1 << 6)) != 0;
+                const is_terminal = (lib_index & (1 << 7)) != 0;
+
+                lib_index = @as(u5, @truncate(lib_index));
+
+                // Test whether the inclusion applies to our current library and target.
+                const ok_lib_and_target =
+                    (lib_index == lib_i) and
+                    ((targets & (@as(u64, 1) << @as(u6, @intCast(target_targ_index)))) != 0);
+
+                while (true) {
+                    const byte = try inc_reader.readByte();
+                    const last = (byte & 0b1000_0000) != 0;
+                    const ver_i = @as(u7, @truncate(byte));
+                    if (ok_lib_and_target and ver_i <= target_ver_index) {
+                        versions.set(ver_i);
+                        sizes[ver_i] = size;
+                        if (is_unversioned) sym_unversioned = true;
+                        if (is_weak) weak_linkages.set(ver_i);
+                    }
+                    if (last) break;
+                }
+
+                if (is_terminal) {
+                    opt_symbol_name = null;
+                } else continue;
+            }
+
+            // Pick the default symbol version:
+            // - If there are no versions, don't emit it
+            // - Take the greatest one <= than the target one
+            // - If none of them is <= than the
+            //   specified one don't pick any default version
+            var chosen_def_ver_index: usize = 255;
+            {
+                var versions_iter = versions.iterator(.{});
+                while (versions_iter.next()) |ver_i| {
+                    if (chosen_def_ver_index == 255 or ver_i > chosen_def_ver_index) {
+                        chosen_def_ver_index = ver_i;
+                    }
+                }
+            }
+
+            {
+                var versions_iter = versions.iterator(.{});
+                while (versions_iter.next()) |ver_index| {
+                    if (sym_unversioned) {
+                        // Example:
+                        // .balign 4
+                        // .globl _ThreadRuneLocale
+                        // .type _ThreadRuneLocale, %object
+                        // .size _ThreadRuneLocale, 4
+                        // _ThreadRuneLocale: .fill 4, 1, 0
+                        try stubs_writer.print(
+                            \\.balign {d}
+                            \\.{s} {s}
+                            \\.type {s}, %tls_object
+                            \\.size {s}, {d}
+                            \\{s}: {s} 0
+                            \\
+                        , .{
+                            target.ptrBitWidth() / 8,
+                            if (weak_linkages.isSet(ver_index)) "weak" else "globl",
+                            sym_name,
+                            sym_name,
+                            sym_name,
+                            sizes[ver_index],
+                            sym_name,
+                            wordDirective(target),
+                        });
+                    }
+
+                    // Example:
+                    // .balign 4
+                    // .globl _ThreadRuneLocale_1_3
+                    // .type _ThreadRuneLocale_1_3, %tls_object
+                    // .size _ThreadRuneLocale_1_3, 4
+                    // .symver _ThreadRuneLocale_1_3, _ThreadRuneLocale@@FBSD_1.3
+                    // _ThreadRuneLocale_1_3: .fill 4, 1, 0
+                    const ver = metadata.all_versions[ver_index];
+
+                    // Default symbol version definition vs normal symbol version definition
+                    const want_default = chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index;
+                    const at_sign_str: []const u8 = if (want_default) "@@" else "@";
+                    const sym_plus_ver = try std.fmt.allocPrint(
+                        arena,
+                        "{s}_FBSD_{d}_{d}",
+                        .{ sym_name, ver.major, ver.minor },
+                    );
+
+                    try stubs_writer.print(
+                        \\.balign {d}
+                        \\.{s} {s}
+                        \\.type {s}, %tls_object
+                        \\.size {s}, {d}
+                        \\.symver {s}, {s}{s}FBSD_{d}.{d}
+                        \\{s}: .fill {d}, 1, 0
+                        \\
+                    , .{
+                        target.ptrBitWidth() / 8,
+                        if (weak_linkages.isSet(ver_index)) "weak" else "globl",
+                        sym_plus_ver,
+                        sym_plus_ver,
+                        sym_plus_ver,
+                        sizes[ver_index],
+                        sym_plus_ver,
+                        sym_name,
+                        at_sign_str,
+                        ver.major,
+                        ver.minor,
+                        sym_plus_ver,
+                        sizes[ver_index],
+                    });
+                }
+            }
+        }
+
+        var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "stdthreads", etc.
+        const asm_file_basename = std.fmt.bufPrint(&lib_name_buf, "{s}.s", .{lib.name}) catch unreachable;
+        try o_directory.handle.writeFile(.{ .sub_path = asm_file_basename, .data = stubs_asm.items });
+        try buildSharedLib(comp, arena, comp.global_cache_directory, o_directory, asm_file_basename, lib, prog_node);
+    }
+
+    man.writeManifest() catch |err| {
+        log.warn("failed to write cache manifest for FreeBSD libc stubs: {s}", .{@errorName(err)});
+    };
+
+    return queueSharedObjects(comp, .{
+        .lock = man.toOwnedLock(),
+        .dir_path = .{
+            .root_dir = comp.global_cache_directory,
+            .sub_path = try gpa.dupe(u8, "o" ++ fs.path.sep_str ++ digest),
+        },
+    });
+}
+
+pub fn sharedObjectsCount() u8 {
+    return libs.len;
+}
+
+fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void {
+    assert(comp.freebsd_so_files == null);
+    comp.freebsd_so_files = so_files;
+
+    var task_buffer: [libs.len]link.Task = undefined;
+    var task_buffer_i: usize = 0;
+
+    {
+        comp.mutex.lock(); // protect comp.arena
+        defer comp.mutex.unlock();
+
+        for (libs) |lib| {
+            const so_path: Path = .{
+                .root_dir = so_files.dir_path.root_dir,
+                .sub_path = std.fmt.allocPrint(comp.arena, "{s}{c}lib{s}.so.{d}", .{
+                    so_files.dir_path.sub_path, fs.path.sep, lib.name, lib.sover,
+                }) catch return comp.setAllocFailure(),
+            };
+            task_buffer[task_buffer_i] = .{ .load_dso = so_path };
+            task_buffer_i += 1;
+        }
+    }
+
+    comp.queueLinkTasks(task_buffer[0..task_buffer_i]);
+}
+
+fn buildSharedLib(
+    comp: *Compilation,
+    arena: Allocator,
+    zig_cache_directory: Compilation.Directory,
+    bin_directory: Compilation.Directory,
+    asm_file_basename: []const u8,
+    lib: Lib,
+    prog_node: std.Progress.Node,
+) !void {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
+    const emit_bin = Compilation.EmitLoc{
+        .directory = bin_directory,
+        .basename = basename,
+    };
+    const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
+    const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
+    const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename;
+    const map_file_path = try path.join(arena, &.{ bin_directory.path.?, all_map_basename });
+
+    const optimize_mode = comp.compilerRtOptMode();
+    const strip = comp.compilerRtStrip();
+    const config = try Compilation.Config.resolve(.{
+        .output_mode = .Lib,
+        .link_mode = .dynamic,
+        .resolved_target = comp.root_mod.resolved_target,
+        .is_test = false,
+        .have_zcu = false,
+        .emit_bin = true,
+        .root_optimize_mode = optimize_mode,
+        .root_strip = strip,
+        .link_libc = false,
+    });
+
+    const root_mod = try Module.create(arena, .{
+        .global_cache_directory = comp.global_cache_directory,
+        .paths = .{
+            .root = .{ .root_dir = comp.zig_lib_directory },
+            .root_src_path = "",
+        },
+        .fully_qualified_name = "root",
+        .inherited = .{
+            .resolved_target = comp.root_mod.resolved_target,
+            .strip = strip,
+            .stack_check = false,
+            .stack_protector = 0,
+            .sanitize_c = .off,
+            .sanitize_thread = false,
+            .red_zone = comp.root_mod.red_zone,
+            .omit_frame_pointer = comp.root_mod.omit_frame_pointer,
+            .valgrind = false,
+            .optimize_mode = optimize_mode,
+            .structured_cfg = comp.root_mod.structured_cfg,
+        },
+        .global = config,
+        .cc_argv = &.{},
+        .parent = null,
+        .builtin_mod = null,
+        .builtin_modules = null, // there is only one module in this compilation
+    });
+
+    const c_source_files = [1]Compilation.CSourceFile{
+        .{
+            .src_path = try path.join(arena, &.{ bin_directory.path.?, asm_file_basename }),
+            .owner = root_mod,
+        },
+    };
+
+    const sub_compilation = try Compilation.create(comp.gpa, arena, .{
+        .local_cache_directory = zig_cache_directory,
+        .global_cache_directory = comp.global_cache_directory,
+        .zig_lib_directory = comp.zig_lib_directory,
+        .thread_pool = comp.thread_pool,
+        .self_exe_path = comp.self_exe_path,
+        .cache_mode = .incremental,
+        .config = config,
+        .root_mod = root_mod,
+        .root_name = lib.name,
+        .libc_installation = comp.libc_installation,
+        .emit_bin = emit_bin,
+        .emit_h = null,
+        .verbose_cc = comp.verbose_cc,
+        .verbose_link = comp.verbose_link,
+        .verbose_air = comp.verbose_air,
+        .verbose_llvm_ir = comp.verbose_llvm_ir,
+        .verbose_llvm_bc = comp.verbose_llvm_bc,
+        .verbose_cimport = comp.verbose_cimport,
+        .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features,
+        .clang_passthrough_mode = comp.clang_passthrough_mode,
+        .version = version,
+        .version_script = map_file_path,
+        .soname = soname,
+        .c_source_files = &c_source_files,
+        .skip_linker_dependencies = true,
+    });
+    defer sub_compilation.destroy();
+
+    try comp.updateSubCompilation(sub_compilation, .@"freebsd libc shared object", prog_node);
+}
src/link/Elf.zig
@@ -2058,11 +2058,18 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
                         try argv.append(lib_path);
                     }
                     try argv.append(try comp.crtFileAsString(arena, "libc_nonshared.a"));
-                } else if (target.abi.isMusl()) {
+                } else if (target.isMuslLibC()) {
                     try argv.append(try comp.crtFileAsString(arena, switch (link_mode) {
                         .static => "libc.a",
                         .dynamic => "libc.so",
                     }));
+                } else if (target.isFreeBSDLibC()) {
+                    for (freebsd.libs) |lib| {
+                        const lib_path = try std.fmt.allocPrint(arena, "{}{c}lib{s}.so.{d}", .{
+                            comp.freebsd_so_files.?.dir_path, fs.path.sep, lib.name, lib.sover,
+                        });
+                        try argv.append(lib_path);
+                    }
                 } else {
                     diags.flags.missing_libc = true;
                 }
@@ -5280,8 +5287,9 @@ const dev = @import("../dev.zig");
 const eh_frame = @import("Elf/eh_frame.zig");
 const gc = @import("Elf/gc.zig");
 const glibc = @import("../libs/glibc.zig");
-const link = @import("../link.zig");
 const musl = @import("../libs/musl.zig");
+const freebsd = @import("../libs/freebsd.zig");
+const link = @import("../link.zig");
 const relocatable = @import("Elf/relocatable.zig");
 const relocation = @import("Elf/relocation.zig");
 const target_util = @import("../target.zig");
src/Compilation.zig
@@ -23,6 +23,7 @@ const build_options = @import("build_options");
 const LibCInstallation = std.zig.LibCInstallation;
 const glibc = @import("libs/glibc.zig");
 const musl = @import("libs/musl.zig");
+const freebsd = @import("libs/freebsd.zig");
 const mingw = @import("libs/mingw.zig");
 const libunwind = @import("libs/libunwind.zig");
 const libcxx = @import("libs/libcxx.zig");
@@ -248,6 +249,7 @@ compiler_rt_obj: ?CrtFile = null,
 fuzzer_lib: ?CrtFile = null,
 
 glibc_so_files: ?glibc.BuiltSharedObjects = null,
+freebsd_so_files: ?freebsd.BuiltSharedObjects = null,
 wasi_emulated_libs: []const wasi_libc.CrtFile,
 
 /// For example `Scrt1.o` and `libc_nonshared.a`. These are populated after building libc from source,
@@ -294,12 +296,14 @@ const QueuedJobs = struct {
     update_builtin_zig: bool,
     musl_crt_file: [@typeInfo(musl.CrtFile).@"enum".fields.len]bool = @splat(false),
     glibc_crt_file: [@typeInfo(glibc.CrtFile).@"enum".fields.len]bool = @splat(false),
+    freebsd_crt_file: [@typeInfo(freebsd.CrtFile).@"enum".fields.len]bool = @splat(false),
     /// one of WASI libc static objects
     wasi_libc_crt_file: [@typeInfo(wasi_libc.CrtFile).@"enum".fields.len]bool = @splat(false),
     /// one of the mingw-w64 static objects
     mingw_crt_file: [@typeInfo(mingw.CrtFile).@"enum".fields.len]bool = @splat(false),
     /// all of the glibc shared objects
     glibc_shared_objects: bool = false,
+    freebsd_shared_objects: bool = false,
     /// libunwind.a, usually needed when linking libc
     libunwind: bool = false,
     libcxx: bool = false,
@@ -789,6 +793,8 @@ pub const MiscTask = enum {
     glibc_crt_file,
     glibc_shared_objects,
     musl_crt_file,
+    freebsd_crt_file,
+    freebsd_shared_objects,
     mingw_crt_file,
     windows_import_lib,
     libunwind,
@@ -823,6 +829,9 @@ pub const MiscTask = enum {
     @"glibc libc_nonshared.a",
     @"glibc shared object",
 
+    @"freebsd libc Scrt1.o",
+    @"freebsd libc shared object",
+
     @"mingw-w64 crt2.o",
     @"mingw-w64 dllcrt2.o",
     @"mingw-w64 libmingw32.lib",
@@ -1874,6 +1883,16 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
 
                     comp.queued_jobs.glibc_crt_file[@intFromEnum(glibc.CrtFile.libc_nonshared_a)] = true;
                     comp.remaining_prelink_tasks += 1;
+                } else if (target.isFreeBSDLibC()) {
+                    if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
+
+                    if (freebsd.needsCrt0(comp.config.output_mode)) |f| {
+                        comp.queued_jobs.freebsd_crt_file[@intFromEnum(f)] = true;
+                        comp.remaining_prelink_tasks += 1;
+                    }
+
+                    comp.queued_jobs.freebsd_shared_objects = true;
+                    comp.remaining_prelink_tasks += freebsd.sharedObjectsCount();
                 } else if (target.isWasiLibC()) {
                     if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable;
 
@@ -2028,6 +2047,10 @@ pub fn destroy(comp: *Compilation) void {
         glibc_file.deinit(gpa);
     }
 
+    if (comp.freebsd_so_files) |*freebsd_file| {
+        freebsd_file.deinit(gpa);
+    }
+
     for (comp.c_object_table.keys()) |key| {
         key.destroy(gpa);
     }
@@ -3839,6 +3862,10 @@ fn performAllTheWorkInner(
         comp.link_task_wait_group.spawnManager(buildGlibcSharedObjects, .{ comp, main_progress_node });
     }
 
+    if (comp.queued_jobs.freebsd_shared_objects) {
+        comp.link_task_wait_group.spawnManager(buildFreeBSDSharedObjects, .{ comp, main_progress_node });
+    }
+
     if (comp.queued_jobs.libunwind) {
         comp.link_task_wait_group.spawnManager(buildLibUnwind, .{ comp, main_progress_node });
     }
@@ -3873,6 +3900,13 @@ fn performAllTheWorkInner(
         }
     }
 
+    for (0..@typeInfo(freebsd.CrtFile).@"enum".fields.len) |i| {
+        if (comp.queued_jobs.freebsd_crt_file[i]) {
+            const tag: freebsd.CrtFile = @enumFromInt(i);
+            comp.link_task_wait_group.spawnManager(buildFreeBSDCrtFile, .{ comp, tag, main_progress_node });
+        }
+    }
+
     for (0..@typeInfo(wasi_libc.CrtFile).@"enum".fields.len) |i| {
         if (comp.queued_jobs.wasi_libc_crt_file[i]) {
             const tag: wasi_libc.CrtFile = @enumFromInt(i);
@@ -4876,6 +4910,29 @@ fn buildGlibcSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) voi
     }
 }
 
+fn buildFreeBSDCrtFile(comp: *Compilation, crt_file: freebsd.CrtFile, prog_node: std.Progress.Node) void {
+    if (freebsd.buildCrtFile(comp, crt_file, prog_node)) |_| {
+        comp.queued_jobs.freebsd_crt_file[@intFromEnum(crt_file)] = false;
+    } else |err| switch (err) {
+        error.SubCompilationFailed => return, // error reported already
+        else => comp.lockAndSetMiscFailure(.freebsd_crt_file, "unable to build FreeBSD {s}: {s}", .{
+            @tagName(crt_file), @errorName(err),
+        }),
+    }
+}
+
+fn buildFreeBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) void {
+    if (freebsd.buildSharedObjects(comp, prog_node)) |_| {
+        // The job should no longer be queued up since it succeeded.
+        comp.queued_jobs.freebsd_shared_objects = false;
+    } else |err| switch (err) {
+        error.SubCompilationFailed => return, // error reported already
+        else => comp.lockAndSetMiscFailure(.freebsd_shared_objects, "unable to build FreeBSD libc shared objects: {s}", .{
+            @errorName(err),
+        }),
+    }
+}
+
 fn buildMingwCrtFile(comp: *Compilation, crt_file: mingw.CrtFile, prog_node: std.Progress.Node) void {
     if (mingw.buildCrtFile(comp, crt_file, prog_node)) |_| {
         comp.queued_jobs.mingw_crt_file[@intFromEnum(crt_file)] = false;
CMakeLists.txt
@@ -586,6 +586,7 @@ set(ZIG_STAGE2_SOURCES
     src/codegen/spirv/spec.zig
     src/crash_report.zig
     src/dev.zig
+    src/libs/freebsd.zig
     src/libs/glibc.zig
     src/introspect.zig
     src/libs/libcxx.zig