Commit de8e612455

Jakub Konka <kubkon@jakubkonka.com>
2021-11-23 12:59:58
zld: resolve frameworks in BFS order
Handle clang's linker flag `-weak_framework` as a standard framework to link. This requires further investigation especially to do with weak imports and how to tie one with the other.
1 parent e08b614
Changed files (3)
src/link/MachO/Dylib.zig
@@ -38,9 +38,6 @@ id: ?Id = null,
 /// a symbol is referenced by an object file.
 symbols: std.StringArrayHashMapUnmanaged(void) = .{},
 
-/// Array list of all dependent libs of this dylib.
-dependent_libs: std.ArrayListUnmanaged(Id) = .{},
-
 pub const Id = struct {
     name: []const u8,
     timestamp: u32,
@@ -139,10 +136,6 @@ pub fn deinit(self: *Dylib, allocator: *Allocator) void {
     }
     self.symbols.deinit(allocator);
 
-    for (self.dependent_libs.items) |*id| {
-        id.deinit(allocator);
-    }
-    self.dependent_libs.deinit(allocator);
     allocator.free(self.name);
 
     if (self.id) |*id| {
@@ -150,7 +143,7 @@ pub fn deinit(self: *Dylib, allocator: *Allocator) void {
     }
 }
 
-pub fn parse(self: *Dylib, allocator: *Allocator, target: std.Target) !void {
+pub fn parse(self: *Dylib, allocator: *Allocator, target: std.Target, dependent_libs: anytype) !void {
     log.debug("parsing shared library '{s}'", .{self.name});
 
     self.library_offset = try fat.getLibraryOffset(self.file.reader(), target);
@@ -172,12 +165,12 @@ pub fn parse(self: *Dylib, allocator: *Allocator, target: std.Target) !void {
         return error.MismatchedCpuArchitecture;
     }
 
-    try self.readLoadCommands(allocator, reader);
+    try self.readLoadCommands(allocator, reader, dependent_libs);
     try self.parseId(allocator);
     try self.parseSymbols(allocator);
 }
 
-fn readLoadCommands(self: *Dylib, allocator: *Allocator, reader: anytype) !void {
+fn readLoadCommands(self: *Dylib, allocator: *Allocator, reader: anytype, dependent_libs: anytype) !void {
     const should_lookup_reexports = self.header.?.flags & macho.MH_NO_REEXPORTED_DYLIBS == 0;
 
     try self.load_commands.ensureUnusedCapacity(allocator, self.header.?.ncmds);
@@ -198,8 +191,8 @@ fn readLoadCommands(self: *Dylib, allocator: *Allocator, reader: anytype) !void
             macho.LC_REEXPORT_DYLIB => {
                 if (should_lookup_reexports) {
                     // Parse install_name to dependent dylib.
-                    const id = try Id.fromLoadCommand(allocator, cmd.Dylib);
-                    try self.dependent_libs.append(allocator, id);
+                    var id = try Id.fromLoadCommand(allocator, cmd.Dylib);
+                    try dependent_libs.writeItem(id);
                 }
             },
             else => {
@@ -341,7 +334,13 @@ const TargetMatcher = struct {
     }
 };
 
-pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, lib_stub: LibStub) !void {
+pub fn parseFromStub(
+    self: *Dylib,
+    allocator: *Allocator,
+    target: std.Target,
+    lib_stub: LibStub,
+    dependent_libs: anytype,
+) !void {
     if (lib_stub.inner.len == 0) return error.EmptyStubFile;
 
     log.debug("parsing shared library from stub '{s}'", .{self.name});
@@ -416,8 +415,8 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
 
                                 log.debug("  (found re-export '{s}')", .{lib});
 
-                                const dep_id = try Id.default(allocator, lib);
-                                try self.dependent_libs.append(allocator, dep_id);
+                                var dep_id = try Id.default(allocator, lib);
+                                try dependent_libs.writeItem(dep_id);
                             }
                         }
                     }
@@ -521,55 +520,10 @@ pub fn parseFromStub(self: *Dylib, allocator: *Allocator, target: std.Target, li
 
                     log.debug("  (found re-export '{s}')", .{lib});
 
-                    const dep_id = try Id.default(allocator, lib);
-                    try self.dependent_libs.append(allocator, dep_id);
+                    var dep_id = try Id.default(allocator, lib);
+                    try dependent_libs.writeItem(dep_id);
                 }
             }
         }
     }
 }
-
-pub fn parseDependentLibs(
-    self: *Dylib,
-    macho_file: *MachO,
-    syslibroot: ?[]const u8,
-) !void {
-    outer: for (self.dependent_libs.items) |id| {
-        if (macho_file.dylibs_map.contains(id.name)) continue :outer;
-
-        const has_ext = blk: {
-            const basename = fs.path.basename(id.name);
-            break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
-        };
-        const extension = if (has_ext) fs.path.extension(id.name) else "";
-        const without_ext = if (has_ext) blk: {
-            const index = mem.lastIndexOfScalar(u8, id.name, '.') orelse unreachable;
-            break :blk id.name[0..index];
-        } else id.name;
-
-        for (&[_][]const u8{ extension, ".tbd" }) |ext| {
-            const with_ext = try std.fmt.allocPrint(macho_file.base.allocator, "{s}{s}", .{
-                without_ext,
-                ext,
-            });
-            defer macho_file.base.allocator.free(with_ext);
-
-            const full_path = if (syslibroot) |root|
-                try fs.path.join(macho_file.base.allocator, &.{ root, with_ext })
-            else
-                with_ext;
-            defer if (syslibroot) |_| macho_file.base.allocator.free(full_path);
-
-            log.debug("trying dependency at fully resolved path {s}", .{full_path});
-
-            const did_parse_successfully = try macho_file.parseDylib(full_path, .{
-                .id = id,
-                .syslibroot = syslibroot,
-                .is_dependent = true,
-            });
-            if (!did_parse_successfully) continue;
-        } else {
-            log.debug("unable to resolve dependency {s}", .{id.name});
-        }
-    }
-}
src/link/MachO.zig
@@ -842,8 +842,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
                 Compilation.dump_argv(argv.items);
             }
 
-            try self.parseInputFiles(positionals.items, self.base.options.sysroot);
-            try self.parseLibs(libs.items, self.base.options.sysroot);
+            var dependent_libs = std.fifo.LinearFifo(Dylib.Id, .Dynamic).init(self.base.allocator);
+            defer dependent_libs.deinit();
+            try self.parseInputFiles(positionals.items, self.base.options.sysroot, &dependent_libs);
+            try self.parseLibs(libs.items, self.base.options.sysroot, &dependent_libs);
+            try self.parseDependentLibs(self.base.options.sysroot, &dependent_libs);
         }
 
         if (self.bss_section_index) |idx| {
@@ -1161,7 +1164,8 @@ const ParseDylibError = error{
 } || fs.File.OpenError || std.os.PReadError || Dylib.Id.ParseError;
 
 const DylibCreateOpts = struct {
-    syslibroot: ?[]const u8 = null,
+    syslibroot: ?[]const u8,
+    dependent_libs: *std.fifo.LinearFifo(Dylib.Id, .Dynamic),
     id: ?Dylib.Id = null,
     is_dependent: bool = false,
 };
@@ -1181,7 +1185,7 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy
         .file = file,
     };
 
-    dylib.parse(self.base.allocator, self.base.options.target) catch |err| switch (err) {
+    dylib.parse(self.base.allocator, self.base.options.target, opts.dependent_libs) catch |err| switch (err) {
         error.EndOfStream, error.NotDylib => {
             try file.seekTo(0);
 
@@ -1191,7 +1195,7 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy
             };
             defer lib_stub.deinit();
 
-            try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub);
+            try dylib.parseFromStub(self.base.allocator, self.base.options.target, lib_stub, opts.dependent_libs);
         },
         else => |e| return e,
     };
@@ -1218,14 +1222,10 @@ pub fn parseDylib(self: *MachO, path: []const u8, opts: DylibCreateOpts) ParseDy
         try self.referenced_dylibs.putNoClobber(self.base.allocator, dylib_id, {});
     }
 
-    // TODO this should not be performed if the user specifies `-flat_namespace` flag.
-    // See ld64 manpages.
-    try dylib.parseDependentLibs(self, opts.syslibroot);
-
     return true;
 }
 
-fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8) !void {
+fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const u8, dependent_libs: anytype) !void {
     for (files) |file_name| {
         const full_path = full_path: {
             var buffer: [fs.MAX_PATH_BYTES]u8 = undefined;
@@ -1239,17 +1239,19 @@ fn parseInputFiles(self: *MachO, files: []const []const u8, syslibroot: ?[]const
         if (try self.parseArchive(full_path)) continue;
         if (try self.parseDylib(full_path, .{
             .syslibroot = syslibroot,
+            .dependent_libs = dependent_libs,
         })) continue;
 
         log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
     }
 }
 
-fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !void {
+fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8, dependent_libs: anytype) !void {
     for (libs) |lib| {
         log.debug("parsing lib path '{s}'", .{lib});
         if (try self.parseDylib(lib, .{
             .syslibroot = syslibroot,
+            .dependent_libs = dependent_libs,
         })) continue;
         if (try self.parseArchive(lib)) continue;
 
@@ -1257,6 +1259,50 @@ fn parseLibs(self: *MachO, libs: []const []const u8, syslibroot: ?[]const u8) !v
     }
 }
 
+fn parseDependentLibs(self: *MachO, syslibroot: ?[]const u8, dependent_libs: anytype) !void {
+    // At this point, we can now parse dependents of dylibs preserving the inclusion order of:
+    // 1) anything on the linker line is parsed first
+    // 2) afterwards, we parse dependents of the included dylibs
+    // TODO this should not be performed if the user specifies `-flat_namespace` flag.
+    // See ld64 manpages.
+    var arena_alloc = std.heap.ArenaAllocator.init(self.base.allocator);
+    const arena = &arena_alloc.allocator;
+    defer arena_alloc.deinit();
+
+    while (dependent_libs.readItem()) |*id| {
+        defer id.deinit(self.base.allocator);
+
+        if (self.dylibs_map.contains(id.name)) continue;
+
+        const has_ext = blk: {
+            const basename = fs.path.basename(id.name);
+            break :blk mem.lastIndexOfScalar(u8, basename, '.') != null;
+        };
+        const extension = if (has_ext) fs.path.extension(id.name) else "";
+        const without_ext = if (has_ext) blk: {
+            const index = mem.lastIndexOfScalar(u8, id.name, '.') orelse unreachable;
+            break :blk id.name[0..index];
+        } else id.name;
+
+        for (&[_][]const u8{ extension, ".tbd" }) |ext| {
+            const with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ without_ext, ext });
+            const full_path = if (syslibroot) |root| try fs.path.join(arena, &.{ root, with_ext }) else with_ext;
+
+            log.debug("trying dependency at fully resolved path {s}", .{full_path});
+
+            const did_parse_successfully = try self.parseDylib(full_path, .{
+                .id = id.*,
+                .syslibroot = syslibroot,
+                .is_dependent = true,
+                .dependent_libs = dependent_libs,
+            });
+            if (did_parse_successfully) break;
+        } else {
+            log.warn("unable to resolve dependency {s}", .{id.name});
+        }
+    }
+}
+
 pub const MatchingSection = struct {
     seg: u16,
     sect: u16,
src/main.zig
@@ -1613,6 +1613,12 @@ fn buildOutputType(
                     ) catch |err| {
                         fatal("unable to parse '{s}': {s}", .{ arg, @errorName(err) });
                     };
+                } else if (mem.eql(u8, arg, "-weak_framework")) {
+                    i += 1;
+                    if (i >= linker_args.items.len) {
+                        fatal("expected linker arg after '{s}'", .{arg});
+                    }
+                    try frameworks.append(linker_args.items[i]);
                 } else {
                     warn("unsupported linker arg: {s}", .{arg});
                 }