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 = &sections,
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 = &sections });
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}