authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-10-04 15:14:45+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-10-16 19:33:04+02:00
log9a80ac0429b34541b91671246833c47143d1e4da
tree692c0ea0e5e0be753f4a35b6899c19294b29c869
parent2ee221328f1aa1e34a37f38ac8426c1e8e8ec204

elf: add garbage collection of sections


4 files changed, 199 insertions(+), 25 deletions(-)

CMakeLists.txt+1
...@@ -595,6 +595,7 @@ set(ZIG_STAGE2_SOURCES...@@ -595,6 +595,7 @@ set(ZIG_STAGE2_SOURCES
595 "${CMAKE_SOURCE_DIR}/src/link/Elf/ZigModule.zig"595 "${CMAKE_SOURCE_DIR}/src/link/Elf/ZigModule.zig"
596 "${CMAKE_SOURCE_DIR}/src/link/Elf/eh_frame.zig"596 "${CMAKE_SOURCE_DIR}/src/link/Elf/eh_frame.zig"
597 "${CMAKE_SOURCE_DIR}/src/link/Elf/file.zig"597 "${CMAKE_SOURCE_DIR}/src/link/Elf/file.zig"
598 "${CMAKE_SOURCE_DIR}/src/link/Elf/gc.zig"
598 "${CMAKE_SOURCE_DIR}/src/link/Elf/synthetic_sections.zig"599 "${CMAKE_SOURCE_DIR}/src/link/Elf/synthetic_sections.zig"
599 "${CMAKE_SOURCE_DIR}/src/link/MachO.zig"600 "${CMAKE_SOURCE_DIR}/src/link/MachO.zig"
600 "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig"601 "${CMAKE_SOURCE_DIR}/src/link/MachO/Archive.zig"
src/link/Elf.zig+36-25
...@@ -53,7 +53,7 @@ phdr_load_tls_data_index: ?u16 = null,...@@ -53,7 +53,7 @@ phdr_load_tls_data_index: ?u16 = null,
53/// The index into the program headers of a PT_LOAD program header with TLS zerofill data.53/// The index into the program headers of a PT_LOAD program header with TLS zerofill data.
54phdr_load_tls_zerofill_index: ?u16 = null,54phdr_load_tls_zerofill_index: ?u16 = null,
5555
56entry_addr: ?u64 = null,56entry_index: ?Symbol.Index = null,
57page_size: u32,57page_size: u32,
58default_sym_version: elf.Elf64_Versym,58default_sym_version: elf.Elf64_Versym,
5959
...@@ -665,7 +665,6 @@ pub fn populateMissingMetadata(self: *Elf) !void {...@@ -665,7 +665,6 @@ pub fn populateMissingMetadata(self: *Elf) !void {
665 .alignment = self.page_size,665 .alignment = self.page_size,
666 .flags = elf.PF_X | elf.PF_R | elf.PF_W,666 .flags = elf.PF_X | elf.PF_R | elf.PF_W,
667 });667 });
668 self.entry_addr = null;
669 }668 }
670669
671 if (self.phdr_got_index == null) {670 if (self.phdr_got_index == null) {
...@@ -1283,9 +1282,29 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node...@@ -1283,9 +1282,29 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
1283 // Any qualifing unresolved symbol will be upgraded to an absolute, weak1282 // Any qualifing unresolved symbol will be upgraded to an absolute, weak
1284 // symbol for potential resolution at load-time.1283 // symbol for potential resolution at load-time.
1285 self.resolveSymbols();1284 self.resolveSymbols();
1286 try self.addLinkerDefinedSymbols();
1287 self.markEhFrameAtomsDead();1285 self.markEhFrameAtomsDead();
1288 self.markImportsExports();1286 self.markImportsExports();
1287
1288 // Look for entry address in objects if not set by the incremental compiler.
1289 if (self.entry_index == null) {
1290 const entry: ?[]const u8 = entry: {
1291 if (self.base.options.entry) |entry| break :entry entry;
1292 if (!self.isDynLib()) break :entry "_start";
1293 break :entry null;
1294 };
1295 self.entry_index = if (entry) |name| self.globalByName(name) else null;
1296 }
1297
1298 const gc_sections = self.base.options.gc_sections orelse false;
1299 if (gc_sections) {
1300 try gc.gcAtoms(self);
1301
1302 // if (self.base.options.print_gc_sections) {
1303 // try gc.dumpPrunedAtoms(self);
1304 // }
1305 }
1306
1307 try self.addLinkerDefinedSymbols();
1289 self.claimUnresolved();1308 self.claimUnresolved();
12901309
1291 // Scan and create missing synthetic entries such as GOT indirection.1310 // Scan and create missing synthetic entries such as GOT indirection.
...@@ -1310,19 +1329,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node...@@ -1310,19 +1329,6 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
1310 state_log.debug("{}", .{self.dumpState()});1329 state_log.debug("{}", .{self.dumpState()});
1311 }1330 }
13121331
1313 // Look for entry address in objects if not set by the incremental compiler.
1314 if (self.entry_addr == null) {
1315 const entry: ?[]const u8 = entry: {
1316 if (self.base.options.entry) |entry| break :entry entry;
1317 if (!self.isDynLib()) break :entry "_start";
1318 break :entry null;
1319 };
1320 self.entry_addr = if (entry) |name| entry_addr: {
1321 const global_index = self.globalByName(name) orelse break :entry_addr null;
1322 break :entry_addr self.symbol(global_index).value;
1323 } else null;
1324 }
1325
1326 // Beyond this point, everything has been allocated a virtual address and we can resolve1332 // Beyond this point, everything has been allocated a virtual address and we can resolve
1327 // the relocations, and commit objects to file.1333 // the relocations, and commit objects to file.
1328 if (self.zig_module_index) |index| {1334 if (self.zig_module_index) |index| {
...@@ -1538,7 +1544,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node...@@ -1538,7 +1544,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
1538 try self.writeAtoms();1544 try self.writeAtoms();
1539 try self.writeSyntheticSections();1545 try self.writeSyntheticSections();
15401546
1541 if (self.entry_addr == null and self.base.options.effectiveOutputMode() == .Exe) {1547 if (self.entry_index == null and self.base.options.effectiveOutputMode() == .Exe) {
1542 log.debug("flushing. no_entry_point_found = true", .{});1548 log.debug("flushing. no_entry_point_found = true", .{});
1543 self.error_flags.no_entry_point_found = true;1549 self.error_flags.no_entry_point_found = true;
1544 } else {1550 } else {
...@@ -2580,7 +2586,7 @@ fn writeHeader(self: *Elf) !void {...@@ -2580,7 +2586,7 @@ fn writeHeader(self: *Elf) !void {
2580 mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian);2586 mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian);
2581 index += 4;2587 index += 4;
25822588
2583 const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?;2589 const e_entry = if (self.entry_index) |entry_index| self.symbol(entry_index).value else 0;
25842590
2585 // TODO2591 // TODO
2586 const phdr_table_offset = if (self.phdr_table_index) |ind|2592 const phdr_table_offset = if (self.phdr_table_index) |ind|
...@@ -3199,13 +3205,7 @@ pub fn updateDeclExports(...@@ -3199,13 +3205,7 @@ pub fn updateDeclExports(
3199 }3205 }
3200 const stb_bits: u8 = switch (exp.opts.linkage) {3206 const stb_bits: u8 = switch (exp.opts.linkage) {
3201 .Internal => elf.STB_LOCAL,3207 .Internal => elf.STB_LOCAL,
3202 .Strong => blk: {3208 .Strong => elf.STB_GLOBAL,
3203 const entry_name = self.base.options.entry orelse "_start";
3204 if (mem.eql(u8, exp_name, entry_name)) {
3205 self.entry_addr = decl_sym.value;
3206 }
3207 break :blk elf.STB_GLOBAL;
3208 },
3209 .Weak => elf.STB_WEAK,3209 .Weak => elf.STB_WEAK,
3210 .LinkOnce => {3210 .LinkOnce => {
3211 try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);3211 try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
...@@ -4693,6 +4693,16 @@ fn calcNumIRelativeRelocs(self: *Elf) usize {...@@ -4693,6 +4693,16 @@ fn calcNumIRelativeRelocs(self: *Elf) usize {
4693 return count;4693 return count;
4694}4694}
46954695
4696pub fn isCIdentifier(name: []const u8) bool {
4697 if (name.len == 0) return false;
4698 const first_c = name[0];
4699 if (!std.ascii.isAlphabetic(first_c) and first_c != '_') return false;
4700 for (name[1..]) |c| {
4701 if (!std.ascii.isAlphanumeric(c) and c != '_') return false;
4702 }
4703 return true;
4704}
4705
4696pub fn atom(self: *Elf, atom_index: Atom.Index) ?*Atom {4706pub fn atom(self: *Elf, atom_index: Atom.Index) ?*Atom {
4697 if (atom_index == 0) return null;4707 if (atom_index == 0) return null;
4698 assert(atom_index < self.atoms.items.len);4708 assert(atom_index < self.atoms.items.len);
...@@ -5202,6 +5212,7 @@ const mem = std.mem;...@@ -5202,6 +5212,7 @@ const mem = std.mem;
52025212
5203const codegen = @import("../codegen.zig");5213const codegen = @import("../codegen.zig");
5204const eh_frame = @import("Elf/eh_frame.zig");5214const eh_frame = @import("Elf/eh_frame.zig");
5215const gc = @import("Elf/gc.zig");
5205const glibc = @import("../glibc.zig");5216const glibc = @import("../glibc.zig");
5206const link = @import("../link.zig");5217const link = @import("../link.zig");
5207const lldMain = @import("../main.zig").lldMain;5218const lldMain = @import("../main.zig").lldMain;
src/link/Elf/gc.zig created+161
...@@ -0,0 +1,161 @@
1pub fn gcAtoms(elf_file: *Elf) !void {
2 var roots = std.ArrayList(*Atom).init(elf_file.base.allocator);
3 defer roots.deinit();
4 try collectRoots(&roots, elf_file);
5 mark(roots, elf_file);
6 prune(elf_file);
7}
8
9fn collectRoots(roots: *std.ArrayList(*Atom), elf_file: *Elf) !void {
10 if (elf_file.entry_index) |index| {
11 const global = elf_file.symbol(index);
12 try markSymbol(global, roots, elf_file);
13 }
14
15 for (elf_file.objects.items) |index| {
16 for (elf_file.file(index).?.object.globals()) |global_index| {
17 const global = elf_file.symbol(global_index);
18 if (global.file(elf_file)) |file| {
19 if (file.index() == index and global.flags.@"export")
20 try markSymbol(global, roots, elf_file);
21 }
22 }
23 }
24
25 for (elf_file.objects.items) |index| {
26 const object = elf_file.file(index).?.object;
27
28 for (object.atoms.items) |atom_index| {
29 const atom = elf_file.atom(atom_index) orelse continue;
30 if (!atom.flags.alive) continue;
31
32 const shdr = atom.inputShdr(elf_file);
33 const name = atom.name(elf_file);
34 const is_gc_root = blk: {
35 if (shdr.sh_flags & elf.SHF_GNU_RETAIN != 0) break :blk true;
36 if (shdr.sh_type == elf.SHT_NOTE) break :blk true;
37 if (shdr.sh_type == elf.SHT_PREINIT_ARRAY) break :blk true;
38 if (shdr.sh_type == elf.SHT_INIT_ARRAY) break :blk true;
39 if (shdr.sh_type == elf.SHT_FINI_ARRAY) break :blk true;
40 if (mem.startsWith(u8, name, ".ctors")) break :blk true;
41 if (mem.startsWith(u8, name, ".dtors")) break :blk true;
42 if (mem.startsWith(u8, name, ".init")) break :blk true;
43 if (mem.startsWith(u8, name, ".fini")) break :blk true;
44 if (Elf.isCIdentifier(name)) break :blk true;
45 break :blk false;
46 };
47 if (is_gc_root and markAtom(atom)) try roots.append(atom);
48 if (shdr.sh_flags & elf.SHF_ALLOC == 0) atom.flags.visited = true;
49 }
50
51 // Mark every atom referenced by CIE as alive.
52 for (object.cies.items) |cie| {
53 for (cie.relocs(elf_file)) |rel| {
54 const sym = elf_file.symbol(object.symbols.items[rel.r_sym()]);
55 try markSymbol(sym, roots, elf_file);
56 }
57 }
58 }
59}
60
61fn markSymbol(sym: *Symbol, roots: *std.ArrayList(*Atom), elf_file: *Elf) !void {
62 const atom = sym.atom(elf_file) orelse return;
63 if (markAtom(atom)) try roots.append(atom);
64}
65
66fn markAtom(atom: *Atom) bool {
67 const already_visited = atom.flags.visited;
68 atom.flags.visited = true;
69 return atom.flags.alive and !already_visited;
70}
71
72fn markLive(atom: *Atom, elf_file: *Elf) void {
73 if (@import("build_options").enable_logging) track_live_level.incr();
74
75 assert(atom.flags.visited);
76 const object = atom.file(elf_file).?.object;
77
78 for (atom.fdes(elf_file)) |fde| {
79 for (fde.relocs(elf_file)[1..]) |rel| {
80 const target_sym = elf_file.symbol(object.symbols.items[rel.r_sym()]);
81 const target_atom = target_sym.atom(elf_file) orelse continue;
82 target_atom.flags.alive = true;
83 gc_track_live_log.debug("{}marking live atom({d})", .{ track_live_level, target_atom.atom_index });
84 if (markAtom(target_atom)) markLive(target_atom, elf_file);
85 }
86 }
87
88 for (atom.relocs(elf_file)) |rel| {
89 const target_sym = elf_file.symbol(object.symbols.items[rel.r_sym()]);
90 const target_atom = target_sym.atom(elf_file) orelse continue;
91 target_atom.flags.alive = true;
92 gc_track_live_log.debug("{}marking live atom({d})", .{ track_live_level, target_atom.atom_index });
93 if (markAtom(target_atom)) markLive(target_atom, elf_file);
94 }
95}
96
97fn mark(roots: std.ArrayList(*Atom), elf_file: *Elf) void {
98 for (roots.items) |root| {
99 gc_track_live_log.debug("root atom({d})", .{root.atom_index});
100 markLive(root, elf_file);
101 }
102}
103
104fn prune(elf_file: *Elf) void {
105 for (elf_file.objects.items) |index| {
106 for (elf_file.file(index).?.object.atoms.items) |atom_index| {
107 const atom = elf_file.atom(atom_index) orelse continue;
108 if (atom.flags.alive and !atom.flags.visited) {
109 atom.flags.alive = false;
110 atom.markFdesDead(elf_file);
111 }
112 }
113 }
114}
115
116pub fn dumpPrunedAtoms(elf_file: *Elf) !void {
117 const stderr = std.io.getStdErr().writer();
118 for (elf_file.objects.items) |index| {
119 for (elf_file.file(index).?.object.atoms.items) |atom_index| {
120 const atom = elf_file.atom(atom_index) orelse continue;
121 if (!atom.flags.alive)
122 // TODO should we simply print to stderr?
123 try stderr.print("link: removing unused section '{s}' in file '{}'\n", .{
124 atom.name(elf_file),
125 atom.file(elf_file).?.fmtPath(),
126 });
127 }
128 }
129}
130
131const Level = struct {
132 value: usize = 0,
133
134 fn incr(self: *@This()) void {
135 self.value += 1;
136 }
137
138 pub fn format(
139 self: *const @This(),
140 comptime unused_fmt_string: []const u8,
141 options: std.fmt.FormatOptions,
142 writer: anytype,
143 ) !void {
144 _ = unused_fmt_string;
145 _ = options;
146 try writer.writeByteNTimes(' ', self.value);
147 }
148};
149
150var track_live_level: Level = .{};
151
152const std = @import("std");
153const assert = std.debug.assert;
154const elf = std.elf;
155const gc_track_live_log = std.log.scoped(.gc_track_live);
156const mem = std.mem;
157
158const Allocator = mem.Allocator;
159const Atom = @import("Atom.zig");
160const Elf = @import("../Elf.zig");
161const Symbol = @import("Symbol.zig");
test/link/elf.zig+1
...@@ -60,6 +60,7 @@ fn testGcSections(b: *Build, opts: Options) *Step {...@@ -60,6 +60,7 @@ fn testGcSections(b: *Build, opts: Options) *Step {
60 \\}60 \\}
61 );61 );
62 obj.link_function_sections = true;62 obj.link_function_sections = true;
63 obj.link_data_sections = true;
63 obj.is_linking_libc = true;64 obj.is_linking_libc = true;
64 obj.is_linking_libcpp = true;65 obj.is_linking_libcpp = true;
6566