From bfe6117b1bd479a2e1871c842faac61e2bf31cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 1 Dec 2025 04:30:28 +0100 Subject: [PATCH] compiler: support building openbsd crt0 and stub shared libraries closes #2878 --- CMakeLists.txt | 1 + src/Compilation.zig | 59 ++++ src/libs/openbsd.zig | 699 +++++++++++++++++++++++++++++++++++++++++++ src/link/Lld.zig | 8 + 4 files changed, 767 insertions(+) create mode 100644 src/libs/openbsd.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 7090f88527..6e0684d373 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -553,6 +553,7 @@ set(ZIG_STAGE2_SOURCES src/libs/freebsd.zig src/libs/glibc.zig src/libs/netbsd.zig + src/libs/openbsd.zig src/introspect.zig src/libs/libcxx.zig src/libs/libtsan.zig diff --git a/src/Compilation.zig b/src/Compilation.zig index be3d49e31a..da69f6df00 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -29,6 +29,7 @@ const glibc = @import("libs/glibc.zig"); const musl = @import("libs/musl.zig"); const freebsd = @import("libs/freebsd.zig"); const netbsd = @import("libs/netbsd.zig"); +const openbsd = @import("libs/openbsd.zig"); const mingw = @import("libs/mingw.zig"); const libunwind = @import("libs/libunwind.zig"); const libcxx = @import("libs/libcxx.zig"); @@ -236,6 +237,7 @@ fuzzer_lib: ?CrtFile = null, glibc_so_files: ?glibc.BuiltSharedObjects = null, freebsd_so_files: ?freebsd.BuiltSharedObjects = null, netbsd_so_files: ?netbsd.BuiltSharedObjects = null, +openbsd_so_files: ?openbsd.BuiltSharedObjects = null, /// For example `Scrt1.o` and `libc_nonshared.a`. These are populated after building libc from source, /// The set of needed CRT (C runtime) files differs depending on the target and compilation settings. @@ -306,6 +308,7 @@ const QueuedJobs = struct { glibc_crt_file: [@typeInfo(glibc.CrtFile).@"enum".fields.len]bool = @splat(false), freebsd_crt_file: [@typeInfo(freebsd.CrtFile).@"enum".fields.len]bool = @splat(false), netbsd_crt_file: [@typeInfo(netbsd.CrtFile).@"enum".fields.len]bool = @splat(false), + openbsd_crt_file: [@typeInfo(openbsd.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 @@ -314,6 +317,7 @@ const QueuedJobs = struct { glibc_shared_objects: bool = false, freebsd_shared_objects: bool = false, netbsd_shared_objects: bool = false, + openbsd_shared_objects: bool = false, /// libunwind.a, usually needed when linking libc libunwind: bool = false, libcxx: bool = false, @@ -1391,6 +1395,8 @@ pub const MiscTask = enum { freebsd_shared_objects, netbsd_crt_file, netbsd_shared_objects, + openbsd_crt_file, + openbsd_shared_objects, mingw_crt_file, windows_import_lib, libunwind, @@ -1426,6 +1432,9 @@ pub const MiscTask = enum { @"netbsd libc Scrt0.o", @"netbsd libc shared object", + @"openbsd libc Scrt0.o", + @"openbsd libc shared object", + @"mingw-w64 crt2.o", @"mingw-w64 dllcrt2.o", @"mingw-w64 libmingw32.lib", @@ -2592,6 +2601,14 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, } comp.queued_jobs.netbsd_shared_objects = true; + } else if (target.isOpenBSDLibC()) { + if (!std.zig.target.canBuildLibC(target)) return diag.fail(.cross_libc_unavailable); + + if (openbsd.needsCrt0(comp.config.output_mode)) |f| { + comp.queued_jobs.openbsd_crt_file[@intFromEnum(f)] = true; + } + + comp.queued_jobs.openbsd_shared_objects = true; } else if (target.isWasiLibC()) { if (!std.zig.target.canBuildLibC(target)) return diag.fail(.cross_libc_unavailable); @@ -2733,6 +2750,10 @@ pub fn destroy(comp: *Compilation) void { netbsd_file.deinit(gpa); } + if (comp.openbsd_so_files) |*openbsd_file| { + openbsd_file.deinit(gpa); + } + for (comp.c_object_table.keys()) |key| { key.destroy(gpa); } @@ -4720,6 +4741,11 @@ fn performAllTheWork( comp.link_task_wait_group.spawnManager(buildNetBSDSharedObjects, .{ comp, main_progress_node }); } + if (comp.queued_jobs.openbsd_shared_objects) { + comp.link_task_queue.startPrelinkItem(); + comp.link_task_wait_group.spawnManager(buildOpenBSDSharedObjects, .{ comp, main_progress_node }); + } + if (comp.queued_jobs.libunwind) { comp.link_task_queue.startPrelinkItem(); comp.link_task_wait_group.spawnManager(buildLibUnwind, .{ comp, main_progress_node }); @@ -4777,6 +4803,14 @@ fn performAllTheWork( } } + for (0..@typeInfo(openbsd.CrtFile).@"enum".fields.len) |i| { + if (comp.queued_jobs.openbsd_crt_file[i]) { + const tag: openbsd.CrtFile = @enumFromInt(i); + comp.link_task_queue.startPrelinkItem(); + comp.link_task_wait_group.spawnManager(buildOpenBSDCrtFile, .{ 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); @@ -5989,6 +6023,31 @@ fn buildNetBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) vo } } +fn buildOpenBSDCrtFile(comp: *Compilation, crt_file: openbsd.CrtFile, prog_node: std.Progress.Node) void { + defer comp.link_task_queue.finishPrelinkItem(comp); + if (openbsd.buildCrtFile(comp, crt_file, prog_node)) |_| { + comp.queued_jobs.openbsd_crt_file[@intFromEnum(crt_file)] = false; + } else |err| switch (err) { + error.AlreadyReported => return, + else => comp.lockAndSetMiscFailure(.openbsd_crt_file, "unable to build OpenBSD {s}: {s}", .{ + @tagName(crt_file), @errorName(err), + }), + } +} + +fn buildOpenBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) void { + defer comp.link_task_queue.finishPrelinkItem(comp); + if (openbsd.buildSharedObjects(comp, prog_node)) |_| { + // The job should no longer be queued up since it succeeded. + comp.queued_jobs.openbsd_shared_objects = false; + } else |err| switch (err) { + error.AlreadyReported => return, + else => comp.lockAndSetMiscFailure(.openbsd_shared_objects, "unable to build OpenBSD libc shared objects: {s}", .{ + @errorName(err), + }), + } +} + fn buildMingwCrtFile(comp: *Compilation, crt_file: mingw.CrtFile, prog_node: std.Progress.Node) void { defer comp.link_task_queue.finishPrelinkItem(comp); if (mingw.buildCrtFile(comp, crt_file, prog_node)) |_| { diff --git a/src/libs/openbsd.zig b/src/libs/openbsd.zig new file mode 100644 index 0000000000..318fdb01e3 --- /dev/null +++ b/src/libs/openbsd.zig @@ -0,0 +1,699 @@ +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 { + scrt0_o, +}; + +pub fn needsCrt0(output_mode: std.builtin.OutputMode) ?CrtFile { + // https://github.com/ziglang/zig/issues/23574#issuecomment-2869089897 + return switch (output_mode) { + .Obj, .Lib => null, + .Exe => .scrt0_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 ++ "openbsd" ++ path.sep_str ++ "lib" ++ path.sep_str ++ "csu", + sub_path, + }); +} + +/// TODO replace anyerror with explicit error set, recording user-friendly errors with +/// lockAndSetMiscFailure and returning error.AlreadyReported. 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; + const target_version = target.os.version_range.semver.min; + + // 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) { + .scrt0_o => { + var cflags = std.array_list.Managed([]const u8).init(arena); + try cflags.appendSlice(&.{ + "-w", // Disable all warnings. + }); + + // See `Compilation.addCommonCCArgs`. + try cflags.append(try std.fmt.allocPrint(arena, "-D___OpenBSD={d}", .{ + 202510, + })); + try cflags.append(try std.fmt.allocPrint(arena, "-DOpenBSD{d}_{d}", .{ + target_version.major, + target_version.minor, + })); + + try cflags.appendSlice(&.{ + "-I", + try includePath(comp, arena, try std.fmt.allocPrint(arena, "{s}-{s}-{s}", .{ + std.zig.target.openbsdArchNameHeaders(target.cpu.arch), + @tagName(target.os.tag), + @tagName(target.abi), + })), + "-I", + try includePath(comp, arena, "generic-openbsd"), + "-I", + try csuPath(comp, arena, switch (target.cpu.arch) { + .mips64el => "mips64", + .x86 => "i386", + .x86_64 => "amd64", + else => |t| @tagName(t), + }), + "-Qunused-arguments", + }); + + const sources = [_]struct { + path: []const u8, + flags: []const []const u8, + }{ + .{ + .path = "crt0.c", + .flags = cflags.items, + }, + .{ + .path = "crtbegin.c", + .flags = cflags.items, + }, + }; + + var files_buf: [sources.len]Compilation.CSourceFile = undefined; + var files_index: usize = 0; + for (sources) |file| { + 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("crt0", .Obj, .@"openbsd libc Scrt0.o", prog_node, files, .{ + // Unclear why OpenBSD does this, but we'll do the same. + .omit_frame_pointer = if (target.cpu.arch.isX86()) false else null, + .pic = true, + }); + }, + } +} + +pub const Lib = struct { + name: []const u8, +}; + +// Library versions are bumped frequently on OpenBSD. Fortunately, by linking to +// just libc.so, the dynamic linker will happily bind to e.g. libc.so.102.0. +pub const libs = [_]Lib{ + .{ .name = "m" }, + .{ .name = "pthread" }, + .{ .name = "c" }, + .{ .name = "ld" }, + .{ .name = "util" }, + .{ .name = "execinfo" }, +}; + +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 ++ "openbsd" ++ path.sep_str ++ "abilists"; +pub const abilists_max_size = 300 * 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 ++ "openbsd" ++ 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, "openbsd")) { + log.err("abilists: expected OS 'openbsd', 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 = .openbsd, + .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; + } +}; + +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 +/// lockAndSetMiscFailure and returning error.AlreadyReported. 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; + const io = comp.io; + + var arena_allocator = std.heap.ArenaAllocator.init(gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const target = comp.getTarget(); + const target_version = target.os.version_range.semver.min; + + // Use the global cache directory. + var cache: Cache = .{ + .gpa = gpa, + .io = io, + .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 OpenBSD libc version: {f}", .{target_version}); + return error.InvalidTargetLibCVersion; + }, + } + } else blk: { + const latest_index = metadata.all_versions.len - 1; + log.warn("zig cannot build new OpenBSD libc version {f}; providing instead {f}", .{ + target_version, metadata.all_versions[latest_index], + }); + break :blk latest_index; + }; + + var stubs_asm = std.array_list.Managed(u8).init(gpa); + defer stubs_asm.deinit(); + + for (libs, 0..) |lib, lib_i| { + stubs_asm.shrinkRetainingCapacity(0); + + try stubs_asm.appendSlice(".text\n"); + + var sym_i: usize = 0; + var sym_name_buf: std.Io.Writer.Allocating = .init(arena); + var opt_symbol_name: ?[]const u8 = null; + + var inc_reader: std.Io.Reader = .fixed(metadata.inclusions); + + const fn_inclusions_len = try inc_reader.takeInt(u16, .little); + + var chosen_ver_index: usize = 255; + var chosen_is_weak: bool = undefined; + + while (sym_i < fn_inclusions_len) : (sym_i += 1) { + const sym_name = opt_symbol_name orelse n: { + sym_name_buf.clearRetainingCapacity(); + _ = try inc_reader.streamDelimiter(&sym_name_buf.writer, 0); + assert(inc_reader.buffered()[0] == 0); // TODO change streamDelimiter API + inc_reader.toss(1); + + opt_symbol_name = sym_name_buf.written(); + chosen_ver_index = 255; + + break :n sym_name_buf.written(); + }; + + { + const targets = try inc_reader.takeLeb128(u64); + var lib_index = try inc_reader.takeByte(); + + 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.takeByte(); + const last = (byte & 0b1000_0000) != 0; + const ver_i = @as(u7, @truncate(byte)); + if (ok_lib_and_target and ver_i <= target_ver_index and + (chosen_ver_index == 255 or ver_i > chosen_ver_index)) + { + chosen_ver_index = ver_i; + chosen_is_weak = is_weak; + } + if (last) break; + } + + if (is_terminal) { + opt_symbol_name = null; + } else continue; + } + + if (chosen_ver_index != 255) { + // Example: + // .balign 4 + // .globl _Exit + // .type _Exit, %function + // _Exit: .long 0 + try stubs_asm.print( + \\.balign {d} + \\.{s} {s} + \\.type {s}, %function + \\{s}: {s} 0 + \\ + , .{ + target.ptrBitWidth() / 8, + if (chosen_is_weak) "weak" else "globl", + sym_name, + sym_name, + sym_name, + wordDirective(target), + }); + } + } + + try stubs_asm.appendSlice(".data\n"); + + const obj_inclusions_len = try inc_reader.takeInt(u16, .little); + + sym_i = 0; + opt_symbol_name = null; + + var chosen_size: u16 = undefined; + + while (sym_i < obj_inclusions_len) : (sym_i += 1) { + const sym_name = opt_symbol_name orelse n: { + sym_name_buf.clearRetainingCapacity(); + _ = try inc_reader.streamDelimiter(&sym_name_buf.writer, 0); + assert(inc_reader.buffered()[0] == 0); // TODO change streamDelimiter API + inc_reader.toss(1); + + opt_symbol_name = sym_name_buf.written(); + chosen_ver_index = 255; + + break :n sym_name_buf.written(); + }; + + { + const targets = try inc_reader.takeLeb128(u64); + const size = try inc_reader.takeLeb128(u16); + var lib_index = try inc_reader.takeByte(); + + 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.takeByte(); + const last = (byte & 0b1000_0000) != 0; + const ver_i = @as(u7, @truncate(byte)); + if (ok_lib_and_target and ver_i <= target_ver_index and + (chosen_ver_index == 255 or ver_i > chosen_ver_index)) + { + chosen_ver_index = ver_i; + chosen_size = size; + chosen_is_weak = is_weak; + } + if (last) break; + } + + if (is_terminal) { + opt_symbol_name = null; + } else continue; + } + + if (chosen_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_asm.print( + \\.balign {d} + \\.{s} {s} + \\.type {s}, %object + \\.size {s}, {d} + \\{s}: {s} 0 + \\ + , .{ + target.ptrBitWidth() / 8, + if (chosen_is_weak) "weak" else "globl", + sym_name, + sym_name, + sym_name, + chosen_size, + sym_name, + wordDirective(target), + }); + } + } + + var lib_name_buf: [32]u8 = undefined; // Larger than each of the names "c", "pthread", 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 OpenBSD 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), + }, + }); +} + +fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) void { + assert(comp.openbsd_so_files == null); + comp.openbsd_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", .{ + so_files.dir_path.sub_path, fs.path.sep, lib.name, + }) 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 io = comp.io; + const basename = try std.fmt.allocPrint(arena, "lib{s}.so", .{lib.name}); + const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?); + const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else 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 misc_task: Compilation.MiscTask = .@"openbsd libc shared object"; + + var sub_create_diag: Compilation.CreateDiagnostic = undefined; + const sub_compilation = Compilation.create(comp.gpa, arena, io, &sub_create_diag, .{ + .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, + .soname = soname, + .c_source_files = &c_source_files, + .skip_linker_dependencies = true, + }) catch |err| switch (err) { + error.CreateFail => { + comp.lockAndSetMiscFailure(misc_task, "sub-compilation of {t} failed: {f}", .{ misc_task, sub_create_diag }); + return error.AlreadyReported; + }, + else => |e| return e, + }; + defer sub_compilation.destroy(); + + try comp.updateSubCompilation(sub_compilation, misc_task, prog_node); +} diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 7b1e34df78..175bbb5bb2 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1202,6 +1202,13 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { }); try argv.append(lib_path); } + } else if (target.isOpenBSDLibC()) { + for (openbsd.libs) |lib| { + const lib_path = try std.fmt.allocPrint(arena, "{f}{c}lib{s}.so", .{ + comp.openbsd_so_files.?.dir_path, fs.path.sep, lib.name, + }); + try argv.append(lib_path); + } } else { diags.flags.missing_libc = true; } @@ -1702,6 +1709,7 @@ const dev = @import("../dev.zig"); const freebsd = @import("../libs/freebsd.zig"); const glibc = @import("../libs/glibc.zig"); const netbsd = @import("../libs/netbsd.zig"); +const openbsd = @import("../libs/openbsd.zig"); const wasi_libc = @import("../libs/wasi_libc.zig"); const link = @import("../link.zig"); const lldMain = @import("../main.zig").lldMain;