diff options
| author | Jakub Konka <kubkon@jakubkonka.com> | 2022-07-04 20:40:10 +0200 |
|---|---|---|
| committer | Jakub Konka <kubkon@jakubkonka.com> | 2022-07-22 16:58:20 +0200 |
| commit | 03feea0fb200f273dd74bf778997e6a6bead86cc (patch) | |
| tree | c439ed641e01ec4d860abb181ee585a2a287494c /src/link/MachO.zig | |
| parent | d042b88c112aa919386bc76294225d4f7bd9a7b3 (diff) | |
| download | zig-03feea0fb200f273dd74bf778997e6a6bead86cc.tar.gz zig-03feea0fb200f273dd74bf778997e6a6bead86cc.zip | |
macho: split section into subsections if requested and/or possible
Diffstat (limited to 'src/link/MachO.zig')
| -rw-r--r-- | src/link/MachO.zig | 291 |
1 files changed, 264 insertions, 27 deletions
diff --git a/src/link/MachO.zig b/src/link/MachO.zig index c5ed6cb6ac..38624ae152 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -57,6 +57,8 @@ const SystemLib = struct { weak: bool = false, }; +const N_DESC_GCED: u16 = @bitCast(u16, @as(i16, -1)); + base: File, /// If this is not null, an object file is created by LLVM and linked with LLD afterwards. @@ -256,6 +258,8 @@ unnamed_const_atoms: UnnamedConstTable = .{}, /// TODO consolidate this. decls: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, ?MatchingSection) = .{}, +gc_roots: std.AutoHashMapUnmanaged(*Atom, void) = .{}, + const Entry = struct { target: Atom.Relocation.Target, atom: *Atom, @@ -1165,6 +1169,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No const use_llvm = build_options.have_llvm and self.base.options.use_llvm; if (use_llvm or use_stage1) { + self.logAtoms(); + try self.gcAtoms(); try self.pruneAndSortSections(); try self.allocateSegments(); try self.allocateLocals(); @@ -1173,9 +1179,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No try self.allocateSpecialSymbols(); try self.allocateGlobals(); - if (build_options.enable_logging) { + if (build_options.enable_logging or true) { self.logSymtab(); self.logSectionOrdinals(); + self.logAtoms(); } if (use_llvm or use_stage1) { @@ -2177,6 +2184,7 @@ pub fn createEmptyAtom(self: *MachO, local_sym_index: u32, size: u64, alignment: try atom.code.resize(self.base.allocator, size_usize); mem.set(u8, atom.code.items, 0); + try self.atom_by_index_table.putNoClobber(self.base.allocator, local_sym_index, atom); try self.managed_atoms.append(self.base.allocator, atom); return atom; } @@ -3298,12 +3306,7 @@ fn resolveDyldStubBinder(self: *MachO) !void { const vaddr = try self.allocateAtom(atom, @sizeOf(u64), 8, match); log.debug("allocated {s} atom at 0x{x}", .{ self.getString(sym.n_strx), vaddr }); atom_sym.n_value = vaddr; - } else { - const seg = &self.load_commands.items[self.data_const_segment_cmd_index.?].segment; - const sect = &seg.sections.items[self.got_section_index.?]; - sect.size += atom.size; - try self.addAtomToSection(atom, match); - } + } else try self.addAtomToSection(atom, match); atom_sym.n_sect = @intCast(u8, self.section_ordinals.getIndex(match).? + 1); } @@ -3564,6 +3567,7 @@ pub fn deinit(self: *MachO) void { self.symbol_resolver.deinit(self.base.allocator); self.unresolved.deinit(self.base.allocator); self.tentatives.deinit(self.base.allocator); + self.gc_roots.deinit(self.base.allocator); for (self.objects.items) |*object| { object.deinit(self.base.allocator); @@ -3916,7 +3920,6 @@ pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: Modu const required_alignment = typed_value.ty.abiAlignment(self.base.options.target); const local_sym_index = try self.allocateLocalSymbol(); const atom = try self.createEmptyAtom(local_sym_index, @sizeOf(u64), math.log2(required_alignment)); - try self.atom_by_index_table.putNoClobber(self.base.allocator, local_sym_index, atom); const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .none, .{ .parent_atom_index = local_sym_index, @@ -5597,7 +5600,7 @@ fn pruneAndSortSectionsInSegment(self: *MachO, maybe_seg_id: *?u16, indices: []* const old_idx = maybe_index.* orelse continue; const sect = sections[old_idx]; if (sect.size == 0) { - log.debug("pruning section {s},{s}", .{ sect.segName(), sect.sectName() }); + log.warn("pruning section {s},{s}", .{ sect.segName(), sect.sectName() }); maybe_index.* = null; seg.inner.cmdsize -= @sizeOf(macho.section_64); seg.inner.nsects -= 1; @@ -5630,7 +5633,7 @@ fn pruneAndSortSectionsInSegment(self: *MachO, maybe_seg_id: *?u16, indices: []* if (seg.inner.nsects == 0 and !mem.eql(u8, "__TEXT", seg.inner.segName())) { // Segment has now become empty, so mark it as such - log.debug("marking segment {s} as dead", .{seg.inner.segName()}); + log.warn("marking segment {s} as dead", .{seg.inner.segName()}); seg.inner.cmd = @intToEnum(macho.LC, 0); maybe_seg_id.* = null; } @@ -5712,6 +5715,189 @@ fn pruneAndSortSections(self: *MachO) !void { self.sections_order_dirty = false; } +fn gcAtoms(self: *MachO) !void { + const dead_strip = self.base.options.gc_sections orelse false; + if (!dead_strip) return; + + // Add all exports as GC roots + for (self.globals.items) |sym| { + if (sym.n_type == 0) continue; + const resolv = self.symbol_resolver.get(sym.n_strx).?; + assert(resolv.where == .global); + const gc_root = self.atom_by_index_table.get(resolv.local_sym_index) orelse { + log.warn("skipping {s}", .{self.getString(sym.n_strx)}); + continue; + }; + _ = try self.gc_roots.getOrPut(self.base.allocator, gc_root); + } + + // if (self.tlv_ptrs_section_index) |sect| { + // var atom = self.atoms.get(.{ + // .seg = self.data_segment_cmd_index.?, + // .sect = sect, + // }).?; + + // while (true) { + // _ = try self.gc_roots.getOrPut(self.base.allocator, atom); + + // if (atom.prev) |prev| { + // atom = prev; + // } else break; + // } + // } + + // Add any atom targeting an import as GC root + var atoms_it = self.atoms.iterator(); + while (atoms_it.next()) |entry| { + var atom = entry.value_ptr.*; + + while (true) { + for (atom.relocs.items) |rel| { + if ((try Atom.getTargetAtom(rel, self)) == null) switch (rel.target) { + .local => {}, + .global => |n_strx| { + const resolv = self.symbol_resolver.get(n_strx).?; + switch (resolv.where) { + .global => {}, + .undef => { + _ = try self.gc_roots.getOrPut(self.base.allocator, atom); + break; + }, + } + }, + }; + } + + if (atom.prev) |prev| { + atom = prev; + } else break; + } + } + + var stack = std.ArrayList(*Atom).init(self.base.allocator); + defer stack.deinit(); + try stack.ensureUnusedCapacity(self.gc_roots.count()); + + var retained = std.AutoHashMap(*Atom, void).init(self.base.allocator); + defer retained.deinit(); + try retained.ensureUnusedCapacity(self.gc_roots.count()); + + log.warn("GC roots:", .{}); + var gc_roots_it = self.gc_roots.keyIterator(); + while (gc_roots_it.next()) |gc_root| { + self.logAtom(gc_root.*); + + stack.appendAssumeCapacity(gc_root.*); + retained.putAssumeCapacityNoClobber(gc_root.*, {}); + } + + log.warn("walking tree...", .{}); + while (stack.popOrNull()) |source_atom| { + for (source_atom.relocs.items) |rel| { + if (try Atom.getTargetAtom(rel, self)) |target_atom| { + const gop = try retained.getOrPut(target_atom); + if (!gop.found_existing) { + log.warn(" RETAINED ATOM(%{d}) -> ATOM(%{d})", .{ + source_atom.local_sym_index, + target_atom.local_sym_index, + }); + try stack.append(target_atom); + } + } + } + } + + atoms_it = self.atoms.iterator(); + while (atoms_it.next()) |entry| { + const match = entry.key_ptr.*; + + if (self.text_segment_cmd_index) |seg| { + if (seg == match.seg) { + if (self.eh_frame_section_index) |sect| { + if (sect == match.sect) continue; + } + } + } + + if (self.data_segment_cmd_index) |seg| { + if (seg == match.seg) { + if (self.rustc_section_index) |sect| { + if (sect == match.sect) continue; + } + } + } + + const seg = &self.load_commands.items[match.seg].segment; + const sect = &seg.sections.items[match.sect]; + var atom = entry.value_ptr.*; + + log.warn("GCing atoms in {s},{s}", .{ sect.segName(), sect.sectName() }); + + while (true) { + const orig_prev = atom.prev; + + if (!retained.contains(atom)) { + // Dead atom; remove. + log.warn(" DEAD ATOM(%{d})", .{atom.local_sym_index}); + + const sym = &self.locals.items[atom.local_sym_index]; + sym.n_desc = N_DESC_GCED; + + if (self.symbol_resolver.getPtr(sym.n_strx)) |resolv| { + if (resolv.local_sym_index == atom.local_sym_index) { + const global = &self.globals.items[resolv.where_index]; + global.n_desc = N_DESC_GCED; + } + } + + for (self.got_entries.items) |got_entry| { + if (got_entry.atom == atom) { + _ = self.got_entries_table.swapRemove(got_entry.target); + break; + } + } + + for (self.stubs.items) |stub, i| { + if (stub == atom) { + _ = self.stubs_table.swapRemove(@intCast(u32, i)); + break; + } + } + + for (atom.contained.items) |sym_off| { + const inner = &self.locals.items[sym_off.local_sym_index]; + inner.n_desc = N_DESC_GCED; + + if (self.symbol_resolver.getPtr(inner.n_strx)) |resolv| { + if (resolv.local_sym_index == atom.local_sym_index) { + const global = &self.globals.items[resolv.where_index]; + global.n_desc = N_DESC_GCED; + } + } + } + + log.warn(" BEFORE size = {x}", .{sect.size}); + sect.size -= atom.size; + log.warn(" AFTER size = {x}", .{sect.size}); + if (atom.prev) |prev| { + prev.next = atom.next; + } + if (atom.next) |next| { + next.prev = atom.prev; + } else { + // TODO I think a null would be better here. + // The section will be GCed in the next step. + entry.value_ptr.* = if (atom.prev) |prev| prev else undefined; + } + } + + if (orig_prev) |prev| { + atom = prev; + } else break; + } + } +} + fn updateSectionOrdinals(self: *MachO) !void { if (!self.sections_order_dirty) return; @@ -5776,8 +5962,11 @@ fn writeDyldInfoData(self: *MachO) !void { } const seg = self.load_commands.items[match.seg].segment; + const sect = seg.sections.items[match.sect]; + log.warn("dyld info for {s},{s}", .{ sect.segName(), sect.sectName() }); while (true) { + log.warn(" ATOM %{d}", .{atom.local_sym_index}); const sym = self.locals.items[atom.local_sym_index]; const base_offset = sym.n_value - seg.inner.vmaddr; @@ -6217,10 +6406,19 @@ fn writeSymbolTable(self: *MachO) !void { for (self.locals.items) |sym| { if (sym.n_strx == 0) continue; + if (sym.n_desc == N_DESC_GCED) continue; if (self.symbol_resolver.get(sym.n_strx)) |_| continue; try locals.append(sym); } + var globals = std.ArrayList(macho.nlist_64).init(self.base.allocator); + defer globals.deinit(); + + for (self.globals.items) |sym| { + if (sym.n_desc == N_DESC_GCED) continue; + try globals.append(sym); + } + // TODO How do we handle null global symbols in incremental context? var undefs = std.ArrayList(macho.nlist_64).init(self.base.allocator); defer undefs.deinit(); @@ -6291,7 +6489,7 @@ fn writeSymbolTable(self: *MachO) !void { } const nlocals = locals.items.len; - const nexports = self.globals.items.len; + const nexports = globals.items.len; const nundefs = undefs.items.len; const locals_off = symtab.symoff; @@ -6302,7 +6500,7 @@ fn writeSymbolTable(self: *MachO) !void { const exports_off = locals_off + locals_size; const exports_size = nexports * @sizeOf(macho.nlist_64); log.debug("writing exported symbols from 0x{x} to 0x{x}", .{ exports_off, exports_size + exports_off }); - try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.globals.items), exports_off); + try self.base.file.?.pwriteAll(mem.sliceAsBytes(globals.items), exports_off); const undefs_off = exports_off + exports_size; const undefs_size = nundefs * @sizeOf(macho.nlist_64); @@ -6898,55 +7096,55 @@ fn snapshotState(self: *MachO) !void { } fn logSymtab(self: MachO) void { - log.debug("locals:", .{}); + log.warn("locals:", .{}); for (self.locals.items) |sym, id| { - log.debug(" {d}: {s}: @{x} in {d}", .{ id, self.getString(sym.n_strx), sym.n_value, sym.n_sect }); + log.warn(" {d}: {s}: @{x} in {d}", .{ id, self.getString(sym.n_strx), sym.n_value, sym.n_sect }); } - log.debug("globals:", .{}); + log.warn("globals:", .{}); for (self.globals.items) |sym, id| { - log.debug(" {d}: {s}: @{x} in {d}", .{ id, self.getString(sym.n_strx), sym.n_value, sym.n_sect }); + log.warn(" {d}: {s}: @{x} in {d}", .{ id, self.getString(sym.n_strx), sym.n_value, sym.n_sect }); } - log.debug("undefs:", .{}); + log.warn("undefs:", .{}); for (self.undefs.items) |sym, id| { - log.debug(" {d}: {s}: in {d}", .{ id, self.getString(sym.n_strx), sym.n_desc }); + log.warn(" {d}: {s}: in {d}", .{ id, self.getString(sym.n_strx), sym.n_desc }); } { - log.debug("resolver:", .{}); + log.warn("resolver:", .{}); var it = self.symbol_resolver.iterator(); while (it.next()) |entry| { - log.debug(" {s} => {}", .{ self.getString(entry.key_ptr.*), entry.value_ptr.* }); + log.warn(" {s} => {}", .{ self.getString(entry.key_ptr.*), entry.value_ptr.* }); } } - log.debug("GOT entries:", .{}); + log.warn("GOT entries:", .{}); for (self.got_entries_table.values()) |value| { const key = self.got_entries.items[value].target; const atom = self.got_entries.items[value].atom; const n_value = self.locals.items[atom.local_sym_index].n_value; switch (key) { - .local => |ndx| log.debug(" {d}: @{x}", .{ ndx, n_value }), - .global => |n_strx| log.debug(" {s}: @{x}", .{ self.getString(n_strx), n_value }), + .local => |ndx| log.warn(" {d}: @{x}", .{ ndx, n_value }), + .global => |n_strx| log.warn(" {s}: @{x}", .{ self.getString(n_strx), n_value }), } } - log.debug("__thread_ptrs entries:", .{}); + log.warn("__thread_ptrs entries:", .{}); for (self.tlv_ptr_entries_table.values()) |value| { const key = self.tlv_ptr_entries.items[value].target; const atom = self.tlv_ptr_entries.items[value].atom; const n_value = self.locals.items[atom.local_sym_index].n_value; assert(key == .global); - log.debug(" {s}: @{x}", .{ self.getString(key.global), n_value }); + log.warn(" {s}: @{x}", .{ self.getString(key.global), n_value }); } - log.debug("stubs:", .{}); + log.warn("stubs:", .{}); for (self.stubs_table.keys()) |key| { const value = self.stubs_table.get(key).?; const atom = self.stubs.items[value]; const sym = self.locals.items[atom.local_sym_index]; - log.debug(" {s}: @{x}", .{ self.getString(key), sym.n_value }); + log.warn(" {s}: @{x}", .{ self.getString(key), sym.n_value }); } } @@ -6964,6 +7162,45 @@ fn logSectionOrdinals(self: MachO) void { } } +fn logAtoms(self: MachO) void { + log.warn("atoms:", .{}); + var it = self.atoms.iterator(); + while (it.next()) |entry| { + const match = entry.key_ptr.*; + var atom = entry.value_ptr.*; + + while (atom.prev) |prev| { + atom = prev; + } + + const seg = self.load_commands.items[match.seg].segment; + const sect = seg.sections.items[match.sect]; + log.warn("{s},{s}", .{ sect.segName(), sect.sectName() }); + + while (true) { + self.logAtom(atom); + + if (atom.next) |next| { + atom = next; + } else break; + } + } +} + +fn logAtom(self: MachO, atom: *const Atom) void { + const sym = self.locals.items[atom.local_sym_index]; + log.warn(" ATOM(%{d}) @ {x}", .{ atom.local_sym_index, sym.n_value }); + + for (atom.contained.items) |sym_off| { + const inner_sym = self.locals.items[sym_off.local_sym_index]; + log.warn(" %{d} ('{s}') @ {x}", .{ + sym_off.local_sym_index, + self.getString(inner_sym.n_strx), + inner_sym.n_value, + }); + } +} + /// Since `os.copy_file_range` cannot be used when copying overlapping ranges within the same file, /// and since `File.copyRangeAll` uses `os.copy_file_range` under-the-hood, we use heap allocated /// buffers on all hosts except Linux (if `copy_file_range` syscall is available). |
