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.dirs.zig_lib.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.dirs.zig_lib.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.dirs.zig_lib.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 => "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, }, .{ .path = "arm" ++ path.sep_str ++ "crt1_s.S", .flags = acflags.items, .condition = target.cpu.arch == .arm, }, .{ .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: *const 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.dirs.global_cache.handle.makeOpenPath("h", .{}), }; cache.addPrefix(.{ .path = null, .handle = fs.cwd() }); cache.addPrefix(comp.dirs.zig_lib); cache.addPrefix(comp.dirs.global_cache); 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.dirs.zig_lib.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.dirs.global_cache, .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: Cache.Directory = .{ .handle = try comp.dirs.global_cache.handle.makeOpenPath(o_sub_path, .{}), .path = try comp.dirs.global_cache.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: {f}", .{target_version}); return error.InvalidTargetLibCVersion; }, } } else blk: { const latest_index = metadata.all_versions.len - 1; log.warn("zig cannot build new FreeBSD libc version {f}; providing instead {f}", .{ 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 inc_fbs = std.io.fixedBufferStream(metadata.inclusions); var inc_reader = inc_fbs.reader(); const fn_inclusions_len = try inc_reader.readInt(u16, .little); // 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 chosen_unversioned_ver_index: usize = 255; 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(); chosen_def_ver_index = 255; chosen_unversioned_ver_index = 255; 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) { if (is_unversioned) { if (chosen_unversioned_ver_index == 255 or ver_i > chosen_unversioned_ver_index) { chosen_unversioned_ver_index = ver_i; } } else { if (chosen_def_ver_index == 255 or ver_i > chosen_def_ver_index) { chosen_def_ver_index = ver_i; } versions.set(ver_i); } weak_linkages.setValue(ver_i, is_weak); } if (last) break; } if (is_terminal) { opt_symbol_name = null; } else continue; } if (chosen_unversioned_ver_index != 255) { // 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(chosen_unversioned_ver_index)) "weak" else "globl", sym_name, sym_name, sym_name, wordDirective(target), }); } { var versions_iter = versions.iterator(.{}); while (versions_iter.next()) |ver_index| { // 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]; 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, // Default symbol version definition vs normal symbol version definition if (chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index) "@@" else "@", ver.major, ver.minor, sym_plus_ver, wordDirective(target), }); } } } try stubs_writer.writeAll(".data\n"); // FreeBSD's `libc.so.7` contains strong references to `__progname` and `environ` which are // defined in the statically-linked startup code. Those references cause the linker to put // the symbols in the dynamic symbol table. We need to create dummy references to them here // to get the same effect. if (std.mem.eql(u8, lib.name, "c")) { try stubs_writer.print( \\.balign {d} \\.globl __progname \\.globl environ \\{s} __progname \\{s} environ \\ , .{ target.ptrBitWidth() / 8, wordDirective(target), wordDirective(target), }); } 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(); chosen_def_ver_index = 255; chosen_unversioned_ver_index = 255; 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) { if (is_unversioned) { if (chosen_unversioned_ver_index == 255 or ver_i > chosen_unversioned_ver_index) { chosen_unversioned_ver_index = ver_i; } } else { if (chosen_def_ver_index == 255 or ver_i > chosen_def_ver_index) { chosen_def_ver_index = ver_i; } versions.set(ver_i); } sizes[ver_i] = size; weak_linkages.setValue(ver_i, is_weak); } if (last) break; } if (is_terminal) { opt_symbol_name = null; } else continue; } if (chosen_unversioned_ver_index != 255) { // 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(chosen_unversioned_ver_index)) "weak" else "globl", sym_name, sym_name, sym_name, sizes[chosen_unversioned_ver_index], sym_name, wordDirective(target), }); } { var versions_iter = versions.iterator(.{}); while (versions_iter.next()) |ver_index| { // 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]; 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, // Default symbol version definition vs normal symbol version definition if (chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index) "@@" else "@", 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(); chosen_def_ver_index = 255; chosen_unversioned_ver_index = 255; 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) { if (is_unversioned) { if (chosen_unversioned_ver_index == 255 or ver_i > chosen_unversioned_ver_index) { chosen_unversioned_ver_index = ver_i; } } else { if (chosen_def_ver_index == 255 or ver_i > chosen_def_ver_index) { chosen_def_ver_index = ver_i; } versions.set(ver_i); } sizes[ver_i] = size; weak_linkages.setValue(ver_i, is_weak); } if (last) break; } if (is_terminal) { opt_symbol_name = null; } else continue; } if (chosen_unversioned_ver_index != 255) { // 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(chosen_unversioned_ver_index)) "weak" else "globl", sym_name, sym_name, sym_name, sizes[chosen_unversioned_ver_index], sym_name, wordDirective(target), }); } { var versions_iter = versions.iterator(.{}); while (versions_iter.next()) |ver_index| { // 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]; 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, // Default symbol version definition vs normal symbol version definition if (chosen_def_ver_index != 255 and ver_index == chosen_def_ver_index) "@@" else "@", 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, 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.dirs.global_cache, .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.PrelinkTask = 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.queuePrelinkTasks(task_buffer[0..task_buffer_i]); } fn buildSharedLib( comp: *Compilation, arena: Allocator, bin_directory: Cache.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 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, .{ .paths = .{ .root = .zig_lib_root, .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, }); 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, .{ .dirs = comp.dirs.withoutLocalCache(), .thread_pool = comp.thread_pool, .self_exe_path = comp.self_exe_path, // Because we manually cache the whole set of objects, we don't cache the individual objects // within it. In fact, we *can't* do that, because we need `emit_bin` to specify the path. .cache_mode = .none, .config = config, .root_mod = root_mod, .root_name = lib.name, .libc_installation = comp.libc_installation, .emit_bin = .{ .yes_path = try bin_directory.join(arena, &.{basename}) }, .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); }