master
  1pub fn gcAtoms(macho_file: *MachO) !void {
  2    const gpa = macho_file.base.comp.gpa;
  3
  4    var objects = try std.array_list.Managed(File.Index).initCapacity(gpa, macho_file.objects.items.len + 1);
  5    defer objects.deinit();
  6    for (macho_file.objects.items) |index| objects.appendAssumeCapacity(index);
  7    if (macho_file.internal_object) |index| objects.appendAssumeCapacity(index);
  8
  9    var roots = std.array_list.Managed(*Atom).init(gpa);
 10    defer roots.deinit();
 11
 12    try collectRoots(&roots, objects.items, macho_file);
 13    mark(roots.items, objects.items, macho_file);
 14    prune(objects.items, macho_file);
 15}
 16
 17fn collectRoots(roots: *std.array_list.Managed(*Atom), objects: []const File.Index, macho_file: *MachO) !void {
 18    for (objects) |index| {
 19        const object = macho_file.getFile(index).?;
 20        for (object.getSymbols(), 0..) |*sym, i| {
 21            const ref = object.getSymbolRef(@intCast(i), macho_file);
 22            const file = ref.getFile(macho_file) orelse continue;
 23            if (file.getIndex() != index) continue;
 24            if (sym.flags.no_dead_strip or (macho_file.base.isDynLib() and sym.visibility == .global))
 25                try markSymbol(sym, roots, macho_file);
 26        }
 27
 28        for (object.getAtoms()) |atom_index| {
 29            const atom = object.getAtom(atom_index) orelse continue;
 30            const isec = atom.getInputSection(macho_file);
 31            switch (isec.type()) {
 32                macho.S_MOD_INIT_FUNC_POINTERS,
 33                macho.S_MOD_TERM_FUNC_POINTERS,
 34                => if (markAtom(atom)) try roots.append(atom),
 35
 36                else => if (isec.isDontDeadStrip() and markAtom(atom)) {
 37                    try roots.append(atom);
 38                },
 39            }
 40        }
 41    }
 42
 43    for (macho_file.objects.items) |index| {
 44        const object = macho_file.getFile(index).?.object;
 45        for (object.unwind_records_indexes.items) |cu_index| {
 46            const cu = object.getUnwindRecord(cu_index);
 47            if (!cu.alive) continue;
 48            if (cu.getFde(macho_file)) |fde| {
 49                if (fde.getCie(macho_file).getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file);
 50            } else if (cu.getPersonality(macho_file)) |sym| try markSymbol(sym, roots, macho_file);
 51        }
 52    }
 53
 54    if (macho_file.getInternalObject()) |obj| {
 55        for (obj.force_undefined.items) |sym_index| {
 56            const ref = obj.getSymbolRef(sym_index, macho_file);
 57            if (ref.getFile(macho_file) != null) {
 58                const sym = ref.getSymbol(macho_file).?;
 59                try markSymbol(sym, roots, macho_file);
 60            }
 61        }
 62
 63        for (&[_]?Symbol.Index{
 64            obj.entry_index,
 65            obj.dyld_stub_binder_index,
 66            obj.objc_msg_send_index,
 67        }) |index| {
 68            if (index) |idx| {
 69                const ref = obj.getSymbolRef(idx, macho_file);
 70                if (ref.getFile(macho_file) != null) {
 71                    const sym = ref.getSymbol(macho_file).?;
 72                    try markSymbol(sym, roots, macho_file);
 73                }
 74            }
 75        }
 76    }
 77}
 78
 79fn markSymbol(sym: *Symbol, roots: *std.array_list.Managed(*Atom), macho_file: *MachO) !void {
 80    const atom = sym.getAtom(macho_file) orelse return;
 81    if (markAtom(atom)) try roots.append(atom);
 82}
 83
 84fn markAtom(atom: *Atom) bool {
 85    const already_visited = atom.visited.swap(true, .seq_cst);
 86    return atom.isAlive() and !already_visited;
 87}
 88
 89fn mark(roots: []*Atom, objects: []const File.Index, macho_file: *MachO) void {
 90    for (roots) |root| {
 91        markLive(root, macho_file);
 92    }
 93
 94    var loop: bool = true;
 95    while (loop) {
 96        loop = false;
 97
 98        for (objects) |index| {
 99            const file = macho_file.getFile(index).?;
100            for (file.getAtoms()) |atom_index| {
101                const atom = file.getAtom(atom_index) orelse continue;
102                const isec = atom.getInputSection(macho_file);
103                if (isec.isDontDeadStripIfReferencesLive() and
104                    !(mem.eql(u8, isec.sectName(), "__eh_frame") or
105                        mem.eql(u8, isec.sectName(), "__compact_unwind") or
106                        isec.attrs() & macho.S_ATTR_DEBUG != 0) and
107                    !atom.isAlive() and refersLive(atom, macho_file))
108                {
109                    markLive(atom, macho_file);
110                    loop = true;
111                }
112            }
113        }
114    }
115}
116
117fn markLive(atom: *Atom, macho_file: *MachO) void {
118    assert(atom.visited.load(.seq_cst));
119    atom.setAlive(true);
120    track_live_log.debug("{f}marking live atom({d},{s})", .{
121        track_live_level,
122        atom.atom_index,
123        atom.getName(macho_file),
124    });
125
126    if (build_options.enable_logging)
127        track_live_level.incr();
128
129    for (atom.getRelocs(macho_file)) |rel| {
130        const target_atom = switch (rel.tag) {
131            .local => rel.getTargetAtom(atom.*, macho_file),
132            .@"extern" => blk: {
133                const ref = rel.getTargetSymbolRef(atom.*, macho_file);
134                break :blk if (ref.getSymbol(macho_file)) |sym| sym.getAtom(macho_file) else null;
135            },
136        };
137        if (target_atom) |ta| {
138            if (markAtom(ta)) markLive(ta, macho_file);
139        }
140    }
141
142    const file = atom.getFile(macho_file);
143    for (atom.getUnwindRecords(macho_file)) |cu_index| {
144        const cu = file.object.getUnwindRecord(cu_index);
145        const cu_atom = cu.getAtom(macho_file);
146        if (markAtom(cu_atom)) markLive(cu_atom, macho_file);
147
148        if (cu.getLsdaAtom(macho_file)) |lsda| {
149            if (markAtom(lsda)) markLive(lsda, macho_file);
150        }
151        if (cu.getFde(macho_file)) |fde| {
152            const fde_atom = fde.getAtom(macho_file);
153            if (markAtom(fde_atom)) markLive(fde_atom, macho_file);
154
155            if (fde.getLsdaAtom(macho_file)) |lsda| {
156                if (markAtom(lsda)) markLive(lsda, macho_file);
157            }
158        }
159    }
160}
161
162fn refersLive(atom: *Atom, macho_file: *MachO) bool {
163    for (atom.getRelocs(macho_file)) |rel| {
164        const target_atom = switch (rel.tag) {
165            .local => rel.getTargetAtom(atom.*, macho_file),
166            .@"extern" => blk: {
167                const ref = rel.getTargetSymbolRef(atom.*, macho_file);
168                break :blk if (ref.getSymbol(macho_file)) |sym| sym.getAtom(macho_file) else null;
169            },
170        };
171        if (target_atom) |ta| {
172            if (ta.isAlive()) return true;
173        }
174    }
175    return false;
176}
177
178fn prune(objects: []const File.Index, macho_file: *MachO) void {
179    for (objects) |index| {
180        const file = macho_file.getFile(index).?;
181        for (file.getAtoms()) |atom_index| {
182            const atom = file.getAtom(atom_index) orelse continue;
183            if (!atom.visited.load(.seq_cst)) {
184                if (atom.alive.cmpxchgStrong(true, false, .seq_cst, .seq_cst) == null) {
185                    atom.markUnwindRecordsDead(macho_file);
186                }
187            }
188        }
189    }
190}
191
192const Level = struct {
193    value: usize = 0,
194
195    fn incr(self: *@This()) void {
196        self.value += 1;
197    }
198
199    pub fn format(self: *const @This(), w: *Writer) Writer.Error!void {
200        try w.splatByteAll(' ', self.value);
201    }
202};
203
204var track_live_level: Level = .{};
205
206const assert = std.debug.assert;
207const build_options = @import("build_options");
208const log = std.log.scoped(.dead_strip);
209const macho = std.macho;
210const math = std.math;
211const mem = std.mem;
212const trace = @import("../../tracy.zig").trace;
213const track_live_log = std.log.scoped(.dead_strip_track_live);
214const std = @import("std");
215const Writer = std.Io.Writer;
216
217const Allocator = mem.Allocator;
218const Atom = @import("Atom.zig");
219const File = @import("file.zig").File;
220const MachO = @import("../MachO.zig");
221const Symbol = @import("Symbol.zig");