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");