Commit 9b5a463111

Jakub Konka <kubkon@jakubkonka.com>
2021-06-28 17:42:59
zld: if libSystem.dylib found, then need to link libc.dylib too
1 parent eca12b7
Changed files (3)
src/link/MachO/Dylib.zig
@@ -369,10 +369,11 @@ fn parseSymbols(self: *Dylib) !void {
     _ = try self.file.?.preadAll(strtab, symtab_cmd.stroff + self.library_offset);
 
     for (slice) |sym| {
-        const sym_name = mem.spanZ(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx));
+        const add_to_symtab = Symbol.isExt(sym) and (Symbol.isSect(sym) or Symbol.isIndr(sym));
 
-        if (!(Symbol.isSect(sym) and Symbol.isExt(sym))) continue;
+        if (!add_to_symtab) continue;
 
+        const sym_name = mem.spanZ(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx));
         const name = try self.allocator.dupe(u8, sym_name);
         try self.symbols.putNoClobber(self.allocator, name, {});
     }
src/link/MachO/Zld.zig
@@ -1644,7 +1644,7 @@ fn resolveSymbols(self: *Zld) !void {
     loop: while (unresolved.popOrNull()) |undef| {
         const proxy = self.imports.get(undef.name) orelse outer: {
             const proxy = inner: {
-                for (self.dylibs.items) |dylib, i| {
+                for (self.dylibs.items) |dylib| {
                     const proxy = (try dylib.createProxy(undef.name)) orelse continue;
                     try referenced.put(dylib, {});
                     break :inner proxy;
src/link/MachO.zig
@@ -514,140 +514,83 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
     }
 }
 
-const LibKind = enum {
-    lib,
-    framework,
-};
-
-fn resolveDirs(
+fn resolveSearchDir(
     arena: *Allocator,
-    resolved_dirs: *std.ArrayList([]const u8),
+    dir: []const u8,
     syslibroot: ?[]const u8,
-    search_dirs: []const []const u8,
-    lib_kind: LibKind,
-) !void {
-    for (search_dirs) |dir| {
-        if (fs.path.isAbsolute(dir)) {
-            var candidates = std.ArrayList([]const u8).init(arena);
-            if (syslibroot) |root| {
-                const full_path = try fs.path.join(arena, &[_][]const u8{ root, dir });
-                try candidates.append(full_path);
-            }
-            try candidates.append(dir);
-
-            var found = false;
-            for (candidates.items) |candidate| {
-                // Verify that search path actually exists
-                var tmp = fs.cwd().openDir(candidate, .{}) catch |err| switch (err) {
-                    error.FileNotFound => continue,
-                    else => |e| return e,
-                };
-                defer tmp.close();
+) !?[]const u8 {
+    var candidates = std.ArrayList([]const u8).init(arena);
 
-                try resolved_dirs.append(candidate);
-                found = true;
-                break;
-            }
+    if (fs.path.isAbsolute(dir)) {
+        if (syslibroot) |root| {
+            const full_path = try fs.path.join(arena, &[_][]const u8{ root, dir });
+            try candidates.append(full_path);
+        }
+    }
 
-            if (!found) {
-                switch (lib_kind) {
-                    .lib => log.warn("directory not found for '-L{s}'", .{dir}),
-                    .framework => log.warn("directory not found for '-F{s}'", .{dir}),
-                }
-            }
-        } else {
-            // Verify that search path actually exists
-            var tmp = fs.cwd().openDir(dir, .{}) catch |err| switch (err) {
-                error.FileNotFound => {
-                    switch (lib_kind) {
-                        .lib => log.warn("directory not found for '-L{s}'", .{dir}),
-                        .framework => log.warn("directory not found for '-F{s}'", .{dir}),
-                    }
-                    continue;
-                },
-                else => |e| return e,
-            };
-            defer tmp.close();
+    try candidates.append(dir);
 
-            try resolved_dirs.append(dir);
-        }
+    for (candidates.items) |candidate| {
+        // Verify that search path actually exists
+        var tmp = fs.cwd().openDir(candidate, .{}) catch |err| switch (err) {
+            error.FileNotFound => continue,
+            else => |e| return e,
+        };
+        defer tmp.close();
+
+        return candidate;
     }
+
+    return null;
 }
 
 fn resolveLib(
     arena: *Allocator,
-    lib_dirs: []const []const u8,
-    lib_name: []const u8,
-    lib_kind: LibKind,
+    search_dirs: []const []const u8,
+    name: []const u8,
+    ext: []const u8,
 ) !?[]const u8 {
-    // Assume ld64 default: -search_paths_first
-    // Look in each directory for a dylib (next, tbd), and then for archive
-    // TODO implement alternative: -search_dylibs_first
-    const exts = switch (lib_kind) {
-        .lib => &[_]?[]const u8{ "dylib", "tbd", "a" },
-        .framework => &[_]?[]const u8{ null, "dylib", "tbd" },
-    };
-
-    for (exts) |ext| {
-        const lib_name_ext = if (ext) |some|
-            try std.fmt.allocPrint(arena, "{s}.{s}", .{ lib_name, some })
-        else
-            lib_name;
-        const with_prefix = blk: {
-            switch (lib_kind) {
-                .lib => {
-                    break :blk try std.fmt.allocPrint(arena, "lib{s}", .{lib_name_ext});
-                },
-                .framework => {
-                    const prefix = try std.fmt.allocPrint(arena, "{s}.framework", .{lib_name});
-                    break :blk try fs.path.join(arena, &[_][]const u8{ prefix, lib_name_ext });
-                },
-            }
-        };
+    const search_name = try std.fmt.allocPrint(arena, "lib{s}{s}", .{ name, ext });
 
-        for (lib_dirs) |dir| {
-            const full_path = try fs.path.join(arena, &[_][]const u8{ dir, with_prefix });
+    for (search_dirs) |dir| {
+        const full_path = try fs.path.join(arena, &[_][]const u8{ dir, search_name });
 
-            // Check if the lib file exists.
-            const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
-                error.FileNotFound => continue,
-                else => |e| return e,
-            };
-            defer tmp.close();
+        // Check if the file exists.
+        const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
+            error.FileNotFound => continue,
+            else => |e| return e,
+        };
+        defer tmp.close();
 
-            return full_path;
-        }
+        return full_path;
     }
 
     return null;
 }
 
-fn resolveLibs(
+fn resolveFramework(
     arena: *Allocator,
-    resolved_libs: *std.ArrayList([]const u8),
-    lib_dirs: []const []const u8,
-    lib_names: []const []const u8,
-    lib_kind: LibKind,
-) !void {
-    for (lib_names) |lib_name| {
-        if (try resolveLib(arena, lib_dirs, lib_name, lib_kind)) |full_path| {
-            try resolved_libs.append(full_path);
-        } else {
-            switch (lib_kind) {
-                .lib => {
-                    log.warn("library not found for '-l{s}'", .{lib_name});
-                    log.warn("Library search paths:", .{});
-                },
-                .framework => {
-                    log.warn("framework not found for '-f{s}'", .{lib_name});
-                    log.warn("Framework search paths:", .{});
-                },
-            }
-            for (lib_dirs) |dir| {
-                log.warn("  {s}", .{dir});
-            }
-        }
+    search_dirs: []const []const u8,
+    name: []const u8,
+    ext: []const u8,
+) !?[]const u8 {
+    const search_name = try std.fmt.allocPrint(arena, "{s}{s}", .{ name, ext });
+    const prefix_path = try std.fmt.allocPrint(arena, "{s}.framework", .{name});
+
+    for (search_dirs) |dir| {
+        const full_path = try fs.path.join(arena, &[_][]const u8{ dir, prefix_path, search_name });
+
+        // Check if the file exists.
+        const tmp = fs.cwd().openFile(full_path, .{}) catch |err| switch (err) {
+            error.FileNotFound => continue,
+            else => |e| return e,
+        };
+        defer tmp.close();
+
+        return full_path;
     }
+
+    return null;
 }
 
 fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
@@ -852,56 +795,96 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
             }
 
             var lib_dirs = std.ArrayList([]const u8).init(arena);
-            try resolveDirs(
-                arena,
-                &lib_dirs,
-                self.base.options.sysroot,
-                self.base.options.lib_dirs,
-                .lib,
-            );
+            for (self.base.options.lib_dirs) |dir| {
+                if (try resolveSearchDir(arena, dir, self.base.options.sysroot)) |search_dir| {
+                    try lib_dirs.append(search_dir);
+                } else {
+                    log.warn("directory not found for '-L{s}'", .{dir});
+                }
+            }
 
             var libs = std.ArrayList([]const u8).init(arena);
-            try resolveLibs(
-                arena,
-                &libs,
-                lib_dirs.items,
-                search_lib_names.items,
-                .lib,
-            );
+            var lib_not_found = false;
+            for (search_lib_names.items) |lib_name| {
+                // Assume ld64 default: -search_paths_first
+                // Look in each directory for a dylib (stub first), and then for archive
+                // TODO implement alternative: -search_dylibs_first
+                for (&[_][]const u8{ ".tbd", ".dylib", ".a" }) |ext| {
+                    if (try resolveLib(arena, lib_dirs.items, lib_name, ext)) |full_path| {
+                        try libs.append(full_path);
+                        break;
+                    }
+                } else {
+                    log.warn("library not found for '-l{s}'", .{lib_name});
+                    lib_not_found = true;
+                }
+            }
+
+            if (lib_not_found) {
+                log.warn("Library search paths:", .{});
+                for (lib_dirs.items) |dir| {
+                    log.warn("  {s}", .{dir});
+                }
+            }
 
             // If we're compiling native and we can find libSystem.B.{dylib, tbd},
             // we link against that instead of embedded libSystem.B.tbd file.
-            var link_native_libsystem = false;
-            if (self.base.options.is_native_os) {
-                if (try resolveLib(arena, lib_dirs.items, "System", .lib)) |full_path| {
+            var native_libsystem_available = false;
+            if (self.base.options.is_native_os) blk: {
+                // Try stub file first. If we hit it, then we're done as the stub file
+                // re-exports every single symbol definition.
+                if (try resolveLib(arena, lib_dirs.items, "System", ".tbd")) |full_path| {
                     try libs.append(full_path);
-                    link_native_libsystem = true;
+                    native_libsystem_available = true;
+                    break :blk;
+                }
+                // If we didn't hit the stub file, try .dylib next. However, libSystem.dylib
+                // doesn't export libc.dylib which we'll need to resolve subsequently also.
+                if (try resolveLib(arena, lib_dirs.items, "System", ".dylib")) |libsystem_path| {
+                    if (try resolveLib(arena, lib_dirs.items, "c", ".dylib")) |libc_path| {
+                        try libs.append(libsystem_path);
+                        try libs.append(libc_path);
+                        native_libsystem_available = true;
+                        break :blk;
+                    }
                 }
             }
-            if (!link_native_libsystem) {
+            if (!native_libsystem_available) {
                 const full_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{
                     "libc", "darwin", "libSystem.B.tbd",
                 });
-                try positionals.append(full_path);
+                try libs.append(full_path);
             }
 
             // frameworks
             var framework_dirs = std.ArrayList([]const u8).init(arena);
-            try resolveDirs(
-                arena,
-                &framework_dirs,
-                self.base.options.sysroot,
-                self.base.options.framework_dirs,
-                .framework,
-            );
+            for (self.base.options.framework_dirs) |dir| {
+                if (try resolveSearchDir(arena, dir, self.base.options.sysroot)) |search_dir| {
+                    try framework_dirs.append(search_dir);
+                } else {
+                    log.warn("directory not found for '-F{s}'", .{dir});
+                }
+            }
 
-            try resolveLibs(
-                arena,
-                &libs,
-                framework_dirs.items,
-                self.base.options.frameworks,
-                .framework,
-            );
+            var framework_not_found = false;
+            for (self.base.options.frameworks) |framework| {
+                for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| {
+                    if (try resolveFramework(arena, framework_dirs.items, framework, ext)) |full_path| {
+                        try libs.append(full_path);
+                        break;
+                    }
+                } else {
+                    log.warn("framework not found for '-f{s}'", .{framework});
+                    framework_not_found = true;
+                }
+            }
+
+            if (framework_not_found) {
+                log.warn("Framework search paths:", .{});
+                for (framework_dirs.items) |dir| {
+                    log.warn("  {s}", .{dir});
+                }
+            }
 
             // rpaths
             var rpath_table = std.StringArrayHashMap(void).init(arena);
@@ -937,8 +920,9 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
                 try argv.append("-o");
                 try argv.append(full_out_path);
 
-                if (link_native_libsystem) {
+                if (native_libsystem_available) {
                     try argv.append("-lSystem");
+                    try argv.append("-lc");
                 }
 
                 for (search_lib_names.items) |l_name| {