master
  1pub fn gcAtoms(elf_file: *Elf) !void {
  2    const comp = elf_file.base.comp;
  3    const gpa = comp.gpa;
  4    var roots = std.array_list.Managed(*Atom).init(gpa);
  5    defer roots.deinit();
  6    try collectRoots(&roots, elf_file);
  7    mark(roots, elf_file);
  8    prune(elf_file);
  9}
 10
 11fn collectRoots(roots: *std.array_list.Managed(*Atom), elf_file: *Elf) !void {
 12    if (elf_file.linkerDefinedPtr()) |obj| {
 13        if (obj.entrySymbol(elf_file)) |sym| {
 14            try markSymbol(sym, roots, elf_file);
 15        }
 16    }
 17
 18    if (elf_file.zigObjectPtr()) |zo| {
 19        for (0..zo.global_symbols.items.len) |i| {
 20            const ref = zo.resolveSymbol(@intCast(i | ZigObject.global_symbol_bit), elf_file);
 21            const sym = elf_file.symbol(ref) orelse continue;
 22            if (sym.file(elf_file).?.index() != zo.index) continue;
 23            if (sym.flags.@"export") {
 24                try markSymbol(sym, roots, elf_file);
 25            }
 26        }
 27    }
 28
 29    for (elf_file.objects.items) |index| {
 30        const object = elf_file.file(index).?.object;
 31        for (0..object.globals().len) |i| {
 32            const ref = object.resolveSymbol(@intCast(i), elf_file);
 33            const sym = elf_file.symbol(ref) orelse continue;
 34            if (sym.file(elf_file).?.index() != object.index) continue;
 35            if (sym.flags.@"export") {
 36                try markSymbol(sym, roots, elf_file);
 37            }
 38        }
 39    }
 40
 41    const atomRoots = struct {
 42        fn atomRoots(file: File, rs: anytype, ef: *Elf) !void {
 43            for (file.atoms()) |atom_index| {
 44                const atom = file.atom(atom_index) orelse continue;
 45                if (!atom.alive) continue;
 46
 47                const shdr = atom.inputShdr(ef);
 48                const name = atom.name(ef);
 49                const is_gc_root = blk: {
 50                    if (shdr.sh_flags & elf.SHF_GNU_RETAIN != 0) break :blk true;
 51                    if (shdr.sh_type == elf.SHT_NOTE) break :blk true;
 52                    if (shdr.sh_type == elf.SHT_PREINIT_ARRAY) break :blk true;
 53                    if (shdr.sh_type == elf.SHT_INIT_ARRAY) break :blk true;
 54                    if (shdr.sh_type == elf.SHT_FINI_ARRAY) break :blk true;
 55                    if (mem.startsWith(u8, name, ".ctors")) break :blk true;
 56                    if (mem.startsWith(u8, name, ".dtors")) break :blk true;
 57                    if (mem.startsWith(u8, name, ".init")) break :blk true;
 58                    if (mem.startsWith(u8, name, ".fini")) break :blk true;
 59                    if (Elf.isCIdentifier(name)) break :blk true;
 60                    break :blk false;
 61                };
 62                if (is_gc_root and markAtom(atom)) try rs.append(atom);
 63                if (shdr.sh_flags & elf.SHF_ALLOC == 0) atom.visited = true;
 64            }
 65
 66            // Mark every atom referenced by CIE as alive.
 67            for (file.cies()) |cie| {
 68                for (cie.relocs(ef)) |rel| {
 69                    const ref = file.resolveSymbol(rel.r_sym(), ef);
 70                    const sym = ef.symbol(ref) orelse continue;
 71                    try markSymbol(sym, rs, ef);
 72                }
 73            }
 74        }
 75    }.atomRoots;
 76
 77    if (elf_file.zigObjectPtr()) |zo| {
 78        try atomRoots(zo.asFile(), roots, elf_file);
 79    }
 80    for (elf_file.objects.items) |index| {
 81        try atomRoots(elf_file.file(index).?, roots, elf_file);
 82    }
 83}
 84
 85fn markSymbol(sym: *Symbol, roots: *std.array_list.Managed(*Atom), elf_file: *Elf) !void {
 86    if (sym.mergeSubsection(elf_file)) |msub| {
 87        msub.alive = true;
 88        return;
 89    }
 90    const atom = sym.atom(elf_file) orelse return;
 91    if (markAtom(atom)) try roots.append(atom);
 92}
 93
 94fn markAtom(atom: *Atom) bool {
 95    const already_visited = atom.visited;
 96    atom.visited = true;
 97    return atom.alive and !already_visited;
 98}
 99
100fn markLive(atom: *Atom, elf_file: *Elf) void {
101    if (@import("build_options").enable_logging) track_live_level.incr();
102
103    assert(atom.visited);
104    const file = atom.file(elf_file).?;
105
106    switch (file) {
107        .object => |object| {
108            for (atom.fdes(object)) |fde| {
109                for (fde.relocs(object)[1..]) |rel| {
110                    const ref = file.resolveSymbol(rel.r_sym(), elf_file);
111                    const target_sym = elf_file.symbol(ref) orelse continue;
112                    const target_atom = target_sym.atom(elf_file) orelse continue;
113                    target_atom.alive = true;
114                    gc_track_live_log.debug("{f}marking live atom({d})", .{ track_live_level, target_atom.atom_index });
115                    if (markAtom(target_atom)) markLive(target_atom, elf_file);
116                }
117            }
118        },
119        else => {},
120    }
121
122    for (atom.relocs(elf_file)) |rel| {
123        const ref = file.resolveSymbol(rel.r_sym(), elf_file);
124        const target_sym = elf_file.symbol(ref) orelse continue;
125        if (target_sym.mergeSubsection(elf_file)) |msub| {
126            msub.alive = true;
127            continue;
128        }
129        const target_atom = target_sym.atom(elf_file) orelse continue;
130        target_atom.alive = true;
131        gc_track_live_log.debug("{f}marking live atom({d})", .{ track_live_level, target_atom.atom_index });
132        if (markAtom(target_atom)) markLive(target_atom, elf_file);
133    }
134}
135
136fn mark(roots: std.array_list.Managed(*Atom), elf_file: *Elf) void {
137    for (roots.items) |root| {
138        gc_track_live_log.debug("root atom({d})", .{root.atom_index});
139        markLive(root, elf_file);
140    }
141}
142
143fn pruneInFile(file: File) void {
144    for (file.atoms()) |atom_index| {
145        const atom = file.atom(atom_index) orelse continue;
146        if (atom.alive and !atom.visited) {
147            atom.alive = false;
148            switch (file) {
149                .object => |object| atom.markFdesDead(object),
150                else => {},
151            }
152        }
153    }
154}
155
156fn prune(elf_file: *Elf) void {
157    if (elf_file.zigObjectPtr()) |zo| {
158        pruneInFile(zo.asFile());
159    }
160    for (elf_file.objects.items) |index| {
161        pruneInFile(elf_file.file(index).?);
162    }
163}
164
165const Level = struct {
166    value: usize = 0,
167
168    fn incr(self: *@This()) void {
169        self.value += 1;
170    }
171
172    pub fn format(self: *const @This(), w: *std.Io.Writer) std.Io.Writer.Error!void {
173        try w.splatByteAll(' ', self.value);
174    }
175};
176
177var track_live_level: Level = .{};
178
179const std = @import("std");
180const assert = std.debug.assert;
181const elf = std.elf;
182const gc_track_live_log = std.log.scoped(.gc_track_live);
183const mem = std.mem;
184
185const Allocator = mem.Allocator;
186const Atom = @import("Atom.zig");
187const Elf = @import("../Elf.zig");
188const File = @import("file.zig").File;
189const Symbol = @import("Symbol.zig");
190const ZigObject = @import("ZigObject.zig");