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