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