authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-27 09:24:18+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-27 19:53:38+02:00
logefc5c97bff87d4c28ae9642fe69d9bc2c7e9eeb7
tree12b31bae7ed111c1f62541c7d46f20ad6d222717
parenta76775b50a65fd0ea0fd17d6ef3c42058df13997

macho: implement -dead_strip_dylibs linker flag


13 files changed, 94 insertions(+), 47 deletions(-)

lib/std/build.zig+6
......@@ -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+6-2
......@@ -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
916918fn 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.
2372pub const link_hash_implementation_version = 6;
2375pub const link_hash_implementation_version = 7;
23732376
23742377fn 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+3
......@@ -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+1-1
......@@ -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+1-1
......@@ -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+12-2
......@@ -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+1-1
......@@ -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+7
......@@ -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+1-1
......@@ -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 });
test/link/macho/dead_strip_dylibs/build.zig created+46
......@@ -0,0 +1,46 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3const LibExeObjectStep = std.build.LibExeObjStep;
4
5pub 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
38fn 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}
test/link/macho/dead_strip_dylibs/main.c created+10
......@@ -0,0 +1,10 @@
1#include <objc/runtime.h>
2
3int 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 deleted-32
......@@ -1,32 +0,0 @@
1const std = @import("std");
2const Builder = std.build.Builder;
3
4pub fn build(b: *Builder) void {
5 const mode = b.standardReleaseOptions();
6
7 const test_step = b.step("test", "Test the program");
8
9 const exe = b.addExecutable("test", null);
10 b.default_step.dependOn(&exe.step);
11 exe.addCSourceFile("main.c", &[0][]const u8{});
12 exe.setBuildMode(mode);
13 exe.linkLibC();
14 exe.linkFramework("Cocoa");
15
16 const check = exe.checkObject(.macho);
17 check.checkStart("cmd LOAD_DYLIB");
18 check.checkNext("name {*}Cocoa");
19
20 switch (mode) {
21 .Debug, .ReleaseSafe => {
22 check.checkStart("cmd LOAD_DYLIB");
23 check.checkNext("name {*}libobjc{*}.dylib");
24 },
25 else => {},
26 }
27
28 test_step.dependOn(&check.step);
29
30 const run_cmd = exe.run();
31 test_step.dependOn(&run_cmd.step);
32}
test/link/macho/frameworks/main.c deleted-7
......@@ -1,7 +0,0 @@
1#include <assert.h>
2#include <objc/runtime.h>
3
4int main() {
5 assert(objc_getClass("NSObject") > 0);
6 assert(objc_getClass("NSApplication") > 0);
7}