pub const File = union(enum) { zig_object: *ZigObject, internal: *InternalObject, object: *Object, dylib: *Dylib, pub fn getIndex(file: File) Index { return switch (file) { inline else => |x| x.index, }; } pub fn fmtPath(file: File) std.fmt.Alt(File, formatPath) { return .{ .data = file }; } fn formatPath(file: File, w: *Writer) Writer.Error!void { switch (file) { .zig_object => |zo| try w.writeAll(zo.basename), .internal => try w.writeAll("internal"), .object => |x| try w.print("{f}", .{x.fmtPath()}), .dylib => |dl| try w.print("{f}", .{@as(Path, dl.path)}), } } pub fn resolveSymbols(file: File, macho_file: *MachO) !void { return switch (file) { inline else => |x| x.resolveSymbols(macho_file), }; } pub fn scanRelocs(file: File, macho_file: *MachO) !void { return switch (file) { .dylib => unreachable, inline else => |x| x.scanRelocs(macho_file), }; } /// Encodes symbol rank so that the following ordering applies: /// * strong in object /// * strong in archive/dylib /// * weak in object /// * weak in archive/dylib /// * tentative in object /// * tentative in archive /// * unclaimed /// Ties are broken by file priority. pub fn getSymbolRank(file: File, args: struct { archive: bool = false, weak: bool = false, tentative: bool = false, }) u32 { const archive_or_dylib = @as(u32, @intFromBool(file == .dylib or args.archive)) << 29; const strength: u32 = if (args.tentative) 0b10 << 30 else if (args.weak) 0b01 << 30 else 0b00 << 30; return strength | archive_or_dylib | file.getIndex(); } pub fn getAtom(file: File, atom_index: Atom.Index) ?*Atom { return switch (file) { .dylib => unreachable, inline else => |x| x.getAtom(atom_index), }; } pub fn getAtoms(file: File) []const Atom.Index { return switch (file) { .dylib => unreachable, inline else => |x| x.getAtoms(), }; } pub fn addAtomExtra(file: File, allocator: Allocator, extra: Atom.Extra) !u32 { return switch (file) { .dylib => unreachable, inline else => |x| x.addAtomExtra(allocator, extra), }; } pub fn getAtomExtra(file: File, index: u32) Atom.Extra { return switch (file) { .dylib => unreachable, inline else => |x| x.getAtomExtra(index), }; } pub fn setAtomExtra(file: File, index: u32, extra: Atom.Extra) void { return switch (file) { .dylib => unreachable, inline else => |x| x.setAtomExtra(index, extra), }; } pub fn getSymbols(file: File) []Symbol { return switch (file) { inline else => |x| x.symbols.items, }; } pub fn getSymbolRef(file: File, sym_index: Symbol.Index, macho_file: *MachO) MachO.Ref { return switch (file) { inline else => |x| x.getSymbolRef(sym_index, macho_file), }; } pub fn getNlists(file: File) []macho.nlist_64 { return switch (file) { .dylib => unreachable, .internal => |x| x.symtab.items, inline else => |x| x.symtab.items(.nlist), }; } pub fn getGlobals(file: File) []MachO.SymbolResolver.Index { return switch (file) { inline else => |x| x.globals.items, }; } pub fn markImportsExports(file: File, macho_file: *MachO) void { const tracy = trace(@src()); defer tracy.end(); const nsyms = switch (file) { .dylib => unreachable, inline else => |x| x.symbols.items.len, }; for (0..nsyms) |i| { const ref = file.getSymbolRef(@intCast(i), macho_file); if (ref.getFile(macho_file) == null) continue; const sym = ref.getSymbol(macho_file).?; if (sym.visibility != .global) continue; if (sym.getFile(macho_file).? == .dylib and !sym.flags.abs) { sym.flags.import = true; continue; } if (file.getIndex() == ref.file) { sym.flags.@"export" = true; } } } pub fn markExportsRelocatable(file: File, macho_file: *MachO) void { const tracy = trace(@src()); defer tracy.end(); assert(file == .object or file == .zig_object); for (file.getSymbols(), 0..) |*sym, i| { const ref = file.getSymbolRef(@intCast(i), macho_file); const other_file = ref.getFile(macho_file) orelse continue; if (other_file.getIndex() != file.getIndex()) continue; if (sym.visibility != .global) continue; sym.flags.@"export" = true; } } pub fn createSymbolIndirection(file: File, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const nsyms = switch (file) { inline else => |x| x.symbols.items.len, }; for (0..nsyms) |i| { const ref = file.getSymbolRef(@intCast(i), macho_file); if (ref.getFile(macho_file) == null) continue; if (ref.file != file.getIndex()) continue; const sym = ref.getSymbol(macho_file).?; if (sym.getSectionFlags().needs_got) { log.debug("'{s}' needs GOT", .{sym.getName(macho_file)}); try macho_file.got.addSymbol(ref, macho_file); } if (sym.getSectionFlags().stubs) { log.debug("'{s}' needs STUBS", .{sym.getName(macho_file)}); try macho_file.stubs.addSymbol(ref, macho_file); } if (sym.getSectionFlags().tlv_ptr) { log.debug("'{s}' needs TLV pointer", .{sym.getName(macho_file)}); try macho_file.tlv_ptr.addSymbol(ref, macho_file); } if (sym.getSectionFlags().objc_stubs) { log.debug("'{s}' needs OBJC STUBS", .{sym.getName(macho_file)}); try macho_file.objc_stubs.addSymbol(ref, macho_file); } } } pub fn claimUnresolved(file: File, macho_file: *MachO) void { const tracy = trace(@src()); defer tracy.end(); assert(file == .object or file == .zig_object); for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| { if (!nlist.n_type.bits.ext) continue; if (nlist.n_type.bits.type != .undf) continue; if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue; const is_import = switch (macho_file.undefined_treatment) { .@"error" => false, .warn, .suppress => nlist.n_desc.weak_ref, .dynamic_lookup => true, }; if (is_import) { sym.value = 0; sym.atom_ref = .{ .index = 0, .file = 0 }; sym.flags.weak = false; sym.flags.weak_ref = nlist.n_desc.weak_ref; sym.flags.import = is_import; sym.visibility = .global; const idx = file.getGlobals()[i]; macho_file.resolver.values.items[idx - 1] = .{ .index = @intCast(i), .file = file.getIndex() }; } } } pub fn claimUnresolvedRelocatable(file: File, macho_file: *MachO) void { const tracy = trace(@src()); defer tracy.end(); assert(file == .object or file == .zig_object); for (file.getSymbols(), file.getNlists(), 0..) |*sym, nlist, i| { if (!nlist.n_type.bits.ext) continue; if (nlist.n_type.bits.type != .undf) continue; if (file.getSymbolRef(@intCast(i), macho_file).getFile(macho_file) != null) continue; sym.value = 0; sym.atom_ref = .{ .index = 0, .file = 0 }; sym.flags.weak_ref = nlist.n_desc.weak_ref; sym.flags.import = true; sym.visibility = .global; const idx = file.getGlobals()[i]; macho_file.resolver.values.items[idx - 1] = .{ .index = @intCast(i), .file = file.getIndex() }; } } pub fn checkDuplicates(file: File, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); const gpa = macho_file.base.comp.gpa; for (file.getSymbols(), file.getNlists(), 0..) |sym, nlist, i| { if (sym.visibility != .global) continue; if (sym.flags.weak) continue; if (nlist.n_type.bits.type == .undf) continue; const ref = file.getSymbolRef(@intCast(i), macho_file); const ref_file = ref.getFile(macho_file) orelse continue; if (ref_file.getIndex() == file.getIndex()) continue; macho_file.dupes_mutex.lock(); defer macho_file.dupes_mutex.unlock(); const gop = try macho_file.dupes.getOrPut(gpa, file.getGlobals()[i]); if (!gop.found_existing) { gop.value_ptr.* = .{}; } try gop.value_ptr.append(gpa, file.getIndex()); } } pub fn initOutputSections(file: File, macho_file: *MachO) !void { const tracy = trace(@src()); defer tracy.end(); for (file.getAtoms()) |atom_index| { const atom = file.getAtom(atom_index) orelse continue; if (!atom.isAlive()) continue; atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(macho_file), macho_file); } } pub fn dedupLiterals(file: File, lp: MachO.LiteralPool, macho_file: *MachO) void { return switch (file) { .dylib => unreachable, inline else => |x| x.dedupLiterals(lp, macho_file), }; } pub fn writeAtoms(file: File, macho_file: *MachO) !void { return switch (file) { .dylib => unreachable, inline else => |x| x.writeAtoms(macho_file), }; } pub fn writeAtomsRelocatable(file: File, macho_file: *MachO) !void { return switch (file) { .dylib, .internal => unreachable, inline else => |x| x.writeAtomsRelocatable(macho_file), }; } pub fn calcSymtabSize(file: File, macho_file: *MachO) void { return switch (file) { inline else => |x| x.calcSymtabSize(macho_file), }; } pub fn writeSymtab(file: File, macho_file: *MachO, ctx: anytype) void { return switch (file) { inline else => |x| x.writeSymtab(macho_file, ctx), }; } pub fn updateArSymtab(file: File, ar_symtab: *Archive.ArSymtab, macho_file: *MachO) error{OutOfMemory}!void { return switch (file) { .dylib, .internal => unreachable, inline else => |x| x.updateArSymtab(ar_symtab, macho_file), }; } pub fn updateArSize(file: File, macho_file: *MachO) !void { return switch (file) { .dylib, .internal => unreachable, .zig_object => |x| x.updateArSize(), .object => |x| x.updateArSize(macho_file), }; } pub fn writeAr(file: File, ar_format: Archive.Format, macho_file: *MachO, writer: anytype) !void { return switch (file) { .dylib, .internal => unreachable, .zig_object => |x| x.writeAr(ar_format, writer), .object => |x| x.writeAr(ar_format, macho_file, writer), }; } pub fn parse(file: File, macho_file: *MachO) !void { return switch (file) { .internal, .zig_object => unreachable, .object => |x| x.parse(macho_file), .dylib => |x| x.parse(macho_file), }; } pub fn parseAr(file: File, macho_file: *MachO) !void { return switch (file) { .internal, .zig_object, .dylib => unreachable, .object => |x| x.parseAr(macho_file), }; } pub const Index = u32; pub const Entry = union(enum) { null: void, zig_object: ZigObject, internal: InternalObject, object: Object, dylib: Dylib, }; pub const Handle = std.fs.File; pub const HandleIndex = Index; }; const std = @import("std"); const assert = std.debug.assert; const log = std.log.scoped(.link); const macho = std.macho; const Allocator = std.mem.Allocator; const Path = std.Build.Cache.Path; const Writer = std.Io.Writer; const trace = @import("../../tracy.zig").trace; const Archive = @import("Archive.zig"); const Atom = @import("Atom.zig"); const InternalObject = @import("InternalObject.zig"); const MachO = @import("../MachO.zig"); const Object = @import("Object.zig"); const Dylib = @import("Dylib.zig"); const Symbol = @import("Symbol.zig"); const ZigObject = @import("ZigObject.zig");