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