Skip to content

Commit 70c9f20

Browse files
committed
Remove duplicate dylibs
This is a loader hard-error now to load the same library multiple times since macOS 15.
1 parent 58cd472 commit 70c9f20

File tree

3 files changed

+73
-0
lines changed

3 files changed

+73
-0
lines changed

src/Dylib.zig

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,25 @@ pub const Id = struct {
10111011

10121012
return out;
10131013
}
1014+
1015+
pub fn eql(id: Id, other: Id) bool {
1016+
return mem.eql(u8, id.name, other.name) and
1017+
id.timestamp == other.timestamp and
1018+
id.current_version == other.current_version and
1019+
id.compatibility_version == other.compatibility_version;
1020+
}
1021+
1022+
/// Hashes the Id.
1023+
/// TODO: we currently do differentiate between dylibs installed at the *same* path but having different
1024+
/// versions. This might be wrong and we should dedup them. Then again, surely this should be an error, right?
1025+
pub fn hash(id: Id) u64 {
1026+
var hasher = std.hash.Wyhash.init(0);
1027+
hasher.update(id.name);
1028+
hasher.update(mem.asBytes(&id.timestamp));
1029+
hasher.update(mem.asBytes(&id.current_version));
1030+
hasher.update(mem.asBytes(&id.compatibility_version));
1031+
return hasher.final();
1032+
}
10141033
};
10151034

10161035
const Export = struct {

src/MachO.zig

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ pub fn link(self: *MachO) !void {
249249
}
250250

251251
try self.parseInputFiles();
252+
try self.dedupDylibs(resolved_objects.items);
252253
try self.parseDependentDylibs(arena, lib_dirs.items, framework_dirs.items);
253254

254255
if (!self.options.relocatable) {
@@ -832,6 +833,58 @@ fn isHoisted(self: *MachO, install_name: []const u8) bool {
832833
return false;
833834
}
834835

836+
fn dedupDylibs(self: *MachO, resolved_objects: []const LinkObject) !void {
837+
const tracy = trace(@src());
838+
defer tracy.end();
839+
840+
var map = std.HashMap(Dylib.Id, void, struct {
841+
pub fn hash(ctx: @This(), id: Dylib.Id) u64 {
842+
_ = ctx;
843+
return id.hash();
844+
}
845+
846+
pub fn eql(ctx: @This(), id: Dylib.Id, other: Dylib.Id) bool {
847+
_ = ctx;
848+
return id.eql(other);
849+
}
850+
}, std.hash_map.default_max_load_percentage).init(self.allocator);
851+
defer map.deinit();
852+
try map.ensureTotalCapacity(@intCast(self.dylibs.items.len));
853+
854+
var marked_dylibs = std.ArrayList(bool).init(self.allocator);
855+
defer marked_dylibs.deinit();
856+
try marked_dylibs.ensureTotalCapacityPrecise(self.dylibs.items.len);
857+
marked_dylibs.resize(self.dylibs.items.len) catch unreachable;
858+
@memset(marked_dylibs.items, false);
859+
860+
for (self.dylibs.items, marked_dylibs.items) |index, *marker| {
861+
const dylib = self.getFile(index).dylib;
862+
const cmd_object = resolved_objects[@intFromEnum(index)];
863+
864+
const gop = map.getOrPutAssumeCapacity(dylib.id.?);
865+
866+
if (!gop.found_existing) continue;
867+
868+
if (cmd_object.tag == .lib) {
869+
self.warn("ignoring duplicate libraries: {}", .{cmd_object});
870+
}
871+
872+
marker.* = true;
873+
}
874+
875+
var i: usize = 0;
876+
while (i < self.dylibs.items.len) {
877+
const index = self.dylibs.items[i];
878+
const marker = marked_dylibs.items[i];
879+
if (marker) {
880+
_ = self.dylibs.orderedRemove(i);
881+
_ = marked_dylibs.orderedRemove(i);
882+
self.files.items(.data)[@intFromEnum(index)].dylib.deinit(self.allocator);
883+
self.files.set(@intFromEnum(index), .none);
884+
} else i += 1;
885+
}
886+
}
887+
835888
fn parseDependentDylibs(
836889
self: *MachO,
837890
arena: Allocator,

test/macho.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,7 @@ fn testDedupDylibs(b: *Build, opts: Options) *Step {
407407
"-install_name",
408408
"@rpath/liba.dylib",
409409
"-lSystem",
410+
"-lSystem",
410411
"-lc",
411412
});
412413

0 commit comments

Comments
 (0)