Skip to content

Commit efc5c97

Browse files
committed
macho: implement -dead_strip_dylibs linker flag
1 parent a76775b commit efc5c97

File tree

13 files changed

+94
-47
lines changed

13 files changed

+94
-47
lines changed

lib/std/build.zig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,9 @@ pub const LibExeObjStep = struct {
16011601
/// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN.
16021602
headerpad_max_install_names: bool = false,
16031603

1604+
/// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols.
1605+
dead_strip_dylibs: bool = false,
1606+
16041607
/// Position Independent Code
16051608
force_pic: ?bool = null,
16061609

@@ -2676,6 +2679,9 @@ pub const LibExeObjStep = struct {
26762679
if (self.headerpad_max_install_names) {
26772680
try zig_args.append("-headerpad_max_install_names");
26782681
}
2682+
if (self.dead_strip_dylibs) {
2683+
try zig_args.append("-dead_strip_dylibs");
2684+
}
26792685

26802686
if (self.bundle_compiler_rt) |x| {
26812687
if (x) {

src/Compilation.zig

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,8 @@ pub const InitOptions = struct {
911911
headerpad_size: ?u32 = null,
912912
/// (Darwin) set enough space as if all paths were MATPATHLEN
913913
headerpad_max_install_names: bool = false,
914+
/// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
915+
dead_strip_dylibs: bool = false,
914916
};
915917

916918
fn addPackageTableToCacheHash(
@@ -1754,6 +1756,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
17541756
.search_strategy = options.search_strategy,
17551757
.headerpad_size = options.headerpad_size,
17561758
.headerpad_max_install_names = options.headerpad_max_install_names,
1759+
.dead_strip_dylibs = options.dead_strip_dylibs,
17571760
});
17581761
errdefer bin_file.destroy();
17591762
comp.* = .{
@@ -2369,7 +2372,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
23692372
/// to remind the programmer to update multiple related pieces of code that
23702373
/// are in different locations. Bump this number when adding or deleting
23712374
/// anything from the link cache manifest.
2372-
pub const link_hash_implementation_version = 6;
2375+
pub const link_hash_implementation_version = 7;
23732376

23742377
fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
23752378
const gpa = comp.gpa;
@@ -2379,7 +2382,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
23792382
defer arena_allocator.deinit();
23802383
const arena = arena_allocator.allocator();
23812384

2382-
comptime assert(link_hash_implementation_version == 6);
2385+
comptime assert(link_hash_implementation_version == 7);
23832386

23842387
if (comp.bin_file.options.module) |mod| {
23852388
const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{
@@ -2488,6 +2491,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
24882491
man.hash.addOptional(comp.bin_file.options.search_strategy);
24892492
man.hash.addOptional(comp.bin_file.options.headerpad_size);
24902493
man.hash.add(comp.bin_file.options.headerpad_max_install_names);
2494+
man.hash.add(comp.bin_file.options.dead_strip_dylibs);
24912495

24922496
// COFF specific stuff
24932497
man.hash.addOptional(comp.bin_file.options.subsystem);

src/link.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ pub const Options = struct {
199199
/// (Darwin) set enough space as if all paths were MATPATHLEN
200200
headerpad_max_install_names: bool = false,
201201

202+
/// (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
203+
dead_strip_dylibs: bool = false,
204+
202205
pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
203206
return if (options.use_lld) .Obj else options.output_mode;
204207
}

src/link/Coff.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Node) !
969969
man = comp.cache_parent.obtain();
970970
self.base.releaseLock();
971971

972-
comptime assert(Compilation.link_hash_implementation_version == 6);
972+
comptime assert(Compilation.link_hash_implementation_version == 7);
973973

974974
for (self.base.options.objects) |obj| {
975975
_ = try man.addFile(obj.path, null);

src/link/Elf.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1298,7 +1298,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
12981298
// We are about to obtain this lock, so here we give other processes a chance first.
12991299
self.base.releaseLock();
13001300

1301-
comptime assert(Compilation.link_hash_implementation_version == 6);
1301+
comptime assert(Compilation.link_hash_implementation_version == 7);
13021302

13031303
try man.addOptionalFile(self.base.options.linker_script);
13041304
try man.addOptionalFile(self.base.options.version_script);

src/link/MachO.zig

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
541541
// We are about to obtain this lock, so here we give other processes a chance first.
542542
self.base.releaseLock();
543543

544-
comptime assert(Compilation.link_hash_implementation_version == 6);
544+
comptime assert(Compilation.link_hash_implementation_version == 7);
545545

546546
for (self.base.options.objects) |obj| {
547547
_ = try man.addFile(obj.path, null);
@@ -558,6 +558,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
558558
man.hash.addOptional(self.base.options.search_strategy);
559559
man.hash.addOptional(self.base.options.headerpad_size);
560560
man.hash.add(self.base.options.headerpad_max_install_names);
561+
man.hash.add(self.base.options.dead_strip_dylibs);
561562
man.hash.addListOfBytes(self.base.options.lib_dirs);
562563
man.hash.addListOfBytes(self.base.options.framework_dirs);
563564
man.hash.addListOfBytes(self.base.options.frameworks);
@@ -987,6 +988,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
987988
try argv.append("-headerpad_max_install_names");
988989
}
989990

991+
if (self.base.options.dead_strip_dylibs) {
992+
try argv.append("-dead_strip_dylibs");
993+
}
994+
990995
if (self.base.options.entry) |entry| {
991996
try argv.append("-e");
992997
try argv.append(entry);
@@ -1425,7 +1430,12 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy
14251430
try self.dylibs.append(self.base.allocator, dylib);
14261431
try self.dylibs_map.putNoClobber(self.base.allocator, dylib.id.?.name, dylib_id);
14271432

1428-
if (!(opts.is_dependent or self.referenced_dylibs.contains(dylib_id))) {
1433+
const should_link_dylib_even_if_unreachable = blk: {
1434+
if (self.base.options.dead_strip_dylibs) break :blk false;
1435+
break :blk !(opts.is_dependent or self.referenced_dylibs.contains(dylib_id));
1436+
};
1437+
1438+
if (should_link_dylib_even_if_unreachable) {
14291439
try self.addLoadDylibLC(dylib_id);
14301440
try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
14311441
}

src/link/Wasm.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2546,7 +2546,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
25462546
// We are about to obtain this lock, so here we give other processes a chance first.
25472547
self.base.releaseLock();
25482548

2549-
comptime assert(Compilation.link_hash_implementation_version == 6);
2549+
comptime assert(Compilation.link_hash_implementation_version == 7);
25502550

25512551
for (self.base.options.objects) |obj| {
25522552
_ = try man.addFile(obj.path, null);

src/main.zig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ const usage_build_generic =
452452
\\ -search_dylibs_first (Darwin) search `libx.dylib` in each dir in library search paths, then `libx.a`
453453
\\ -headerpad [value] (Darwin) set minimum space for future expansion of the load commands in hexadecimal notation
454454
\\ -headerpad_max_install_names (Darwin) set enough space as if all paths were MAXPATHLEN
455+
\\ -dead_strip_dylibs (Darwin) remove dylibs that are unreachable by the entry point or exported symbols
455456
\\ --import-memory (WebAssembly) import memory from the environment
456457
\\ --import-table (WebAssembly) import function table from the host environment
457458
\\ --export-table (WebAssembly) export function table to the host environment
@@ -703,6 +704,7 @@ fn buildOutputType(
703704
var search_strategy: ?link.File.MachO.SearchStrategy = null;
704705
var headerpad_size: ?u32 = null;
705706
var headerpad_max_install_names: bool = false;
707+
var dead_strip_dylibs: bool = false;
706708

707709
// e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
708710
// This array is populated by zig cc frontend and then has to be converted to zig-style
@@ -937,6 +939,8 @@ fn buildOutputType(
937939
};
938940
} else if (mem.eql(u8, arg, "-headerpad_max_install_names")) {
939941
headerpad_max_install_names = true;
942+
} else if (mem.eql(u8, arg, "-dead_strip_dylibs")) {
943+
dead_strip_dylibs = true;
940944
} else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {
941945
linker_script = args_iter.next() orelse {
942946
fatal("expected parameter after {s}", .{arg});
@@ -1700,6 +1704,8 @@ fn buildOutputType(
17001704
};
17011705
} else if (mem.eql(u8, arg, "-headerpad_max_install_names")) {
17021706
headerpad_max_install_names = true;
1707+
} else if (mem.eql(u8, arg, "-dead_strip_dylibs")) {
1708+
dead_strip_dylibs = true;
17031709
} else if (mem.eql(u8, arg, "--gc-sections")) {
17041710
linker_gc_sections = true;
17051711
} else if (mem.eql(u8, arg, "--no-gc-sections")) {
@@ -2821,6 +2827,7 @@ fn buildOutputType(
28212827
.search_strategy = search_strategy,
28222828
.headerpad_size = headerpad_size,
28232829
.headerpad_max_install_names = headerpad_max_install_names,
2830+
.dead_strip_dylibs = dead_strip_dylibs,
28242831
}) catch |err| switch (err) {
28252832
error.LibCUnavailable => {
28262833
const target = target_info.target;

test/link.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
4040
.build_modes = true,
4141
});
4242

43-
cases.addBuildFile("test/link/macho/frameworks/build.zig", .{
43+
cases.addBuildFile("test/link/macho/dead_strip_dylibs/build.zig", .{
4444
.build_modes = true,
4545
.requires_macos_sdk = true,
4646
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const std = @import("std");
2+
const Builder = std.build.Builder;
3+
const LibExeObjectStep = std.build.LibExeObjStep;
4+
5+
pub fn build(b: *Builder) void {
6+
const mode = b.standardReleaseOptions();
7+
8+
const test_step = b.step("test", "Test the program");
9+
10+
{
11+
// Without -dead_strip_dylibs we expect `-la` to include liba.dylib in the final executable
12+
const exe = createScenario(b, mode);
13+
14+
const check = exe.checkObject(.macho);
15+
check.checkStart("cmd LOAD_DYLIB");
16+
check.checkNext("name {*}Cocoa");
17+
18+
check.checkStart("cmd LOAD_DYLIB");
19+
check.checkNext("name {*}libobjc{*}.dylib");
20+
21+
test_step.dependOn(&check.step);
22+
23+
const run_cmd = exe.run();
24+
test_step.dependOn(&run_cmd.step);
25+
}
26+
27+
{
28+
// With -dead_strip_dylibs, we should include liba.dylib as it's unreachable
29+
const exe = createScenario(b, mode);
30+
exe.dead_strip_dylibs = true;
31+
32+
const run_cmd = exe.run();
33+
run_cmd.expected_exit_code = @bitCast(u8, @as(i8, -2)); // should fail
34+
test_step.dependOn(&run_cmd.step);
35+
}
36+
}
37+
38+
fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {
39+
const exe = b.addExecutable("test", null);
40+
b.default_step.dependOn(&exe.step);
41+
exe.addCSourceFile("main.c", &[0][]const u8{});
42+
exe.setBuildMode(mode);
43+
exe.linkLibC();
44+
exe.linkFramework("Cocoa");
45+
return exe;
46+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <objc/runtime.h>
2+
3+
int main() {
4+
if (objc_getClass("NSObject") == 0) {
5+
return -1;
6+
}
7+
if (objc_getClass("NSApplication") == 0) {
8+
return -2;
9+
}
10+
}

test/link/macho/frameworks/build.zig

Lines changed: 0 additions & 32 deletions
This file was deleted.

test/link/macho/frameworks/main.c

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)