authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-06-25 17:55:26+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-06-25 17:55:26+02:00
log0078d36ff39f2ef35e32f2d58e5d447b62f3a37e
tree37793e2032189f83915a829dac0bd425264b488e
parent905a18849f7f2c3f269fbf425170e0c86b12524a
parent8f00bc9d231c0c686b2ffa3bd0b14181afa7a171
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #11917 from motiejus/wl-search-paths

macho: implement `-search_paths_first` and `-search_dylibs_first`

12 files changed, 233 insertions(+), 63 deletions(-)

lib/std/build.zig+11
......@@ -1586,6 +1586,13 @@ pub const LibExeObjStep = struct {
15861586 /// (Darwin) Size of the pagezero segment.
15871587 pagezero_size: ?u64 = null,
15881588
1589 /// (Darwin) Search strategy for searching system libraries. Either `paths_first` or `dylibs_first`.
1590 /// The former lowers to `-search_paths_first` linker option, while the latter to `-search_dylibs_first`
1591 /// option.
1592 /// By default, if no option is specified, the linker assumes `paths_first` as the default
1593 /// search strategy.
1594 search_strategy: ?enum { paths_first, dylibs_first } = null,
1595
15891596 /// Position Independent Code
15901597 force_pic: ?bool = null,
15911598
......@@ -2650,6 +2657,10 @@ pub const LibExeObjStep = struct {
26502657 const size = try std.fmt.allocPrint(builder.allocator, "{x}", .{pagezero_size});
26512658 try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size });
26522659 }
2660 if (self.search_strategy) |strat| switch (strat) {
2661 .paths_first => try zig_args.append("-search_paths_first"),
2662 .dylibs_first => try zig_args.append("-search_dylibs_first"),
2663 };
26532664
26542665 if (self.bundle_compiler_rt) |x| {
26552666 if (x) {
src/Compilation.zig+6-2
......@@ -905,6 +905,8 @@ pub const InitOptions = struct {
905905 entitlements: ?[]const u8 = null,
906906 /// (Darwin) size of the __PAGEZERO segment
907907 pagezero_size: ?u64 = null,
908 /// (Darwin) search strategy for system libraries
909 search_strategy: ?link.File.MachO.SearchStrategy = null,
908910};
909911
910912fn addPackageTableToCacheHash(
......@@ -1745,6 +1747,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
17451747 .install_name = options.install_name,
17461748 .entitlements = options.entitlements,
17471749 .pagezero_size = options.pagezero_size,
1750 .search_strategy = options.search_strategy,
17481751 });
17491752 errdefer bin_file.destroy();
17501753 comp.* = .{
......@@ -2360,7 +2363,7 @@ fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemo
23602363/// to remind the programmer to update multiple related pieces of code that
23612364/// are in different locations. Bump this number when adding or deleting
23622365/// anything from the link cache manifest.
2363pub const link_hash_implementation_version = 4;
2366pub const link_hash_implementation_version = 5;
23642367
23652368fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifest) !void {
23662369 const gpa = comp.gpa;
......@@ -2370,7 +2373,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
23702373 defer arena_allocator.deinit();
23712374 const arena = arena_allocator.allocator();
23722375
2373 comptime assert(link_hash_implementation_version == 4);
2376 comptime assert(link_hash_implementation_version == 5);
23742377
23752378 if (comp.bin_file.options.module) |mod| {
23762379 const main_zig_file = try mod.main_pkg.root_src_directory.join(arena, &[_][]const u8{
......@@ -2476,6 +2479,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
24762479 man.hash.addListOfBytes(comp.bin_file.options.frameworks);
24772480 try man.addOptionalFile(comp.bin_file.options.entitlements);
24782481 man.hash.addOptional(comp.bin_file.options.pagezero_size);
2482 man.hash.addOptional(comp.bin_file.options.search_strategy);
24792483
24802484 // COFF specific stuff
24812485 man.hash.addOptional(comp.bin_file.options.subsystem);
src/link.zig+3
......@@ -190,6 +190,9 @@ pub const Options = struct {
190190 /// (Darwin) size of the __PAGEZERO segment
191191 pagezero_size: ?u64 = null,
192192
193 /// (Darwin) search strategy for system libraries
194 search_strategy: ?File.MachO.SearchStrategy = null,
195
193196 pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
194197 return if (options.use_lld) .Obj else options.output_mode;
195198 }
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 == 4);
972 comptime assert(Compilation.link_hash_implementation_version == 5);
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 == 4);
1301 comptime assert(Compilation.link_hash_implementation_version == 5);
13021302
13031303 try man.addOptionalFile(self.base.options.linker_script);
13041304 try man.addOptionalFile(self.base.options.version_script);
src/link/MachO.zig+109-58
......@@ -47,6 +47,11 @@ pub const DebugSymbols = @import("MachO/DebugSymbols.zig");
4747
4848pub const base_tag: File.Tag = File.Tag.macho;
4949
50pub const SearchStrategy = enum {
51 paths_first,
52 dylibs_first,
53};
54
5055base: File,
5156
5257/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
......@@ -536,7 +541,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
536541 // We are about to obtain this lock, so here we give other processes a chance first.
537542 self.base.releaseLock();
538543
539 comptime assert(Compilation.link_hash_implementation_version == 4);
544 comptime assert(Compilation.link_hash_implementation_version == 5);
540545
541546 for (self.base.options.objects) |obj| {
542547 _ = try man.addFile(obj.path, null);
......@@ -550,6 +555,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
550555 // installation sources because they are always a product of the compiler version + target information.
551556 man.hash.add(stack_size);
552557 man.hash.addOptional(self.base.options.pagezero_size);
558 man.hash.addOptional(self.base.options.search_strategy);
553559 man.hash.addListOfBytes(self.base.options.lib_dirs);
554560 man.hash.addListOfBytes(self.base.options.framework_dirs);
555561 man.hash.addListOfBytes(self.base.options.frameworks);
......@@ -784,18 +790,43 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
784790 }
785791
786792 var libs = std.ArrayList([]const u8).init(arena);
787 for (search_lib_names.items) |lib_name| {
788 // Assume ld64 default: -search_paths_first
789 // Look in each directory for a dylib (stub first), and then for archive
790 // TODO implement alternative: -search_dylibs_first
791 for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| {
792 if (try resolveLib(arena, lib_dirs.items, lib_name, ext)) |full_path| {
793 try libs.append(full_path);
794 break;
795 }
796 } else {
797 log.warn("library not found for '-l{s}'", .{lib_name});
798 lib_not_found = true;
793
794 // Assume ld64 default -search_paths_first if no strategy specified.
795 const search_strategy = self.base.options.search_strategy orelse .paths_first;
796 outer: for (search_lib_names.items) |lib_name| {
797 switch (search_strategy) {
798 .paths_first => {
799 // Look in each directory for a dylib (stub first), and then for archive
800 for (lib_dirs.items) |dir| {
801 for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| {
802 if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {
803 try libs.append(full_path);
804 continue :outer;
805 }
806 }
807 } else {
808 log.warn("library not found for '-l{s}'", .{lib_name});
809 lib_not_found = true;
810 }
811 },
812 .dylibs_first => {
813 // First, look for a dylib in each search dir
814 for (lib_dirs.items) |dir| {
815 for (&[_][]const u8{ ".tbd", ".dylib" }) |ext| {
816 if (try resolveLib(arena, dir, lib_name, ext)) |full_path| {
817 try libs.append(full_path);
818 continue :outer;
819 }
820 }
821 } else for (lib_dirs.items) |dir| {
822 if (try resolveLib(arena, dir, lib_name, ".a")) |full_path| {
823 try libs.append(full_path);
824 } else {
825 log.warn("library not found for '-l{s}'", .{lib_name});
826 lib_not_found = true;
827 }
828 }
829 },
799830 }
800831 }
801832
......@@ -811,19 +842,23 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
811842 if (self.base.options.sysroot != null) blk: {
812843 // Try stub file first. If we hit it, then we're done as the stub file
813844 // re-exports every single symbol definition.
814 if (try resolveLib(arena, lib_dirs.items, "System", ".tbd")) |full_path| {
815 try libs.append(full_path);
816 libsystem_available = true;
817 break :blk;
845 for (lib_dirs.items) |dir| {
846 if (try resolveLib(arena, dir, "System", ".tbd")) |full_path| {
847 try libs.append(full_path);
848 libsystem_available = true;
849 break :blk;
850 }
818851 }
819852 // If we didn't hit the stub file, try .dylib next. However, libSystem.dylib
820853 // doesn't export libc.dylib which we'll need to resolve subsequently also.
821 if (try resolveLib(arena, lib_dirs.items, "System", ".dylib")) |libsystem_path| {
822 if (try resolveLib(arena, lib_dirs.items, "c", ".dylib")) |libc_path| {
823 try libs.append(libsystem_path);
824 try libs.append(libc_path);
825 libsystem_available = true;
826 break :blk;
854 for (lib_dirs.items) |dir| {
855 if (try resolveLib(arena, dir, "System", ".dylib")) |libsystem_path| {
856 if (try resolveLib(arena, dir, "c", ".dylib")) |libc_path| {
857 try libs.append(libsystem_path);
858 try libs.append(libc_path);
859 libsystem_available = true;
860 break :blk;
861 }
827862 }
828863 }
829864 }
......@@ -847,11 +882,13 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
847882 }
848883 }
849884
850 for (self.base.options.frameworks) |framework| {
851 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
852 if (try resolveFramework(arena, framework_dirs.items, framework, ext)) |full_path| {
853 try libs.append(full_path);
854 break;
885 outer: for (self.base.options.frameworks) |framework| {
886 for (framework_dirs.items) |dir| {
887 for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
888 if (try resolveFramework(arena, dir, framework, ext)) |full_path| {
889 try libs.append(full_path);
890 continue :outer;
891 }
855892 }
856893 } else {
857894 log.warn("framework not found for '-framework {s}'", .{framework});
......@@ -934,12 +971,36 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
934971 try argv.append(try std.fmt.allocPrint(arena, "0x{x}", .{pagezero_size}));
935972 }
936973
974 if (self.base.options.search_strategy) |strat| switch (strat) {
975 .paths_first => try argv.append("-search_paths_first"),
976 .dylibs_first => try argv.append("-search_dylibs_first"),
977 };
978
937979 if (self.base.options.entry) |entry| {
938980 try argv.append("-e");
939981 try argv.append(entry);
940982 }
941983
942 try argv.appendSlice(positionals.items);
984 for (self.base.options.objects) |obj| {
985 try argv.append(obj.path);
986 }
987
988 for (comp.c_object_table.keys()) |key| {
989 try argv.append(key.status.success.object_path);
990 }
991
992 if (module_obj_path) |p| {
993 try argv.append(p);
994 }
995
996 if (comp.compiler_rt_lib) |lib| {
997 try argv.append(lib.full_object_path);
998 }
999
1000 if (self.base.options.link_libcpp) {
1001 try argv.append(comp.libcxxabi_static_lib.?.full_object_path);
1002 try argv.append(comp.libcxx_static_lib.?.full_object_path);
1003 }
9431004
9441005 try argv.append("-o");
9451006 try argv.append(full_out_path);
......@@ -947,7 +1008,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation, prog_node: *std.Progress.No
9471008 try argv.append("-lSystem");
9481009 try argv.append("-lc");
9491010
950 for (search_lib_names.items) |l_name| {
1011 for (self.base.options.system_libs.keys()) |l_name| {
9511012 try argv.append(try std.fmt.allocPrint(arena, "-l{s}", .{l_name}));
9521013 }
9531014
......@@ -1183,51 +1244,41 @@ fn resolveSearchDir(
11831244
11841245fn resolveLib(
11851246 arena: Allocator,
1186 search_dirs: []const []const u8,
1247 search_dir: []const u8,
11871248 name: []const u8,
11881249 ext: []const u8,
11891250) !?[]const u8 {
11901251 const search_name = try std.fmt.allocPrint(arena, "lib{s}{s}", .{ name, ext });
1252 const full_path = try fs.path.join(arena, &[_][]const u8{ search_dir, search_name });
11911253
1192 for (search_dirs) |dir| {
1193 const full_path = try fs.path.join(arena, &[_][]const u8{ dir, search_name });
1194
1195 // Check if the file exists.
1196 const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
1197 error.FileNotFound => continue,
1198 else => |e| return e,
1199 };
1200 defer tmp.close();
1201
1202 return full_path;
1203 }
1254 // Check if the file exists.
1255 const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
1256 error.FileNotFound => return null,
1257 else => |e| return e,
1258 };
1259 defer tmp.close();
12041260
1205 return null;
1261 return full_path;
12061262}
12071263
12081264fn resolveFramework(
12091265 arena: Allocator,
1210 search_dirs: []const []const u8,
1266 search_dir: []const u8,
12111267 name: []const u8,
12121268 ext: []const u8,
12131269) !?[]const u8 {
12141270 const search_name = try std.fmt.allocPrint(arena, "{s}{s}", .{ name, ext });
12151271 const prefix_path = try std.fmt.allocPrint(arena, "{s}.framework", .{name});
1272 const full_path = try fs.path.join(arena, &[_][]const u8{ search_dir, prefix_path, search_name });
12161273
1217 for (search_dirs) |dir| {
1218 const full_path = try fs.path.join(arena, &[_][]const u8{ dir, prefix_path, search_name });
1219
1220 // Check if the file exists.
1221 const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
1222 error.FileNotFound => continue,
1223 else => |e| return e,
1224 };
1225 defer tmp.close();
1226
1227 return full_path;
1228 }
1274 // Check if the file exists.
1275 const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
1276 error.FileNotFound => return null,
1277 else => |e| return e,
1278 };
1279 defer tmp.close();
12291280
1230 return null;
1281 return full_path;
12311282}
12321283
12331284fn parseObject(self: *MachO, path: []const u8) !bool {
src/link/Wasm.zig+1-1
......@@ -2481,7 +2481,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
24812481 // We are about to obtain this lock, so here we give other processes a chance first.
24822482 self.base.releaseLock();
24832483
2484 comptime assert(Compilation.link_hash_implementation_version == 4);
2484 comptime assert(Compilation.link_hash_implementation_version == 5);
24852485
24862486 for (self.base.options.objects) |obj| {
24872487 _ = try man.addFile(obj.path, null);
src/main.zig+13
......@@ -448,6 +448,8 @@ const usage_build_generic =
448448 \\ -install_name=[value] (Darwin) add dylib's install name
449449 \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature
450450 \\ -pagezero_size [value] (Darwin) size of the __PAGEZERO segment in hexadecimal notation
451 \\ -search_paths_first (Darwin) search each dir in library search paths for `libx.dylib` then `libx.a`
452 \\ -search_dylibs_first (Darwin) search `libx.dylib` in each dir in library search paths, then `libx.a`
451453 \\ --import-memory (WebAssembly) import memory from the environment
452454 \\ --import-table (WebAssembly) import function table from the host environment
453455 \\ --export-table (WebAssembly) export function table to the host environment
......@@ -696,6 +698,7 @@ fn buildOutputType(
696698 var hash_style: link.HashStyle = .both;
697699 var entitlements: ?[]const u8 = null;
698700 var pagezero_size: ?u64 = null;
701 var search_strategy: ?link.File.MachO.SearchStrategy = null;
699702
700703 // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
701704 // This array is populated by zig cc frontend and then has to be converted to zig-style
......@@ -917,6 +920,10 @@ fn buildOutputType(
917920 pagezero_size = std.fmt.parseUnsigned(u64, eatIntPrefix(next_arg, 16), 16) catch |err| {
918921 fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) });
919922 };
923 } else if (mem.eql(u8, arg, "-search_paths_first")) {
924 search_strategy = .paths_first;
925 } else if (mem.eql(u8, arg, "-search_dylibs_first")) {
926 search_strategy = .dylibs_first;
920927 } else if (mem.eql(u8, arg, "-T") or mem.eql(u8, arg, "--script")) {
921928 linker_script = args_iter.next() orelse {
922929 fatal("expected parameter after {s}", .{arg});
......@@ -1475,6 +1482,10 @@ fn buildOutputType(
14751482 mem.eql(u8, linker_arg, "-static"))
14761483 {
14771484 force_static_libs = true;
1485 } else if (mem.eql(u8, linker_arg, "-search_paths_first")) {
1486 search_strategy = .paths_first;
1487 } else if (mem.eql(u8, linker_arg, "-search_dylibs_first")) {
1488 search_strategy = .dylibs_first;
14781489 } else {
14791490 try linker_args.append(linker_arg);
14801491 }
......@@ -2141,6 +2152,7 @@ fn buildOutputType(
21412152 }
21422153
21432154 for (lib_dirs.items) |lib_dir_path| {
2155 if (cross_target.isDarwin()) break; // Targeting Darwin we let the linker resolve the libraries in the correct order
21442156 test_path.clearRetainingCapacity();
21452157 try test_path.writer().print("{s}" ++ sep ++ "{s}{s}{s}", .{
21462158 lib_dir_path,
......@@ -2782,6 +2794,7 @@ fn buildOutputType(
27822794 .install_name = install_name,
27832795 .entitlements = entitlements,
27842796 .pagezero_size = pagezero_size,
2797 .search_strategy = search_strategy,
27852798 }) catch |err| switch (err) {
27862799 error.LibCUnavailable => {
27872800 const target = target_info.target;
test/link.zig+4
......@@ -60,5 +60,9 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
6060 cases.addBuildFile("test/link/macho/stack_size/build.zig", .{
6161 .build_modes = true,
6262 });
63
64 cases.addBuildFile("test/link/macho/search_strategy/build.zig", .{
65 .build_modes = true,
66 });
6367 }
6468}
test/link/macho/search_strategy/a.c created+7
......@@ -0,0 +1,7 @@
1#include <stdio.h>
2
3char world[] = "world";
4
5char* hello() {
6 return "Hello";
7}
test/link/macho/search_strategy/build.zig created+68
......@@ -0,0 +1,68 @@
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");
9 test_step.dependOn(b.getInstallStep());
10
11 {
12 // -search_dylibs_first
13 const exe = createScenario(b, mode);
14 exe.search_strategy = .dylibs_first;
15
16 const check = exe.checkObject(.macho);
17 check.checkStart("cmd LOAD_DYLIB");
18 check.checkNext("name @rpath/liba.dylib");
19
20 test_step.dependOn(&check.step);
21
22 const run = exe.run();
23 run.cwd = b.pathFromRoot(".");
24 run.expectStdOutEqual("Hello world");
25 test_step.dependOn(&run.step);
26 }
27
28 {
29 // -search_paths_first
30 const exe = createScenario(b, mode);
31 exe.search_strategy = .paths_first;
32
33 const run = exe.run();
34 run.cwd = b.pathFromRoot(".");
35 run.expectStdOutEqual("Hello world");
36 test_step.dependOn(&run.step);
37 }
38}
39
40fn createScenario(b: *Builder, mode: std.builtin.Mode) *LibExeObjectStep {
41 const static = b.addStaticLibrary("a", null);
42 static.setBuildMode(mode);
43 static.addCSourceFile("a.c", &.{});
44 static.linkLibC();
45 static.override_dest_dir = std.build.InstallDir{
46 .custom = "static",
47 };
48 static.install();
49
50 const dylib = b.addSharedLibrary("a", null, b.version(1, 0, 0));
51 dylib.setBuildMode(mode);
52 dylib.addCSourceFile("a.c", &.{});
53 dylib.linkLibC();
54 dylib.override_dest_dir = std.build.InstallDir{
55 .custom = "dynamic",
56 };
57 dylib.install();
58
59 const exe = b.addExecutable("main", null);
60 exe.setBuildMode(mode);
61 exe.addCSourceFile("main.c", &.{});
62 exe.linkSystemLibraryName("a");
63 exe.linkLibC();
64 exe.addLibraryPath(b.pathFromRoot("zig-out/static"));
65 exe.addLibraryPath(b.pathFromRoot("zig-out/dynamic"));
66 exe.addRPath(b.pathFromRoot("zig-out/dynamic"));
67 return exe;
68}
test/link/macho/search_strategy/main.c created+9
......@@ -0,0 +1,9 @@
1#include <stdio.h>
2
3char* hello();
4extern char world[];
5
6int main() {
7 printf("%s %s", hello(), world);
8 return 0;
9}