| 1 | pub 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 | |
| 17 | fn 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 | |
| 79 | fn 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 | |
| 84 | fn markAtom(atom: *Atom) bool { |
| 85 | const already_visited = atom.visited.swap(true, .seq_cst); |
| 86 | return atom.isAlive() and !already_visited; |
| 87 | } |
| 88 | |
| 89 | fn 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 | |
| 117 | fn 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 | |
| 162 | fn 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 | |
| 178 | fn 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 | |
| 192 | const 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 | |
| 204 | var track_live_level: Level = .{}; |
| 205 | |
| 206 | const assert = std.debug.assert; |
| 207 | const build_options = @import("build_options"); |
| 208 | const log = std.log.scoped(.dead_strip); |
| 209 | const macho = std.macho; |
| 210 | const math = std.math; |
| 211 | const mem = std.mem; |
| 212 | const trace = @import("../../tracy.zig").trace; |
| 213 | const track_live_log = std.log.scoped(.dead_strip_track_live); |
| 214 | const std = @import("std"); |
| 215 | const Writer = std.Io.Writer; |
| 216 | |
| 217 | const Allocator = mem.Allocator; |
| 218 | const Atom = @import("Atom.zig"); |
| 219 | const File = @import("file.zig").File; |
| 220 | const MachO = @import("../MachO.zig"); |
| 221 | const Symbol = @import("Symbol.zig"); |