| author | |
| committer | |
| log | 9a80ac0429b34541b91671246833c47143d1e4da |
| tree | 692c0ea0e5e0be753f4a35b6899c19294b29c869 |
| parent | 2ee221328f1aa1e34a37f38ac8426c1e8e8ec204 |
4 files changed, 199 insertions(+), 25 deletions(-)
CMakeLists.txt+1| ... | ... | @@ -595,6 +595,7 @@ set(ZIG_STAGE2_SOURCES |
| 595 | 595 | "${CMAKE_SOURCE_DIR}/src/link/Elf/ZigModule.zig" |
| 596 | 596 | "${CMAKE_SOURCE_DIR}/src/link/Elf/eh_frame.zig" |
| 597 | 597 | "${CMAKE_SOURCE_DIR}/src/link/Elf/file.zig" |
| 598 | "${CMAKE_SOURCE_DIR}/src/link/Elf/gc.zig" | |
| 598 | 599 | "${CMAKE_SOURCE_DIR}/src/link/Elf/synthetic_sections.zig" |
| 599 | 600 | "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" |
| 600 | 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 | 53 | /// The index into the program headers of a PT_LOAD program header with TLS zerofill data. |
| 54 | 54 | phdr_load_tls_zerofill_index: ?u16 = null, |
| 55 | 55 | |
| 56 | entry_addr: ?u64 = null, | |
| 56 | entry_index: ?Symbol.Index = null, | |
| 57 | 57 | page_size: u32, |
| 58 | 58 | default_sym_version: elf.Elf64_Versym, |
| 59 | 59 | |
| ... | ... | @@ -665,7 +665,6 @@ pub fn populateMissingMetadata(self: *Elf) !void { |
| 665 | 665 | .alignment = self.page_size, |
| 666 | 666 | .flags = elf.PF_X | elf.PF_R | elf.PF_W, |
| 667 | 667 | }); |
| 668 | self.entry_addr = null; | |
| 669 | 668 | } |
| 670 | 669 | |
| 671 | 670 | if (self.phdr_got_index == null) { |
| ... | ... | @@ -1283,9 +1282,29 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node |
| 1283 | 1282 | // Any qualifing unresolved symbol will be upgraded to an absolute, weak |
| 1284 | 1283 | // symbol for potential resolution at load-time. |
| 1285 | 1284 | self.resolveSymbols(); |
| 1286 | try self.addLinkerDefinedSymbols(); | |
| 1287 | 1285 | self.markEhFrameAtomsDead(); |
| 1288 | 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 | 1308 | self.claimUnresolved(); |
| 1290 | 1309 | |
| 1291 | 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 | 1329 | state_log.debug("{}", .{self.dumpState()}); |
| 1311 | 1330 | } |
| 1312 | 1331 | |
| 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 | 1332 | // Beyond this point, everything has been allocated a virtual address and we can resolve |
| 1327 | 1333 | // the relocations, and commit objects to file. |
| 1328 | 1334 | if (self.zig_module_index) |index| { |
| ... | ... | @@ -1538,7 +1544,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node |
| 1538 | 1544 | try self.writeAtoms(); |
| 1539 | 1545 | try self.writeSyntheticSections(); |
| 1540 | 1546 | |
| 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 | 1548 | log.debug("flushing. no_entry_point_found = true", .{}); |
| 1543 | 1549 | self.error_flags.no_entry_point_found = true; |
| 1544 | 1550 | } else { |
| ... | ... | @@ -2580,7 +2586,7 @@ fn writeHeader(self: *Elf) !void { |
| 2580 | 2586 | mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian); |
| 2581 | 2587 | index += 4; |
| 2582 | 2588 | |
| 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; | |
| 2584 | 2590 | |
| 2585 | 2591 | // TODO |
| 2586 | 2592 | const phdr_table_offset = if (self.phdr_table_index) |ind| |
| ... | ... | @@ -3199,13 +3205,7 @@ pub fn updateDeclExports( |
| 3199 | 3205 | } |
| 3200 | 3206 | const stb_bits: u8 = switch (exp.opts.linkage) { |
| 3201 | 3207 | .Internal => elf.STB_LOCAL, |
| 3202 | .Strong => blk: { | |
| 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 | }, | |
| 3208 | .Strong => elf.STB_GLOBAL, | |
| 3209 | 3209 | .Weak => elf.STB_WEAK, |
| 3210 | 3210 | .LinkOnce => { |
| 3211 | 3211 | try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1); |
| ... | ... | @@ -4693,6 +4693,16 @@ fn calcNumIRelativeRelocs(self: *Elf) usize { |
| 4693 | 4693 | return count; |
| 4694 | 4694 | } |
| 4695 | 4695 | |
| 4696 | pub 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 | ||
| 4696 | 4706 | pub fn atom(self: *Elf, atom_index: Atom.Index) ?*Atom { |
| 4697 | 4707 | if (atom_index == 0) return null; |
| 4698 | 4708 | assert(atom_index < self.atoms.items.len); |
| ... | ... | @@ -5202,6 +5212,7 @@ const mem = std.mem; |
| 5202 | 5212 | |
| 5203 | 5213 | const codegen = @import("../codegen.zig"); |
| 5204 | 5214 | const eh_frame = @import("Elf/eh_frame.zig"); |
| 5215 | const gc = @import("Elf/gc.zig"); | |
| 5205 | 5216 | const glibc = @import("../glibc.zig"); |
| 5206 | 5217 | const link = @import("../link.zig"); |
| 5207 | 5218 | const lldMain = @import("../main.zig").lldMain; |
src/link/Elf/gc.zig created+161| ... | ... | @@ -0,0 +1,161 @@ |
| 1 | pub 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 | ||
| 9 | fn 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 | ||
| 61 | fn 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 | ||
| 66 | fn 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 | ||
| 72 | fn 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 | ||
| 97 | fn 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 | ||
| 104 | fn 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 | ||
| 116 | pub 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 | ||
| 131 | const 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 | ||
| 150 | var track_live_level: Level = .{}; | |
| 151 | ||
| 152 | const std = @import("std"); | |
| 153 | const assert = std.debug.assert; | |
| 154 | const elf = std.elf; | |
| 155 | const gc_track_live_log = std.log.scoped(.gc_track_live); | |
| 156 | const mem = std.mem; | |
| 157 | ||
| 158 | const Allocator = mem.Allocator; | |
| 159 | const Atom = @import("Atom.zig"); | |
| 160 | const Elf = @import("../Elf.zig"); | |
| 161 | const Symbol = @import("Symbol.zig"); |
test/link/elf.zig+1| ... | ... | @@ -60,6 +60,7 @@ fn testGcSections(b: *Build, opts: Options) *Step { |
| 60 | 60 | \\} |
| 61 | 61 | ); |
| 62 | 62 | obj.link_function_sections = true; |
| 63 | obj.link_data_sections = true; | |
| 63 | 64 | obj.is_linking_libc = true; |
| 64 | 65 | obj.is_linking_libcpp = true; |
| 65 | 66 |