authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-05-19 22:42:35+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2024-05-23 12:04:17+02:00
log434e69482ed29de26ceea16dbc5679f32281c502
treedf307c783b90dd598ae3a5afe24506b6fb59905d
parent9be8a9000faead40b1aec4877506ff10b066659c

link/macho: dedup literals in objects and internal object file


7 files changed, 453 insertions(+), 100 deletions(-)

src/link/MachO.zig+102-14
...@@ -539,6 +539,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node...@@ -539,6 +539,7 @@ pub fn flushModule(self: *MachO, arena: Allocator, prog_node: *std.Progress.Node
539539
540 try self.convertTentativeDefinitions();540 try self.convertTentativeDefinitions();
541 try self.createObjcSections();541 try self.createObjcSections();
542 try self.dedupLiterals();
542 try self.claimUnresolved();543 try self.claimUnresolved();
543544
544 if (self.base.gc_sections) {545 if (self.base.gc_sections) {
...@@ -1491,6 +1492,22 @@ fn createObjcSections(self: *MachO) !void {...@@ -1491,6 +1492,22 @@ fn createObjcSections(self: *MachO) !void {
1491 const name = eatPrefix(sym.getName(self), "_objc_msgSend$").?;1492 const name = eatPrefix(sym.getName(self), "_objc_msgSend$").?;
1492 const selrefs_index = try internal.addObjcMsgsendSections(name, self);1493 const selrefs_index = try internal.addObjcMsgsendSections(name, self);
1493 try sym.addExtra(.{ .objc_selrefs = selrefs_index }, self);1494 try sym.addExtra(.{ .objc_selrefs = selrefs_index }, self);
1495 sym.flags.objc_stubs = true;
1496 }
1497}
1498
1499pub fn dedupLiterals(self: *MachO) !void {
1500 const gpa = self.base.comp.gpa;
1501 var lp: LiteralPool = .{};
1502 defer lp.deinit(gpa);
1503 if (self.getZigObject()) |zo| {
1504 try zo.dedupLiterals(&lp, self);
1505 }
1506 for (self.objects.items) |index| {
1507 try self.getFile(index).?.object.dedupLiterals(&lp, self);
1508 }
1509 if (self.getInternalObject()) |object| {
1510 try object.dedupLiterals(&lp, self);
1494 }1511 }
1495}1512}
14961513
...@@ -1728,20 +1745,18 @@ fn initOutputSections(self: *MachO) !void {...@@ -1728,20 +1745,18 @@ fn initOutputSections(self: *MachO) !void {
1728 atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(self), self);1745 atom.out_n_sect = try Atom.initOutputSection(atom.getInputSection(self), self);
1729 }1746 }
1730 }1747 }
1731 if (self.text_sect_index == null) {1748 self.text_sect_index = self.getSectionByName("__TEXT", "__text") orelse
1732 self.text_sect_index = try self.addSection("__TEXT", "__text", .{1749 try self.addSection("__TEXT", "__text", .{
1733 .alignment = switch (self.getTarget().cpu.arch) {1750 .alignment = switch (self.getTarget().cpu.arch) {
1734 .x86_64 => 0,1751 .x86_64 => 0,
1735 .aarch64 => 2,1752 .aarch64 => 2,
1736 else => unreachable,1753 else => unreachable,
1737 },1754 },
1738 .flags = macho.S_REGULAR |1755 .flags = macho.S_REGULAR |
1739 macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,1756 macho.S_ATTR_PURE_INSTRUCTIONS | macho.S_ATTR_SOME_INSTRUCTIONS,
1740 });1757 });
1741 }1758 self.data_sect_index = self.getSectionByName("__DATA", "__data") orelse
1742 if (self.data_sect_index == null) {1759 try self.addSection("__DATA", "__data", .{});
1743 self.data_sect_index = try self.addSection("__DATA", "__data", .{});
1744 }
1745}1760}
17461761
1747fn initSyntheticSections(self: *MachO) !void {1762fn initSyntheticSections(self: *MachO) !void {
...@@ -4387,6 +4402,78 @@ const Section = struct {...@@ -4387,6 +4402,78 @@ const Section = struct {
4387 last_atom_index: Atom.Index = 0,4402 last_atom_index: Atom.Index = 0,
4388};4403};
43894404
4405pub const LiteralPool = struct {
4406 table: std.AutoArrayHashMapUnmanaged(void, void) = .{},
4407 keys: std.ArrayListUnmanaged(Key) = .{},
4408 values: std.ArrayListUnmanaged(Atom.Index) = .{},
4409 data: std.ArrayListUnmanaged(u8) = .{},
4410
4411 pub fn deinit(lp: *LiteralPool, allocator: Allocator) void {
4412 lp.table.deinit(allocator);
4413 lp.keys.deinit(allocator);
4414 lp.values.deinit(allocator);
4415 lp.data.deinit(allocator);
4416 }
4417
4418 const InsertResult = struct {
4419 found_existing: bool,
4420 atom: *Atom.Index,
4421 };
4422
4423 pub fn insert(lp: *LiteralPool, allocator: Allocator, @"type": u8, string: []const u8) !InsertResult {
4424 const size: u32 = @intCast(string.len);
4425 try lp.data.ensureUnusedCapacity(allocator, size);
4426 const off: u32 = @intCast(lp.data.items.len);
4427 lp.data.appendSliceAssumeCapacity(string);
4428 const adapter = Adapter{ .lp = lp };
4429 const key = Key{ .off = off, .size = size, .seed = @"type" };
4430 const gop = try lp.table.getOrPutAdapted(allocator, key, adapter);
4431 if (!gop.found_existing) {
4432 try lp.keys.append(allocator, key);
4433 _ = try lp.values.addOne(allocator);
4434 }
4435 return .{
4436 .found_existing = gop.found_existing,
4437 .atom = &lp.values.items[gop.index],
4438 };
4439 }
4440
4441 const Key = struct {
4442 off: u32,
4443 size: u32,
4444 seed: u8,
4445
4446 fn getData(key: Key, lp: *const LiteralPool) []const u8 {
4447 return lp.data.items[key.off..][0..key.size];
4448 }
4449
4450 fn eql(key: Key, other: Key, lp: *const LiteralPool) bool {
4451 const key_data = key.getData(lp);
4452 const other_data = other.getData(lp);
4453 return mem.eql(u8, key_data, other_data);
4454 }
4455
4456 fn hash(key: Key, lp: *const LiteralPool) u32 {
4457 const data = key.getData(lp);
4458 return @truncate(Hash.hash(key.seed, data));
4459 }
4460 };
4461
4462 const Adapter = struct {
4463 lp: *const LiteralPool,
4464
4465 pub fn eql(ctx: @This(), key: Key, b_void: void, b_map_index: usize) bool {
4466 _ = b_void;
4467 const other = ctx.lp.keys.items[b_map_index];
4468 return key.eql(other, ctx.lp);
4469 }
4470
4471 pub fn hash(ctx: @This(), key: Key) u32 {
4472 return key.hash(ctx.lp);
4473 }
4474 };
4475};
4476
4390const HotUpdateState = struct {4477const HotUpdateState = struct {
4391 mach_task: ?std.c.MachTask = null,4478 mach_task: ?std.c.MachTask = null,
4392};4479};
...@@ -4738,6 +4825,7 @@ const Dylib = @import("MachO/Dylib.zig");...@@ -4738,6 +4825,7 @@ const Dylib = @import("MachO/Dylib.zig");
4738const ExportTrieSection = synthetic.ExportTrieSection;4825const ExportTrieSection = synthetic.ExportTrieSection;
4739const File = @import("MachO/file.zig").File;4826const File = @import("MachO/file.zig").File;
4740const GotSection = synthetic.GotSection;4827const GotSection = synthetic.GotSection;
4828const Hash = std.hash.Wyhash;
4741const Indsymtab = synthetic.Indsymtab;4829const Indsymtab = synthetic.Indsymtab;
4742const InternalObject = @import("MachO/InternalObject.zig");4830const InternalObject = @import("MachO/InternalObject.zig");
4743const ObjcStubsSection = synthetic.ObjcStubsSection;4831const ObjcStubsSection = synthetic.ObjcStubsSection;
src/link/MachO/Atom.zig+11-8
...@@ -143,6 +143,16 @@ pub inline fn setExtra(atom: Atom, extra: Extra, macho_file: *MachO) void {...@@ -143,6 +143,16 @@ pub inline fn setExtra(atom: Atom, extra: Extra, macho_file: *MachO) void {
143}143}
144144
145pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {145pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {
146 if (macho_file.base.isRelocatable()) {
147 const osec = macho_file.getSectionByName(sect.segName(), sect.sectName()) orelse
148 try macho_file.addSection(
149 sect.segName(),
150 sect.sectName(),
151 .{ .flags = sect.flags },
152 );
153 return osec;
154 }
155
146 const segname, const sectname, const flags = blk: {156 const segname, const sectname, const flags = blk: {
147 if (sect.isCode()) break :blk .{157 if (sect.isCode()) break :blk .{
148 "__TEXT",158 "__TEXT",
...@@ -200,18 +210,11 @@ pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {...@@ -200,18 +210,11 @@ pub fn initOutputSection(sect: macho.section_64, macho_file: *MachO) !u8 {
200 else => break :blk .{ sect.segName(), sect.sectName(), sect.flags },210 else => break :blk .{ sect.segName(), sect.sectName(), sect.flags },
201 }211 }
202 };212 };
203 const osec = macho_file.getSectionByName(segname, sectname) orelse try macho_file.addSection(213 return macho_file.getSectionByName(segname, sectname) orelse try macho_file.addSection(
204 segname,214 segname,
205 sectname,215 sectname,
206 .{ .flags = flags },216 .{ .flags = flags },
207 );217 );
208 if (mem.eql(u8, segname, "__TEXT") and mem.eql(u8, sectname, "__text")) {
209 macho_file.text_sect_index = osec;
210 }
211 if (mem.eql(u8, segname, "__DATA") and mem.eql(u8, sectname, "__data")) {
212 macho_file.data_sect_index = osec;
213 }
214 return osec;
215}218}
216219
217/// Returns how much room there is to grow in virtual address space.220/// Returns how much room there is to grow in virtual address space.
src/link/MachO/InternalObject.zig+96-33
...@@ -3,7 +3,6 @@ index: File.Index,...@@ -3,7 +3,6 @@ index: File.Index,
3sections: std.MultiArrayList(Section) = .{},3sections: std.MultiArrayList(Section) = .{},
4atoms: std.ArrayListUnmanaged(Atom.Index) = .{},4atoms: std.ArrayListUnmanaged(Atom.Index) = .{},
5symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},5symbols: std.ArrayListUnmanaged(Symbol.Index) = .{},
6strtab: std.ArrayListUnmanaged(u8) = .{},
76
8objc_methnames: std.ArrayListUnmanaged(u8) = .{},7objc_methnames: std.ArrayListUnmanaged(u8) = .{},
9objc_selrefs: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64),8objc_selrefs: [@sizeOf(u64)]u8 = [_]u8{0} ** @sizeOf(u64),
...@@ -18,7 +17,6 @@ pub fn deinit(self: *InternalObject, allocator: Allocator) void {...@@ -18,7 +17,6 @@ pub fn deinit(self: *InternalObject, allocator: Allocator) void {
18 self.sections.deinit(allocator);17 self.sections.deinit(allocator);
19 self.atoms.deinit(allocator);18 self.atoms.deinit(allocator);
20 self.symbols.deinit(allocator);19 self.symbols.deinit(allocator);
21 self.strtab.deinit(allocator);
22 self.objc_methnames.deinit(allocator);20 self.objc_methnames.deinit(allocator);
23}21}
2422
...@@ -38,9 +36,9 @@ pub fn addSymbol(self: *InternalObject, name: [:0]const u8, macho_file: *MachO)...@@ -38,9 +36,9 @@ pub fn addSymbol(self: *InternalObject, name: [:0]const u8, macho_file: *MachO)
38}36}
3937
40/// Creates a fake input sections __TEXT,__objc_methname and __DATA,__objc_selrefs.38/// Creates a fake input sections __TEXT,__objc_methname and __DATA,__objc_selrefs.
41pub fn addObjcMsgsendSections(self: *InternalObject, sym_name: []const u8, macho_file: *MachO) !u32 {39pub fn addObjcMsgsendSections(self: *InternalObject, sym_name: []const u8, macho_file: *MachO) !Atom.Index {
42 const methname_atom_index = try self.addObjcMethnameSection(sym_name, macho_file);40 const methname_atom_index = try self.addObjcMethnameSection(sym_name, macho_file);
43 return try self.addObjcSelrefsSection(sym_name, methname_atom_index, macho_file);41 return try self.addObjcSelrefsSection(methname_atom_index, macho_file);
44}42}
4543
46fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_file: *MachO) !Atom.Index {44fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_file: *MachO) !Atom.Index {
...@@ -48,11 +46,8 @@ fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_fil...@@ -48,11 +46,8 @@ fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_fil
48 const atom_index = try macho_file.addAtom();46 const atom_index = try macho_file.addAtom();
49 try self.atoms.append(gpa, atom_index);47 try self.atoms.append(gpa, atom_index);
5048
51 const name = try std.fmt.allocPrintZ(gpa, "__TEXT$__objc_methname${s}", .{methname});
52 defer gpa.free(name);
53 const atom = macho_file.getAtom(atom_index).?;49 const atom = macho_file.getAtom(atom_index).?;
54 atom.atom_index = atom_index;50 atom.atom_index = atom_index;
55 atom.name = try self.addString(gpa, name);
56 atom.file = self.index;51 atom.file = self.index;
57 atom.size = methname.len + 1;52 atom.size = methname.len + 1;
58 atom.alignment = .@"1";53 atom.alignment = .@"1";
...@@ -72,21 +67,13 @@ fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_fil...@@ -72,21 +67,13 @@ fn addObjcMethnameSection(self: *InternalObject, methname: []const u8, macho_fil
72 return atom_index;67 return atom_index;
73}68}
7469
75fn addObjcSelrefsSection(70fn addObjcSelrefsSection(self: *InternalObject, methname_atom_index: Atom.Index, macho_file: *MachO) !Atom.Index {
76 self: *InternalObject,
77 methname: []const u8,
78 methname_atom_index: Atom.Index,
79 macho_file: *MachO,
80) !Atom.Index {
81 const gpa = macho_file.base.comp.gpa;71 const gpa = macho_file.base.comp.gpa;
82 const atom_index = try macho_file.addAtom();72 const atom_index = try macho_file.addAtom();
83 try self.atoms.append(gpa, atom_index);73 try self.atoms.append(gpa, atom_index);
8474
85 const name = try std.fmt.allocPrintZ(gpa, "__DATA$__objc_selrefs${s}", .{methname});
86 defer gpa.free(name);
87 const atom = macho_file.getAtom(atom_index).?;75 const atom = macho_file.getAtom(atom_index).?;
88 atom.atom_index = atom_index;76 atom.atom_index = atom_index;
89 atom.name = try self.addString(gpa, name);
90 atom.file = self.index;77 atom.file = self.index;
91 atom.size = @sizeOf(u64);78 atom.size = @sizeOf(u64);
92 atom.alignment = .@"8";79 atom.alignment = .@"8";
...@@ -122,6 +109,83 @@ fn addObjcSelrefsSection(...@@ -122,6 +109,83 @@ fn addObjcSelrefsSection(
122 return atom_index;109 return atom_index;
123}110}
124111
112pub fn dedupLiterals(self: InternalObject, lp: *MachO.LiteralPool, macho_file: *MachO) !void {
113 const gpa = macho_file.base.comp.gpa;
114
115 var killed_atoms = std.AutoHashMap(Atom.Index, Atom.Index).init(gpa);
116 defer killed_atoms.deinit();
117
118 var buffer = std.ArrayList(u8).init(gpa);
119 defer buffer.deinit();
120
121 const slice = self.sections.slice();
122 for (slice.items(.header), self.atoms.items, 0..) |header, atom_index, n_sect| {
123 if (Object.isCstringLiteral(header) or Object.isFixedSizeLiteral(header)) {
124 const data = try self.getSectionData(@intCast(n_sect));
125 const atom = macho_file.getAtom(atom_index).?;
126 const res = try lp.insert(gpa, header.type(), data);
127 if (!res.found_existing) {
128 res.atom.* = atom_index;
129 continue;
130 }
131 atom.flags.alive = false;
132 try killed_atoms.putNoClobber(atom_index, res.atom.*);
133 } else if (Object.isPtrLiteral(header)) {
134 const atom = macho_file.getAtom(atom_index).?;
135 const relocs = atom.getRelocs(macho_file);
136 assert(relocs.len == 1);
137 const rel = relocs[0];
138 assert(rel.tag == .local);
139 const target = macho_file.getAtom(rel.target).?;
140 const addend = std.math.cast(u32, rel.addend) orelse return error.Overflow;
141 try buffer.ensureUnusedCapacity(target.size);
142 buffer.resize(target.size) catch unreachable;
143 try target.getData(macho_file, buffer.items);
144 const res = try lp.insert(gpa, header.type(), buffer.items[addend..]);
145 buffer.clearRetainingCapacity();
146 if (!res.found_existing) {
147 res.atom.* = atom_index;
148 continue;
149 }
150 atom.flags.alive = false;
151 try killed_atoms.putNoClobber(atom_index, res.atom.*);
152 }
153 }
154
155 for (self.atoms.items) |atom_index| {
156 if (killed_atoms.get(atom_index)) |_| continue;
157 const atom = macho_file.getAtom(atom_index) orelse continue;
158 if (!atom.flags.alive) continue;
159 if (!atom.flags.relocs) continue;
160
161 const relocs = blk: {
162 const extra = atom.getExtra(macho_file).?;
163 const relocs = slice.items(.relocs)[atom.n_sect].items;
164 break :blk relocs[extra.rel_index..][0..extra.rel_count];
165 };
166 for (relocs) |*rel| switch (rel.tag) {
167 .local => if (killed_atoms.get(rel.target)) |new_target| {
168 rel.target = new_target;
169 },
170 .@"extern" => {
171 const target = rel.getTargetSymbol(macho_file);
172 if (killed_atoms.get(target.atom)) |new_atom| {
173 target.atom = new_atom;
174 }
175 },
176 };
177 }
178
179 for (self.symbols.items) |sym_index| {
180 const sym = macho_file.getSymbol(sym_index);
181 if (!sym.flags.objc_stubs) continue;
182 const extra = sym.getExtra(macho_file).?;
183 if (killed_atoms.get(extra.objc_selrefs)) |new_atom| {
184 try sym.addExtra(.{ .objc_selrefs = new_atom }, macho_file);
185 }
186 }
187}
188
125pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) !void {189pub fn calcSymtabSize(self: *InternalObject, macho_file: *MachO) !void {
126 for (self.symbols.items) |sym_index| {190 for (self.symbols.items) |sym_index| {
127 const sym = macho_file.getSymbol(sym_index);191 const sym = macho_file.getSymbol(sym_index);
...@@ -167,18 +231,23 @@ fn addSection(self: *InternalObject, allocator: Allocator, segname: []const u8,...@@ -167,18 +231,23 @@ fn addSection(self: *InternalObject, allocator: Allocator, segname: []const u8,
167 return n_sect;231 return n_sect;
168}232}
169233
170pub fn getAtomData(self: *const InternalObject, atom: Atom, buffer: []u8) !void {234fn getSectionData(self: *const InternalObject, index: u32) error{Overflow}![]const u8 {
171 assert(buffer.len == atom.size);
172 const slice = self.sections.slice();235 const slice = self.sections.slice();
173 const sect = slice.items(.header)[atom.n_sect];236 assert(index < slice.items(.header).len);
174 const extra = slice.items(.extra)[atom.n_sect];237 const sect = slice.items(.header)[index];
175 const data = if (extra.is_objc_methname) blk: {238 const extra = slice.items(.extra)[index];
239 if (extra.is_objc_methname) {
176 const size = std.math.cast(usize, sect.size) orelse return error.Overflow;240 const size = std.math.cast(usize, sect.size) orelse return error.Overflow;
177 break :blk self.objc_methnames.items[sect.offset..][0..size];241 return self.objc_methnames.items[sect.offset..][0..size];
178 } else if (extra.is_objc_selref)242 } else if (extra.is_objc_selref)
179 &self.objc_selrefs243 return &self.objc_selrefs
180 else244 else
181 @panic("ref to non-existent section");245 @panic("ref to non-existent section");
246}
247
248pub fn getAtomData(self: *const InternalObject, atom: Atom, buffer: []u8) error{Overflow}!void {
249 assert(buffer.len == atom.size);
250 const data = try self.getSectionData(atom.n_sect);
182 const off = std.math.cast(usize, atom.off) orelse return error.Overflow;251 const off = std.math.cast(usize, atom.off) orelse return error.Overflow;
183 const size = std.math.cast(usize, atom.size) orelse return error.Overflow;252 const size = std.math.cast(usize, atom.size) orelse return error.Overflow;
184 @memcpy(buffer, data[off..][0..size]);253 @memcpy(buffer, data[off..][0..size]);
...@@ -191,17 +260,11 @@ pub fn getAtomRelocs(self: *const InternalObject, atom: Atom, macho_file: *MachO...@@ -191,17 +260,11 @@ pub fn getAtomRelocs(self: *const InternalObject, atom: Atom, macho_file: *MachO
191 return relocs.items[extra.rel_index..][0..extra.rel_count];260 return relocs.items[extra.rel_index..][0..extra.rel_count];
192}261}
193262
194fn addString(self: *InternalObject, allocator: Allocator, name: [:0]const u8) error{OutOfMemory}!u32 {
195 const off: u32 = @intCast(self.strtab.items.len);
196 try self.strtab.ensureUnusedCapacity(allocator, name.len + 1);
197 self.strtab.appendSliceAssumeCapacity(name);
198 self.strtab.appendAssumeCapacity(0);
199 return off;
200}
201
202pub fn getString(self: InternalObject, off: u32) [:0]const u8 {263pub fn getString(self: InternalObject, off: u32) [:0]const u8 {
203 assert(off < self.strtab.items.len);264 _ = self;
204 return mem.sliceTo(@as([*:0]const u8, @ptrCast(self.strtab.items.ptr + off)), 0);265 _ = off;
266 // We don't have any local strings for synthetic atoms.
267 return "";
205}268}
206269
207pub fn asFile(self: *InternalObject) File {270pub fn asFile(self: *InternalObject) File {
src/link/MachO/Object.zig+222-35
...@@ -208,7 +208,9 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {...@@ -208,7 +208,9 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
208 try self.initSections(nlists.items, macho_file);208 try self.initSections(nlists.items, macho_file);
209 }209 }
210210
211 try self.initLiteralSections(macho_file);211 try self.initCstringLiterals(macho_file);
212 try self.initFixedSizeLiterals(macho_file);
213 try self.initPointerLiterals(macho_file);
212 try self.linkNlistToAtom(macho_file);214 try self.linkNlistToAtom(macho_file);
213215
214 try self.sortAtoms(macho_file);216 try self.sortAtoms(macho_file);
...@@ -263,25 +265,33 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {...@@ -263,25 +265,33 @@ pub fn parse(self: *Object, macho_file: *MachO) !void {
263 }265 }
264}266}
265267
266inline fn isLiteral(sect: macho.section_64) bool {268pub fn isCstringLiteral(sect: macho.section_64) bool {
269 return sect.type() == macho.S_CSTRING_LITERALS;
270}
271
272pub fn isFixedSizeLiteral(sect: macho.section_64) bool {
267 return switch (sect.type()) {273 return switch (sect.type()) {
268 macho.S_CSTRING_LITERALS,
269 macho.S_4BYTE_LITERALS,274 macho.S_4BYTE_LITERALS,
270 macho.S_8BYTE_LITERALS,275 macho.S_8BYTE_LITERALS,
271 macho.S_16BYTE_LITERALS,276 macho.S_16BYTE_LITERALS,
272 macho.S_LITERAL_POINTERS,
273 => true,277 => true,
274 else => false,278 else => false,
275 };279 };
276}280}
277281
282pub fn isPtrLiteral(sect: macho.section_64) bool {
283 return sect.type() == macho.S_LITERAL_POINTERS;
284}
285
278fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void {286fn initSubsections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
279 const tracy = trace(@src());287 const tracy = trace(@src());
280 defer tracy.end();288 defer tracy.end();
281 const gpa = macho_file.base.comp.gpa;289 const gpa = macho_file.base.comp.gpa;
282 const slice = self.sections.slice();290 const slice = self.sections.slice();
283 for (slice.items(.header), slice.items(.subsections), 0..) |sect, *subsections, n_sect| {291 for (slice.items(.header), slice.items(.subsections), 0..) |sect, *subsections, n_sect| {
284 if (isLiteral(sect)) continue;292 if (isCstringLiteral(sect)) continue;
293 if (isFixedSizeLiteral(sect)) continue;
294 if (isPtrLiteral(sect)) continue;
285295
286 const nlist_start = for (nlists, 0..) |nlist, i| {296 const nlist_start = for (nlists, 0..) |nlist, i| {
287 if (nlist.nlist.n_sect - 1 == n_sect) break i;297 if (nlist.nlist.n_sect - 1 == n_sect) break i;
...@@ -352,7 +362,9 @@ fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void {...@@ -352,7 +362,9 @@ fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
352 try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len);362 try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len);
353363
354 for (slice.items(.header), 0..) |sect, n_sect| {364 for (slice.items(.header), 0..) |sect, n_sect| {
355 if (isLiteral(sect)) continue;365 if (isCstringLiteral(sect)) continue;
366 if (isFixedSizeLiteral(sect)) continue;
367 if (isPtrLiteral(sect)) continue;
356368
357 const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });369 const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
358 defer gpa.free(name);370 defer gpa.free(name);
...@@ -393,6 +405,206 @@ fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void {...@@ -393,6 +405,206 @@ fn initSections(self: *Object, nlists: anytype, macho_file: *MachO) !void {
393 }405 }
394}406}
395407
408fn initCstringLiterals(self: *Object, macho_file: *MachO) !void {
409 const tracy = trace(@src());
410 defer tracy.end();
411
412 const gpa = macho_file.base.comp.gpa;
413 const slice = self.sections.slice();
414
415 for (slice.items(.header), 0..) |sect, n_sect| {
416 if (!isCstringLiteral(sect)) continue;
417
418 const data = try self.getSectionData(@intCast(n_sect), macho_file);
419 defer gpa.free(data);
420
421 var start: u32 = 0;
422 while (start < data.len) {
423 var end = start;
424 while (end < data.len - 1 and data[end] != 0) : (end += 1) {}
425 if (data[end] != 0) {
426 try macho_file.reportParseError2(
427 self.index,
428 "string not null terminated in '{s},{s}'",
429 .{ sect.segName(), sect.sectName() },
430 );
431 return error.MalformedObject;
432 }
433 end += 1;
434
435 const atom_index = try self.addAtom(.{
436 .name = 0,
437 .n_sect = @intCast(n_sect),
438 .off = start,
439 .size = end - start,
440 .alignment = sect.@"align",
441 }, macho_file);
442 try slice.items(.subsections)[n_sect].append(gpa, .{
443 .atom = atom_index,
444 .off = start,
445 });
446
447 start = end;
448 }
449 }
450}
451
452fn initFixedSizeLiterals(self: *Object, macho_file: *MachO) !void {
453 const tracy = trace(@src());
454 defer tracy.end();
455
456 const gpa = macho_file.base.comp.gpa;
457 const slice = self.sections.slice();
458
459 for (slice.items(.header), 0..) |sect, n_sect| {
460 if (!isFixedSizeLiteral(sect)) continue;
461 const rec_size: u8 = switch (sect.type()) {
462 macho.S_4BYTE_LITERALS => 4,
463 macho.S_8BYTE_LITERALS => 8,
464 macho.S_16BYTE_LITERALS => 16,
465 else => unreachable,
466 };
467 if (sect.size % rec_size != 0) {
468 try macho_file.reportParseError2(
469 self.index,
470 "size not multiple of record size in '{s},{s}'",
471 .{ sect.segName(), sect.sectName() },
472 );
473 return error.MalformedObject;
474 }
475 var pos: u32 = 0;
476 while (pos < sect.size) : (pos += rec_size) {
477 const atom_index = try self.addAtom(.{
478 .name = 0,
479 .n_sect = @intCast(n_sect),
480 .off = pos,
481 .size = rec_size,
482 .alignment = sect.@"align",
483 }, macho_file);
484 try slice.items(.subsections)[n_sect].append(gpa, .{
485 .atom = atom_index,
486 .off = pos,
487 });
488 }
489 }
490}
491
492fn initPointerLiterals(self: *Object, macho_file: *MachO) !void {
493 const tracy = trace(@src());
494 defer tracy.end();
495
496 const gpa = macho_file.base.comp.gpa;
497 const slice = self.sections.slice();
498
499 for (slice.items(.header), 0..) |sect, n_sect| {
500 if (!isPtrLiteral(sect)) continue;
501
502 const rec_size: u8 = 8;
503 if (sect.size % rec_size != 0) {
504 try macho_file.reportParseError2(
505 self.index,
506 "size not multiple of record size in '{s},{s}'",
507 .{ sect.segName(), sect.sectName() },
508 );
509 return error.MalformedObject;
510 }
511 const num_ptrs = @divExact(sect.size, rec_size);
512
513 for (0..num_ptrs) |i| {
514 const pos: u32 = @as(u32, @intCast(i)) * rec_size;
515 const atom_index = try self.addAtom(.{
516 .name = 0,
517 .n_sect = @intCast(n_sect),
518 .off = pos,
519 .size = rec_size,
520 .alignment = sect.@"align",
521 }, macho_file);
522 try slice.items(.subsections)[n_sect].append(gpa, .{
523 .atom = atom_index,
524 .off = pos,
525 });
526 }
527 }
528}
529
530pub fn dedupLiterals(self: Object, lp: *MachO.LiteralPool, macho_file: *MachO) !void {
531 const gpa = macho_file.base.comp.gpa;
532
533 var killed_atoms = std.AutoHashMap(Atom.Index, Atom.Index).init(gpa);
534 defer killed_atoms.deinit();
535
536 var buffer = std.ArrayList(u8).init(gpa);
537 defer buffer.deinit();
538
539 const slice = self.sections.slice();
540 for (slice.items(.header), slice.items(.subsections), 0..) |header, subs, n_sect| {
541 if (isCstringLiteral(header) or isFixedSizeLiteral(header)) {
542 const data = try self.getSectionData(@intCast(n_sect), macho_file);
543 defer gpa.free(data);
544
545 for (subs.items) |sub| {
546 const atom = macho_file.getAtom(sub.atom).?;
547 const atom_data = data[atom.off..][0..atom.size];
548 const res = try lp.insert(gpa, header.type(), atom_data);
549 if (!res.found_existing) {
550 res.atom.* = sub.atom;
551 continue;
552 }
553 atom.flags.alive = false;
554 try killed_atoms.putNoClobber(sub.atom, res.atom.*);
555 }
556 } else if (isPtrLiteral(header)) {
557 for (subs.items) |sub| {
558 const atom = macho_file.getAtom(sub.atom).?;
559 const relocs = atom.getRelocs(macho_file);
560 assert(relocs.len == 1);
561 const rel = relocs[0];
562 const target = switch (rel.tag) {
563 .local => rel.target,
564 .@"extern" => rel.getTargetSymbol(macho_file).atom,
565 };
566 const addend = math.cast(u32, rel.addend) orelse return error.Overflow;
567 const target_atom = macho_file.getAtom(target).?;
568 try buffer.ensureUnusedCapacity(target_atom.size);
569 buffer.resize(target_atom.size) catch unreachable;
570 try target_atom.getData(macho_file, buffer.items);
571 const res = try lp.insert(gpa, header.type(), buffer.items[addend..]);
572 buffer.clearRetainingCapacity();
573 if (!res.found_existing) {
574 res.atom.* = sub.atom;
575 continue;
576 }
577 atom.flags.alive = false;
578 try killed_atoms.putNoClobber(sub.atom, res.atom.*);
579 }
580 }
581 }
582
583 for (self.atoms.items) |atom_index| {
584 if (killed_atoms.get(atom_index)) |_| continue;
585 const atom = macho_file.getAtom(atom_index) orelse continue;
586 if (!atom.flags.alive) continue;
587 if (!atom.flags.relocs) continue;
588
589 const relocs = blk: {
590 const extra = atom.getExtra(macho_file).?;
591 const relocs = slice.items(.relocs)[atom.n_sect].items;
592 break :blk relocs[extra.rel_index..][0..extra.rel_count];
593 };
594 for (relocs) |*rel| switch (rel.tag) {
595 .local => if (killed_atoms.get(rel.target)) |new_target| {
596 rel.target = new_target;
597 },
598 .@"extern" => {
599 const target = rel.getTargetSymbol(macho_file);
600 if (killed_atoms.get(target.atom)) |new_atom| {
601 target.atom = new_atom;
602 }
603 },
604 };
605 }
606}
607
396const AddAtomArgs = struct {608const AddAtomArgs = struct {
397 name: u32,609 name: u32,
398 n_sect: u8,610 n_sect: u8,
...@@ -416,34 +628,6 @@ fn addAtom(self: *Object, args: AddAtomArgs, macho_file: *MachO) !Atom.Index {...@@ -416,34 +628,6 @@ fn addAtom(self: *Object, args: AddAtomArgs, macho_file: *MachO) !Atom.Index {
416 return atom_index;628 return atom_index;
417}629}
418630
419fn initLiteralSections(self: *Object, macho_file: *MachO) !void {
420 const tracy = trace(@src());
421 defer tracy.end();
422 // TODO here we should split into equal-sized records, hash the contents, and then
423 // deduplicate - ICF.
424 // For now, we simply cover each literal section with one large atom.
425 const gpa = macho_file.base.comp.gpa;
426 const slice = self.sections.slice();
427
428 try self.atoms.ensureUnusedCapacity(gpa, self.sections.items(.header).len);
429
430 for (slice.items(.header), 0..) |sect, n_sect| {
431 if (!isLiteral(sect)) continue;
432
433 const name = try std.fmt.allocPrintZ(gpa, "{s}${s}", .{ sect.segName(), sect.sectName() });
434 defer gpa.free(name);
435
436 const atom_index = try self.addAtom(.{
437 .name = try self.addString(gpa, name),
438 .n_sect = @intCast(n_sect),
439 .off = 0,
440 .size = sect.size,
441 .alignment = sect.@"align",
442 }, macho_file);
443 try slice.items(.subsections)[n_sect].append(gpa, .{ .atom = atom_index, .off = 0 });
444 }
445}
446
447pub fn findAtom(self: Object, addr: u64) ?Atom.Index {631pub fn findAtom(self: Object, addr: u64) ?Atom.Index {
448 const tracy = trace(@src());632 const tracy = trace(@src());
449 defer tracy.end();633 defer tracy.end();
...@@ -1369,7 +1553,10 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void {...@@ -1369,7 +1553,10 @@ pub fn calcSymtabSize(self: *Object, macho_file: *MachO) !void {
1369 const name = sym.getName(macho_file);1553 const name = sym.getName(macho_file);
1370 // TODO in -r mode, we actually want to merge symbol names and emit only one1554 // TODO in -r mode, we actually want to merge symbol names and emit only one
1371 // work it out when emitting relocs1555 // work it out when emitting relocs
1372 if (name.len > 0 and (name[0] == 'L' or name[0] == 'l') and !macho_file.base.isObject()) continue;1556 if (name.len > 0 and
1557 (name[0] == 'L' or name[0] == 'l' or
1558 mem.startsWith(u8, name, "_OBJC_SELECTOR_REFERENCES_")) and
1559 !macho_file.base.isObject()) continue;
1373 sym.flags.output_symtab = true;1560 sym.flags.output_symtab = true;
1374 if (sym.isLocal()) {1561 if (sym.isLocal()) {
1375 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);1562 try sym.addExtra(.{ .symtab = self.output_symtab_ctx.nlocals }, macho_file);
src/link/MachO/Symbol.zig+2-2
...@@ -14,8 +14,8 @@ file: File.Index = 0,...@@ -14,8 +14,8 @@ file: File.Index = 0,
14/// Use `getAtom` to get the pointer to the atom.14/// Use `getAtom` to get the pointer to the atom.
15atom: Atom.Index = 0,15atom: Atom.Index = 0,
1616
17/// Assigned output section index for this atom.17/// Assigned output section index for this symbol.
18out_n_sect: u16 = 0,18out_n_sect: u8 = 0,
1919
20/// Index of the source nlist this symbol references.20/// Index of the source nlist this symbol references.
21/// Use `getNlist` to pull the nlist from the relevant file.21/// Use `getNlist` to pull the nlist from the relevant file.
src/link/MachO/ZigObject.zig+7
...@@ -314,6 +314,13 @@ pub fn checkDuplicates(self: *ZigObject, dupes: anytype, macho_file: *MachO) !vo...@@ -314,6 +314,13 @@ pub fn checkDuplicates(self: *ZigObject, dupes: anytype, macho_file: *MachO) !vo
314 }314 }
315}315}
316316
317pub fn dedupLiterals(self: *ZigObject, lp: *MachO.LiteralPool, macho_file: *MachO) !void {
318 _ = self;
319 _ = lp;
320 _ = macho_file;
321 // TODO
322}
323
317/// This is just a temporary helper function that allows us to re-read what we wrote to file into a buffer.324/// This is just a temporary helper function that allows us to re-read what we wrote to file into a buffer.
318/// We need this so that we can write to an archive.325/// We need this so that we can write to an archive.
319/// TODO implement writing ZigObject data directly to a buffer instead.326/// TODO implement writing ZigObject data directly to a buffer instead.
src/link/MachO/relocatable.zig+13-8
...@@ -46,6 +46,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]c...@@ -46,6 +46,7 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]c
4646
47 try macho_file.addUndefinedGlobals();47 try macho_file.addUndefinedGlobals();
48 try macho_file.resolveSymbols();48 try macho_file.resolveSymbols();
49 try macho_file.dedupLiterals();
49 markExports(macho_file);50 markExports(macho_file);
50 claimUnresolved(macho_file);51 claimUnresolved(macho_file);
51 try initOutputSections(macho_file);52 try initOutputSections(macho_file);
...@@ -542,6 +543,9 @@ fn writeAtoms(macho_file: *MachO) !void {...@@ -542,6 +543,9 @@ fn writeAtoms(macho_file: *MachO) !void {
542 const cpu_arch = macho_file.getTarget().cpu.arch;543 const cpu_arch = macho_file.getTarget().cpu.arch;
543 const slice = macho_file.sections.slice();544 const slice = macho_file.sections.slice();
544545
546 var relocs = std.ArrayList(macho.relocation_info).init(gpa);
547 defer relocs.deinit();
548
545 for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| {549 for (slice.items(.header), slice.items(.atoms), 0..) |header, atoms, i| {
546 if (atoms.items.len == 0) continue;550 if (atoms.items.len == 0) continue;
547 if (header.isZerofill()) continue;551 if (header.isZerofill()) continue;
...@@ -553,8 +557,7 @@ fn writeAtoms(macho_file: *MachO) !void {...@@ -553,8 +557,7 @@ fn writeAtoms(macho_file: *MachO) !void {
553 const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0;557 const padding_byte: u8 = if (header.isCode() and cpu_arch == .x86_64) 0xcc else 0;
554 @memset(code, padding_byte);558 @memset(code, padding_byte);
555559
556 var relocs = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);560 try relocs.ensureTotalCapacity(header.nreloc);
557 defer relocs.deinit();
558561
559 for (atoms.items) |atom_index| {562 for (atoms.items) |atom_index| {
560 const atom = macho_file.getAtom(atom_index).?;563 const atom = macho_file.getAtom(atom_index).?;
...@@ -572,22 +575,24 @@ fn writeAtoms(macho_file: *MachO) !void {...@@ -572,22 +575,24 @@ fn writeAtoms(macho_file: *MachO) !void {
572 // TODO scattered writes?575 // TODO scattered writes?
573 try macho_file.base.file.?.pwriteAll(code, header.offset);576 try macho_file.base.file.?.pwriteAll(code, header.offset);
574 try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);577 try macho_file.base.file.?.pwriteAll(mem.sliceAsBytes(relocs.items), header.reloff);
578
579 relocs.clearRetainingCapacity();
575 }580 }
576581
577 if (macho_file.getZigObject()) |zo| {582 if (macho_file.getZigObject()) |zo| {
578 // TODO: this is ugly; perhaps we should aggregrate before?583 // TODO: this is ugly; perhaps we should aggregrate before?
579 var relocs = std.AutoArrayHashMap(u8, std.ArrayList(macho.relocation_info)).init(gpa);584 var zo_relocs = std.AutoArrayHashMap(u8, std.ArrayList(macho.relocation_info)).init(gpa);
580 defer {585 defer {
581 for (relocs.values()) |*list| {586 for (zo_relocs.values()) |*list| {
582 list.deinit();587 list.deinit();
583 }588 }
584 relocs.deinit();589 zo_relocs.deinit();
585 }590 }
586591
587 for (macho_file.sections.items(.header), 0..) |header, n_sect| {592 for (macho_file.sections.items(.header), 0..) |header, n_sect| {
588 if (header.isZerofill()) continue;593 if (header.isZerofill()) continue;
589 if (!macho_file.isZigSection(@intCast(n_sect)) and !macho_file.isDebugSection(@intCast(n_sect))) continue;594 if (!macho_file.isZigSection(@intCast(n_sect)) and !macho_file.isDebugSection(@intCast(n_sect))) continue;
590 const gop = try relocs.getOrPut(@intCast(n_sect));595 const gop = try zo_relocs.getOrPut(@intCast(n_sect));
591 if (gop.found_existing) continue;596 if (gop.found_existing) continue;
592 gop.value_ptr.* = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);597 gop.value_ptr.* = try std.ArrayList(macho.relocation_info).initCapacity(gpa, header.nreloc);
593 }598 }
...@@ -618,12 +623,12 @@ fn writeAtoms(macho_file: *MachO) !void {...@@ -618,12 +623,12 @@ fn writeAtoms(macho_file: *MachO) !void {
618 },623 },
619 };624 };
620 const file_offset = header.offset + atom.value;625 const file_offset = header.offset + atom.value;
621 const rels = relocs.getPtr(atom.out_n_sect).?;626 const rels = zo_relocs.getPtr(atom.out_n_sect).?;
622 try atom.writeRelocs(macho_file, code, rels);627 try atom.writeRelocs(macho_file, code, rels);
623 try macho_file.base.file.?.pwriteAll(code, file_offset);628 try macho_file.base.file.?.pwriteAll(code, file_offset);
624 }629 }
625630
626 for (relocs.keys(), relocs.values()) |sect_id, rels| {631 for (zo_relocs.keys(), zo_relocs.values()) |sect_id, rels| {
627 const header = macho_file.sections.items(.header)[sect_id];632 const header = macho_file.sections.items(.header)[sect_id];
628 assert(rels.items.len == header.nreloc);633 assert(rels.items.len == header.nreloc);
629 mem.sort(macho.relocation_info, rels.items, {}, sortReloc);634 mem.sort(macho.relocation_info, rels.items, {}, sortReloc);