1//! Example usage:
2//! ./gen_stubs /path/to/musl/build-all >libc.S
3//!
4//! The directory 'build-all' is expected to contain these subdirectories:
5//!
6//! * aarch64
7//! * arm
8//! * i386
9//! * hexagon
10//! * loongarch64
11//! * mips
12//! * mips64
13//! * mipsn32
14//! * powerpc
15//! * powerpc64
16//! * riscv32
17//! * riscv64
18//! * s390x
19//! * x32 (currently broken)
20//! * x86_64
21//!
22//! ...each with 'lib/libc.so' inside of them.
23//!
24//! When building the resulting libc.S file, these defines are required:
25//! * `-DTIME32`: When the target's primary time ABI is 32-bit
26//! * `-DPTR64`: When the target has 64-bit pointers
27//! * One of the following, corresponding to the CPU architecture:
28//! - `-DARCH_aarch64`
29//! - `-DARCH_arm`
30//! - `-DARCH_i386`
31//! - `-DARCH_hexagon`
32//! - `-DARCH_loongarch64`
33//! - `-DARCH_mips`
34//! - `-DARCH_mips64`
35//! - `-DARCH_mipsn32`
36//! - `-DARCH_powerpc`
37//! - `-DARCH_powerpc64`
38//! - `-DARCH_riscv32`
39//! - `-DARCH_riscv64`
40//! - `-DARCH_s390x`
41//! - `-DARCH_x32`
42//! - `-DARCH_x86_64`
43//! * One of the following, corresponding to the CPU architecture family:
44//! - `-DFAMILY_aarch64`
45//! - `-DFAMILY_arm`
46//! - `-DFAMILY_hexagon`
47//! - `-DFAMILY_loongarch`
48//! - `-DFAMILY_mips`
49//! - `-DFAMILY_powerpc`
50//! - `-DFAMILY_riscv`
51//! - `-DFAMILY_s390x`
52//! - `-DFAMILY_x86`
53
54// TODO: pick the best index to put them into instead of at the end
55// - e.g. find a common previous symbol and put it after that one
56// - they definitely need to go into the correct section
57
58const builtin = @import("builtin");
59const native_endian = builtin.cpu.arch.endian();
60
61const std = @import("std");
62const Io = std.Io;
63const mem = std.mem;
64const log = std.log;
65const elf = std.elf;
66
67const Arch = enum {
68 aarch64,
69 arm,
70 i386,
71 hexagon,
72 loongarch64,
73 mips,
74 mips64,
75 mipsn32,
76 powerpc,
77 powerpc64,
78 riscv32,
79 riscv64,
80 s390x,
81 x86_64,
82
83 pub fn ptrSize(arch: Arch) u16 {
84 return switch (arch) {
85 .arm,
86 .hexagon,
87 .i386,
88 .mips,
89 .mipsn32,
90 .powerpc,
91 .riscv32,
92 => 4,
93 .aarch64,
94 .loongarch64,
95 .mips64,
96 .powerpc64,
97 .riscv64,
98 .s390x,
99 .x86_64,
100 => 8,
101 };
102 }
103
104 pub fn isTime32(arch: Arch) bool {
105 return switch (arch) {
106 // This list will never grow; newer 32-bit ports will be time64 (e.g. riscv32).
107 .arm,
108 .i386,
109 .mips,
110 .mipsn32,
111 .powerpc,
112 => true,
113 else => false,
114 };
115 }
116
117 pub fn family(arch: Arch) Family {
118 return switch (arch) {
119 .aarch64 => .aarch64,
120 .arm => .arm,
121 .i386, .x86_64 => .x86,
122 .hexagon => .hexagon,
123 .loongarch64 => .loongarch,
124 .mips, .mips64, .mipsn32 => .mips,
125 .powerpc, .powerpc64 => .powerpc,
126 .riscv32, .riscv64 => .riscv,
127 .s390x => .s390x,
128 };
129 }
130};
131
132const Family = enum {
133 aarch64,
134 arm,
135 hexagon,
136 loongarch,
137 mips,
138 powerpc,
139 riscv,
140 s390x,
141 x86,
142};
143
144const arches: [@typeInfo(Arch).@"enum".field_names.len]Arch = blk: {
145 var result: [@typeInfo(Arch).@"enum".field_names.len]Arch = undefined;
146 for (@typeInfo(Arch).@"enum".field_values) |field_value| {
147 const arch: Arch = @fromBackingInt(@intCast(field_value));
148 result[archIndex(arch)] = arch;
149 }
150 break :blk result;
151};
152
153const MultiSym = struct {
154 size: [arches.len]u64,
155 present: [arches.len]bool,
156 binding: [arches.len]u4,
157 section: u16,
158 ty: u4,
159 visib: elf.STV,
160
161 fn isSingleArch(ms: MultiSym) ?Arch {
162 var result: ?Arch = null;
163 inline for (@typeInfo(Arch).@"enum".field_values) |field_value| {
164 const arch: Arch = @fromBackingInt(@intCast(field_value));
165 if (ms.present[archIndex(arch)]) {
166 if (result != null) return null;
167 result = arch;
168 }
169 }
170 return result;
171 }
172
173 fn isFamily(ms: MultiSym) ?Family {
174 var result: ?Family = null;
175 inline for (@typeInfo(Arch).@"enum".field_values) |field_value| {
176 const arch: Arch = @fromBackingInt(@intCast(field_value));
177 if (ms.present[archIndex(arch)]) {
178 const family = arch.family();
179 if (result) |r| if (family != r) return null;
180 result = family;
181 }
182 }
183 return result;
184 }
185
186 fn allPresent(ms: MultiSym) bool {
187 for (arches, 0..) |_, i| {
188 if (!ms.present[i]) {
189 return false;
190 }
191 }
192 return true;
193 }
194
195 fn isTime32Only(ms: MultiSym) bool {
196 inline for (@typeInfo(Arch).@"enum".field_values) |field_value| {
197 const arch: Arch = @fromBackingInt(@intCast(field_value));
198 if (ms.present[archIndex(arch)] != arch.isTime32()) {
199 return false;
200 }
201 }
202 return true;
203 }
204
205 fn commonSize(ms: MultiSym) ?u64 {
206 var size: ?u64 = null;
207 for (arches, 0..) |_, i| {
208 if (!ms.present[i]) continue;
209 if (size) |s| {
210 if (ms.size[i] != s) {
211 return null;
212 }
213 } else {
214 size = ms.size[i];
215 }
216 }
217 return size.?;
218 }
219
220 fn commonBinding(ms: MultiSym) ?u4 {
221 var binding: ?u4 = null;
222 for (arches, 0..) |_, i| {
223 if (!ms.present[i]) continue;
224 if (binding) |b| {
225 if (ms.binding[i] != b) {
226 return null;
227 }
228 } else {
229 binding = ms.binding[i];
230 }
231 }
232 return binding.?;
233 }
234
235 fn isPtrSize(ms: MultiSym, mult: u16) bool {
236 inline for (@typeInfo(Arch).@"enum".field_values) |field_value| {
237 const arch: Arch = @fromBackingInt(@intCast(field_value));
238 const arch_index = archIndex(arch);
239 if (ms.present[arch_index] and ms.size[arch_index] != arch.ptrSize() * mult) {
240 return false;
241 }
242 }
243 return true;
244 }
245
246 fn isWeak64(ms: MultiSym) bool {
247 inline for (@typeInfo(Arch).@"enum".field_values) |field_value| {
248 const arch: Arch = @fromBackingInt(@intCast(field_value));
249 const arch_index = archIndex(arch);
250 const binding: u4 = switch (arch.ptrSize()) {
251 4 => std.elf.STB_GLOBAL,
252 8 => std.elf.STB_WEAK,
253 else => unreachable,
254 };
255 if (ms.present[arch_index] and ms.binding[arch_index] != binding) {
256 return false;
257 }
258 }
259 return true;
260 }
261
262 fn isWeakTime64(ms: MultiSym) bool {
263 inline for (@typeInfo(Arch).@"enum".field_values) |field_value| {
264 const arch: Arch = @fromBackingInt(@intCast(field_value));
265 const arch_index = archIndex(arch);
266 const binding: u4 = if (arch.isTime32()) std.elf.STB_GLOBAL else std.elf.STB_WEAK;
267 if (ms.present[arch_index] and ms.binding[arch_index] != binding) {
268 return false;
269 }
270 }
271 return true;
272 }
273};
274
275const Parse = struct {
276 arena: mem.Allocator,
277 sym_table: *std.array_hash_map.String(MultiSym),
278 sections: *std.array_hash_map.String(void),
279 elf_bytes: []align(@alignOf(elf.Elf64_Ehdr)) u8,
280 header: elf.Header,
281 arch: Arch,
282};
283
284pub fn main(init: std.process.Init) !void {
285 const arena = init.arena.allocator();
286 const io = init.io;
287 const args = try init.minimal.args.toSlice(arena);
288 const build_all_path = args[1];
289
290 var build_all_dir = try Io.Dir.cwd().openDir(io, build_all_path, .{});
291
292 var sym_table: std.array_hash_map.String(MultiSym) = .empty;
293 var sections: std.array_hash_map.String(void) = .empty;
294
295 for (arches) |arch| {
296 const libc_so_path = try std.fmt.allocPrint(arena, "{t}/lib/libc.so", .{arch});
297
298 // Read the ELF header.
299 const elf_bytes = build_all_dir.readFileAllocOptions(
300 io,
301 libc_so_path,
302 arena,
303 .limited(100 * 1024 * 1024),
304 .of(elf.Elf64_Ehdr),
305 null,
306 ) catch |err| {
307 std.debug.panic("unable to read '{s}/{s}': {t}", .{ build_all_path, libc_so_path, err });
308 };
309 var stream: std.Io.Reader = .fixed(elf_bytes);
310 const header = try elf.Header.read(&stream);
311
312 const parse: Parse = .{
313 .arena = arena,
314 .sym_table = &sym_table,
315 .sections = &sections,
316 .elf_bytes = elf_bytes,
317 .header = header,
318 .arch = arch,
319 };
320
321 switch (header.is_64) {
322 true => switch (header.endian) {
323 .big => try parseElf(parse, true, .big),
324 .little => try parseElf(parse, true, .little),
325 },
326 false => switch (header.endian) {
327 .big => try parseElf(parse, false, .big),
328 .little => try parseElf(parse, false, .little),
329 },
330 }
331 }
332
333 var stdout_buffer: [2000]u8 = undefined;
334 var stdout_writer = Io.File.stdout().writerStreaming(io, &stdout_buffer);
335 const stdout = &stdout_writer.interface;
336 try stdout.writeAll(
337 \\#ifdef PTR64
338 \\#define WEAK64 .weak
339 \\#define PTR_SIZE_BYTES 8
340 \\#define PTR2_SIZE_BYTES 16
341 \\#else
342 \\#define WEAK64 .globl
343 \\#define PTR_SIZE_BYTES 4
344 \\#define PTR2_SIZE_BYTES 8
345 \\#endif
346 \\
347 \\#ifdef TIME32
348 \\#define WEAKTIME64 .globl
349 \\#else
350 \\#define WEAKTIME64 .weak
351 \\#endif
352 \\
353 \\
354 );
355
356 // Sort the symbols for deterministic output and cleaner vcs diffs.
357 const SymTableSort = struct {
358 sections: *const std.array_hash_map.String(void),
359 sym_table: *const std.array_hash_map.String(MultiSym),
360
361 /// Sort first by section name, then by symbol name
362 pub fn lessThan(ctx: @This(), index_a: usize, index_b: usize) bool {
363 const multi_sym_a = ctx.sym_table.values()[index_a];
364 const multi_sym_b = ctx.sym_table.values()[index_b];
365
366 const section_a = ctx.sections.keys()[multi_sym_a.section];
367 const section_b = ctx.sections.keys()[multi_sym_b.section];
368
369 switch (mem.order(u8, section_a, section_b)) {
370 .lt => return true,
371 .gt => return false,
372 .eq => {},
373 }
374
375 const symbol_a = ctx.sym_table.keys()[index_a];
376 const symbol_b = ctx.sym_table.keys()[index_b];
377
378 switch (mem.order(u8, symbol_a, symbol_b)) {
379 .lt => return true,
380 .gt, .eq => return false,
381 }
382 }
383 };
384 sym_table.sort(SymTableSort{ .sym_table = &sym_table, .sections = &sections });
385
386 var prev_section: u16 = std.math.maxInt(u16);
387 var prev_pp_state: union(enum) { all, single: Arch, multi, family: Family, time32 } = .all;
388 for (sym_table.values(), 0..) |multi_sym, sym_index| {
389 const name = sym_table.keys()[sym_index];
390
391 if (multi_sym.section != prev_section) {
392 prev_section = multi_sym.section;
393 const sh_name = sections.keys()[multi_sym.section];
394 try stdout.print("{s}\n", .{sh_name});
395 }
396
397 if (multi_sym.allPresent()) {
398 switch (prev_pp_state) {
399 .all => {},
400 .single, .multi, .family, .time32 => {
401 try stdout.writeAll("#endif\n");
402 prev_pp_state = .all;
403 },
404 }
405 } else if (multi_sym.isSingleArch()) |arch| {
406 switch (prev_pp_state) {
407 .all => {
408 try stdout.print("#ifdef ARCH_{s}\n", .{@tagName(arch)});
409 prev_pp_state = .{ .single = arch };
410 },
411 .multi, .family, .time32 => {
412 try stdout.print("#endif\n#ifdef ARCH_{s}\n", .{@tagName(arch)});
413 prev_pp_state = .{ .single = arch };
414 },
415 .single => |prev_arch| {
416 if (arch != prev_arch) {
417 try stdout.print("#endif\n#ifdef ARCH_{s}\n", .{@tagName(arch)});
418 prev_pp_state = .{ .single = arch };
419 }
420 },
421 }
422 } else if (multi_sym.isFamily()) |family| {
423 switch (prev_pp_state) {
424 .all => {
425 try stdout.print("#ifdef FAMILY_{s}\n", .{@tagName(family)});
426 prev_pp_state = .{ .family = family };
427 },
428 .single, .multi, .time32 => {
429 try stdout.print("#endif\n#ifdef FAMILY_{s}\n", .{@tagName(family)});
430 prev_pp_state = .{ .family = family };
431 },
432 .family => |prev_family| {
433 if (family != prev_family) {
434 try stdout.print("#endif\n#ifdef FAMILY_{s}\n", .{@tagName(family)});
435 prev_pp_state = .{ .family = family };
436 }
437 },
438 }
439 } else if (multi_sym.isTime32Only()) {
440 switch (prev_pp_state) {
441 .all => {
442 try stdout.writeAll("#ifdef TIME32\n");
443 prev_pp_state = .time32;
444 },
445 .single, .multi, .family => {
446 try stdout.writeAll("#endif\n#ifdef TIME32\n");
447 prev_pp_state = .time32;
448 },
449 .time32 => {},
450 }
451 } else {
452 switch (prev_pp_state) {
453 .all => {},
454 .single, .multi, .family, .time32 => {
455 try stdout.writeAll("#endif\n");
456 },
457 }
458 prev_pp_state = .multi;
459
460 var first = true;
461 try stdout.writeAll("#if ");
462
463 for (arches, 0..) |arch, i| {
464 if (multi_sym.present[i]) continue;
465
466 if (!first) try stdout.writeAll(" && ");
467 first = false;
468 try stdout.print("!defined(ARCH_{s})", .{@tagName(arch)});
469 }
470
471 try stdout.writeAll("\n");
472 }
473
474 if (multi_sym.commonBinding()) |binding| {
475 switch (binding) {
476 elf.STB_GLOBAL => {
477 try stdout.print(".globl {s}\n", .{name});
478 },
479 elf.STB_WEAK => {
480 try stdout.print(".weak {s}\n", .{name});
481 },
482 else => unreachable,
483 }
484 } else if (multi_sym.isWeak64()) {
485 try stdout.print("WEAK64 {s}\n", .{name});
486 } else if (multi_sym.isWeakTime64()) {
487 try stdout.print("WEAKTIME64 {s}\n", .{name});
488 } else {
489 for (arches, 0..) |arch, i| {
490 log.info("symbol '{s}' binding on {s}: {d}", .{
491 name, @tagName(arch), multi_sym.binding[i],
492 });
493 }
494 }
495
496 switch (multi_sym.ty) {
497 elf.STT_NOTYPE => {},
498 elf.STT_FUNC => {
499 try stdout.print(".type {s}, %function;\n", .{name});
500 // omitting the size is OK for functions
501 },
502 elf.STT_OBJECT => {
503 try stdout.print(".type {s}, %object;\n", .{name});
504 if (multi_sym.commonSize()) |size| {
505 try stdout.print(".size {s}, {d}\n", .{ name, size });
506 } else if (multi_sym.isPtrSize(1)) {
507 try stdout.print(".size {s}, PTR_SIZE_BYTES\n", .{name});
508 } else if (multi_sym.isPtrSize(2)) {
509 try stdout.print(".size {s}, PTR2_SIZE_BYTES\n", .{name});
510 } else {
511 for (arches, 0..) |arch, i| {
512 log.info("symbol '{s}' size on {s}: {d}", .{
513 name, @tagName(arch), multi_sym.size[i],
514 });
515 }
516 //try stdout.print(".size {s}, {d}\n", .{ name, size });
517 }
518 },
519 else => unreachable,
520 }
521
522 switch (multi_sym.visib) {
523 .DEFAULT => {},
524 .PROTECTED => try stdout.print(".protected {s}\n", .{name}),
525 .INTERNAL, .HIDDEN => unreachable,
526 }
527
528 try stdout.print("{s}:\n", .{name});
529 }
530
531 switch (prev_pp_state) {
532 .all => {},
533 .single, .multi, .family, .time32 => try stdout.writeAll("#endif\n"),
534 }
535
536 try stdout.flush();
537}
538
539fn parseElf(parse: Parse, comptime is_64: bool, comptime endian: std.builtin.Endian) !void {
540 const arena = parse.arena;
541 const elf_bytes = parse.elf_bytes;
542 const header = parse.header;
543 const Sym = if (is_64) elf.Elf64_Sym else elf.Elf32_Sym;
544 const S = struct {
545 fn endianSwap(x: anytype) @TypeOf(x) {
546 if (endian != native_endian) {
547 return @byteSwap(x);
548 } else {
549 return x;
550 }
551 }
552 fn symbolAddrLessThan(_: void, lhs: Sym, rhs: Sym) bool {
553 return endianSwap(lhs.st_value) < endianSwap(rhs.st_value);
554 }
555 };
556 // A little helper to do endian swapping.
557 const s = S.endianSwap;
558
559 // Obtain list of sections.
560 const Shdr = if (is_64) elf.Elf64_Shdr else elf.Elf32_Shdr;
561 const shdrs = mem.bytesAsSlice(Shdr, elf_bytes[header.shoff..])[0..header.shnum];
562
563 // Obtain the section header string table.
564 const shstrtab_offset = s(shdrs[header.shstrndx].sh_offset);
565 log.debug("shstrtab is at offset {d}", .{shstrtab_offset});
566 const shstrtab = elf_bytes[shstrtab_offset..];
567
568 // Maps this ELF file's section header index to the multi arch section ArrayHashMap index.
569 const section_index_map = try arena.alloc(u16, shdrs.len);
570
571 // Find the offset of the dynamic symbol table.
572 var dynsym_index: u16 = 0;
573 for (shdrs, 0..) |shdr, i| {
574 const sh_name = try arena.dupe(u8, mem.sliceTo(shstrtab[s(shdr.sh_name)..], 0));
575 log.debug("found section: {s}", .{sh_name});
576 if (mem.eql(u8, sh_name, ".dynsym")) {
577 dynsym_index = @as(u16, @intCast(i));
578 }
579 const gop = try parse.sections.getOrPut(arena, sh_name);
580 section_index_map[i] = @as(u16, @intCast(gop.index));
581 }
582 if (dynsym_index == 0) @panic("did not find the .dynsym section");
583
584 log.debug("found .dynsym section at index {d}", .{dynsym_index});
585
586 // Read the dynamic symbols into a list.
587 const dyn_syms_off = s(shdrs[dynsym_index].sh_offset);
588 const dyn_syms_size = s(shdrs[dynsym_index].sh_size);
589 const dyn_syms = mem.bytesAsSlice(Sym, elf_bytes[dyn_syms_off..][0..dyn_syms_size]);
590
591 const dynstr_offset = s(shdrs[s(shdrs[dynsym_index].sh_link)].sh_offset);
592 const dynstr = elf_bytes[dynstr_offset..];
593
594 // Sort the list by address, ascending.
595 // We need a copy to fix alignment.
596 const copied_dyn_syms = copy: {
597 const ptr = try arena.alloc(Sym, dyn_syms.len);
598 @memcpy(ptr, dyn_syms);
599 break :copy ptr;
600 };
601 mem.sort(Sym, copied_dyn_syms, {}, S.symbolAddrLessThan);
602
603 for (copied_dyn_syms) |sym| {
604 const this_section = s(sym.st_shndx);
605 const name = try arena.dupe(u8, mem.sliceTo(dynstr[s(sym.st_name)..], 0));
606 const ty = @as(u4, @truncate(sym.st_info));
607 const binding = @as(u4, @truncate(sym.st_info >> 4));
608 const visib = @as(elf.STV, @fromBackingInt(@intCast(@as(u3, @truncate(sym.st_other)))));
609 const size = s(sym.st_size);
610
611 if (size == 0) {
612 log.warn("{s}: symbol '{s}' has size 0", .{ @tagName(parse.arch), name });
613 }
614
615 if (sym.st_shndx == elf.SHN_UNDEF) {
616 log.debug("{s}: skipping '{s}' due to it being undefined", .{
617 @tagName(parse.arch), name,
618 });
619 continue;
620 }
621
622 switch (binding) {
623 elf.STB_GLOBAL, elf.STB_WEAK => {},
624 else => {
625 log.debug("{s}: skipping '{s}' due to it having binding '{d}'", .{
626 @tagName(parse.arch), name, binding,
627 });
628 continue;
629 },
630 }
631
632 switch (ty) {
633 elf.STT_NOTYPE, elf.STT_FUNC, elf.STT_OBJECT => {},
634 else => {
635 log.debug("{s}: skipping '{s}' due to it having type '{d}'", .{
636 @tagName(parse.arch), name, ty,
637 });
638 continue;
639 },
640 }
641
642 switch (visib) {
643 .DEFAULT, .PROTECTED => {},
644 .INTERNAL, .HIDDEN => {
645 log.debug("{s}: skipping '{s}' due to it having visibility '{s}'", .{
646 @tagName(parse.arch), name, @tagName(visib),
647 });
648 continue;
649 },
650 }
651
652 const gop = try parse.sym_table.getOrPut(arena, name);
653 if (gop.found_existing) {
654 if (gop.value_ptr.section != section_index_map[this_section]) {
655 const sh_name = mem.sliceTo(shstrtab[s(shdrs[this_section].sh_name)..], 0);
656 fatal("symbol '{s}' in arch {s} is in section {s} but in arch {s} is in section {s}", .{
657 name,
658 @tagName(parse.arch),
659 sh_name,
660 archSetName(gop.value_ptr.present),
661 parse.sections.keys()[gop.value_ptr.section],
662 });
663 }
664 if (gop.value_ptr.ty != ty) blk: {
665 if (ty == elf.STT_NOTYPE) {
666 log.warn("symbol '{s}' in arch {s} has type {d} but in arch {s} has type {d}. going with the one that is not STT_NOTYPE", .{
667 name,
668 @tagName(parse.arch),
669 ty,
670 archSetName(gop.value_ptr.present),
671 gop.value_ptr.ty,
672 });
673 break :blk;
674 }
675 if (gop.value_ptr.ty == elf.STT_NOTYPE) {
676 log.warn("symbol '{s}' in arch {s} has type {d} but in arch {s} has type {d}. going with the one that is not STT_NOTYPE", .{
677 name,
678 @tagName(parse.arch),
679 ty,
680 archSetName(gop.value_ptr.present),
681 gop.value_ptr.ty,
682 });
683 gop.value_ptr.ty = ty;
684 break :blk;
685 }
686 fatal("symbol '{s}' in arch {s} has type {d} but in arch {s} has type {d}", .{
687 name,
688 @tagName(parse.arch),
689 ty,
690 archSetName(gop.value_ptr.present),
691 gop.value_ptr.ty,
692 });
693 }
694 if (gop.value_ptr.visib != visib) {
695 fatal("symbol '{s}' in arch {s} has visib {s} but in arch {s} has visib {s}", .{
696 name,
697 @tagName(parse.arch),
698 @tagName(visib),
699 archSetName(gop.value_ptr.present),
700 @tagName(gop.value_ptr.visib),
701 });
702 }
703 } else {
704 gop.value_ptr.* = .{
705 .present = @splat(false),
706 .section = section_index_map[this_section],
707 .ty = ty,
708 .binding = @splat(0),
709 .visib = visib,
710 .size = @splat(0),
711 };
712 }
713 gop.value_ptr.present[archIndex(parse.arch)] = true;
714 gop.value_ptr.size[archIndex(parse.arch)] = size;
715 gop.value_ptr.binding[archIndex(parse.arch)] = binding;
716 }
717}
718
719fn archIndex(arch: Arch) u8 {
720 return @backingInt(arch);
721}
722
723fn archSetName(arch_set: [arches.len]bool) []const u8 {
724 for (arches, arch_set) |arch, set_item| {
725 if (set_item) {
726 return @tagName(arch);
727 }
728 }
729 return "(none)";
730}
731
732fn fatal(comptime format: []const u8, args: anytype) noreturn {
733 log.err(format, args);
734 std.process.exit(1);
735}