commit a78f891d0528244382e902fb0757cce6f1db9c8a (tree)
parent 00e6895bde4b5871a944bccf4521b2f6f5f879f6
Author: Alex Rønne Petersen <alex@alexrp.com>
Date: Mon, 1 Dec 2025 04:30:28 +0100
compiler: support building openbsd crt0 and stub shared libraries
closes #2878
Diffstat:
4 files changed, 767 insertions(+), 0 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -547,6 +547,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
@@ -27,6 +27,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");
@@ -243,6 +244,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.
@@ -307,6 +309,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
@@ -315,6 +318,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,
@@ -1400,6 +1404,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,
@@ -1436,6 +1442,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",
@@ -2620,6 +2629,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);
@@ -2770,6 +2787,10 @@ pub fn destroy(comp: *Compilation) void {
netbsd_file.deinit(gpa, io);
}
+ if (comp.openbsd_so_files) |*openbsd_file| {
+ openbsd_file.deinit(gpa, io);
+ }
+
for (comp.c_object_table.keys()) |key| {
key.destroy(gpa, io);
}
@@ -4992,6 +5013,10 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node
prelink_group.async(io, buildNetBSDSharedObjects, .{ comp, main_progress_node });
}
+ if (comp.queued_jobs.openbsd_shared_objects) {
+ prelink_group.async(io, buildOpenBSDSharedObjects, .{ comp, main_progress_node });
+ }
+
if (comp.queued_jobs.libunwind) {
prelink_group.async(io, buildLibUnwind, .{ comp, main_progress_node });
}
@@ -5040,6 +5065,13 @@ fn dispatchPrelinkWork(comp: *Compilation, main_progress_node: std.Progress.Node
}
}
+ for (0..@typeInfo(openbsd.CrtFile).@"enum".fields.len) |i| {
+ if (comp.queued_jobs.openbsd_crt_file[i]) {
+ const tag: openbsd.CrtFile = @enumFromInt(i);
+ prelink_group.async(io, 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);
@@ -6041,6 +6073,29 @@ fn buildNetBSDSharedObjects(comp: *Compilation, prog_node: std.Progress.Node) vo
}
}
+fn buildOpenBSDCrtFile(comp: *Compilation, crt_file: openbsd.CrtFile, prog_node: std.Progress.Node) void {
+ 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 {
+ 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 {
if (mingw.buildCrtFile(comp, crt_file, prog_node)) |_| {
comp.queued_jobs.mingw_crt_file[@intFromEnum(crt_file)] = false;
diff --git a/src/libs/openbsd.zig b/src/libs/openbsd.zig
@@ -0,0 +1,703 @@
+const std = @import("std");
+const Io = std.Io;
+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, io: Io) void {
+ self.lock.release(io);
+ 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.createDirPathOpen(io, "h", .{}),
+ .cwd = comp.dirs.cwd,
+ };
+ cache.addPrefix(.{ .path = null, .handle = Io.Dir.cwd() });
+ cache.addPrefix(comp.dirs.zig_lib);
+ cache.addPrefix(comp.dirs.global_cache);
+ defer cache.manifest_dir.close(io);
+
+ 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" ++ 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.createDirPathOpen(io, o_sub_path, .{}),
+ .path = try comp.dirs.global_cache.join(arena, &.{o_sub_path}),
+ };
+ defer o_directory.handle.close(io);
+
+ 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(io, .{ .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" ++ path.sep_str ++ digest),
+ },
+ });
+}
+
+fn queueSharedObjects(comp: *Compilation, so_files: BuiltSharedObjects) std.Io.Cancelable!void {
+ const io = comp.io;
+ 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.lockUncancelable(io); // protect comp.arena
+ defer comp.mutex.unlock(io);
+
+ 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, path.sep, lib.name,
+ }) catch return comp.setAllocFailure(),
+ };
+ task_buffer[task_buffer_i] = .{ .load_dso = so_path };
+ task_buffer_i += 1;
+ }
+ }
+
+ try 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_limit = comp.thread_limit,
+ .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,
+ .environ_map = comp.environ_map,
+ }) 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
@@ -1210,6 +1210,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;
}
@@ -1713,6 +1720,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;