Commit 96a0479db2

Jakub Konka <kubkon@jakubkonka.com>
2021-06-19 16:12:29
zld: parse lib stubs as tbds on the linker line
1 parent 089577a
Changed files (5)
src/link/MachO/Archive.zig
@@ -234,8 +234,8 @@ pub fn parseObject(self: Archive, offset: u32) !*Object {
     return object;
 }
 
-pub fn isArchive(file: fs.File) !bool {
-    const magic = try file.reader().readBytesNoEof(Archive.SARMAG);
-    try file.seekTo(0);
+pub fn isArchive(file: fs.File) bool {
+    const magic = file.reader().readBytesNoEof(Archive.SARMAG) catch return false;
+    file.seekTo(0) catch return false;
     return mem.eql(u8, &magic, Archive.ARMAG);
 }
src/link/MachO/Dylib.zig
@@ -1,6 +1,7 @@
 const Dylib = @This();
 
 const std = @import("std");
+const assert = std.debug.assert;
 const fs = std.fs;
 const log = std.log.scoped(.dylib);
 const macho = std.macho;
@@ -8,6 +9,7 @@ const mem = std.mem;
 
 const Allocator = mem.Allocator;
 const Symbol = @import("Symbol.zig");
+const LibStub = @import("../tapi.zig").LibStub;
 
 usingnamespace @import("commands.zig");
 
@@ -184,8 +186,88 @@ pub fn parseSymbols(self: *Dylib) !void {
     }
 }
 
-pub fn isDylib(file: fs.File) !bool {
-    const header = try file.reader().readStruct(macho.mach_header_64);
-    try file.seekTo(0);
+pub fn isDylib(file: fs.File) bool {
+    const header = file.reader().readStruct(macho.mach_header_64) catch return false;
+    file.seekTo(0) catch return false;
     return header.filetype == macho.MH_DYLIB;
 }
+
+pub fn parseFromStub(self: *Dylib, lib_stub: LibStub) !void {
+    assert(lib_stub.inner.len > 0);
+
+    log.debug("parsing shared library from stub '{s}'", .{self.name.?});
+
+    const umbrella_lib = lib_stub.inner[0];
+    self.id = .{
+        .name = try self.allocator.dupe(u8, umbrella_lib.install_name),
+        // TODO parse from the stub
+        .timestamp = 2,
+        .current_version = 0,
+        .compatibility_version = 0,
+    };
+
+    const target_string: []const u8 = switch (self.arch.?) {
+        .aarch64 => "arm64-macos",
+        .x86_64 => "x86_64-macos",
+        else => unreachable,
+    };
+
+    for (lib_stub.inner) |stub| {
+        if (!hasTarget(stub.targets, target_string)) continue;
+
+        if (stub.exports) |exports| {
+            for (exports) |exp| {
+                if (!hasTarget(exp.targets, target_string)) continue;
+
+                for (exp.symbols) |sym_name| {
+                    if (self.symbols.contains(sym_name)) continue;
+
+                    const name = try self.allocator.dupe(u8, sym_name);
+                    const proxy = try self.allocator.create(Symbol.Proxy);
+                    errdefer self.allocator.destroy(proxy);
+
+                    proxy.* = .{
+                        .base = .{
+                            .@"type" = .proxy,
+                            .name = name,
+                        },
+                        .dylib = self,
+                    };
+
+                    try self.symbols.putNoClobber(self.allocator, name, &proxy.base);
+                }
+            }
+        }
+
+        if (stub.reexports) |reexports| {
+            for (reexports) |reexp| {
+                if (!hasTarget(reexp.targets, target_string)) continue;
+
+                for (reexp.symbols) |sym_name| {
+                    if (self.symbols.contains(sym_name)) continue;
+
+                    const name = try self.allocator.dupe(u8, sym_name);
+                    const proxy = try self.allocator.create(Symbol.Proxy);
+                    errdefer self.allocator.destroy(proxy);
+
+                    proxy.* = .{
+                        .base = .{
+                            .@"type" = .proxy,
+                            .name = name,
+                        },
+                        .dylib = self,
+                    };
+
+                    try self.symbols.putNoClobber(self.allocator, name, &proxy.base);
+                }
+            }
+        }
+    }
+}
+
+fn hasTarget(targets: []const []const u8, target: []const u8) bool {
+    for (targets) |t| {
+        if (mem.eql(u8, t, target)) return true;
+    }
+    return false;
+}
src/link/MachO/Object.zig
@@ -534,8 +534,8 @@ pub fn parseDataInCode(self: *Object) !void {
     }
 }
 
-pub fn isObject(file: fs.File) !bool {
-    const header = try file.reader().readStruct(macho.mach_header_64);
-    try file.seekTo(0);
+pub fn isObject(file: fs.File) bool {
+    const header = file.reader().readStruct(macho.mach_header_64) catch return false;
+    file.seekTo(0) catch return false;
     return header.filetype == macho.MH_OBJECT;
 }
src/link/MachO/Zld.zig
@@ -243,14 +243,16 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
             object,
             archive,
             dylib,
+            stub,
         },
         file: fs.File,
         name: []const u8,
+        stub: ?LibStub = null,
     };
     var classified = std.ArrayList(Input).init(self.allocator);
     defer classified.deinit();
 
-    // First, classify input files: object, archive or dylib.
+    // First, classify input files: object, archive, dylib or stub (tbd).
     for (files) |file_name| {
         const file = try fs.cwd().openFile(file_name, .{});
         const full_path = full_path: {
@@ -260,7 +262,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
         };
 
         try_object: {
-            if (!(try Object.isObject(file))) break :try_object;
+            if (!Object.isObject(file)) break :try_object;
             try classified.append(.{
                 .kind = .object,
                 .file = file,
@@ -270,7 +272,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
         }
 
         try_archive: {
-            if (!(try Archive.isArchive(file))) break :try_archive;
+            if (!Archive.isArchive(file)) break :try_archive;
             try classified.append(.{
                 .kind = .archive,
                 .file = file,
@@ -280,7 +282,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
         }
 
         try_dylib: {
-            if (!(try Dylib.isDylib(file))) break :try_dylib;
+            if (!Dylib.isDylib(file)) break :try_dylib;
             try classified.append(.{
                 .kind = .dylib,
                 .file = file,
@@ -289,6 +291,19 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
             continue;
         }
 
+        try_stub: {
+            var lib_stub = LibStub.loadFromFile(self.allocator, file) catch |_| {
+                break :try_stub;
+            };
+            try classified.append(.{
+                .kind = .stub,
+                .file = file,
+                .name = full_path,
+                .stub = lib_stub,
+            });
+            continue;
+        }
+
         file.close();
         log.warn("unknown filetype for positional input file: '{s}'", .{file_name});
     }
@@ -318,7 +333,7 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
                 try archive.parse();
                 try self.archives.append(self.allocator, archive);
             },
-            .dylib => {
+            .dylib, .stub => {
                 const dylib = try self.allocator.create(Dylib);
                 errdefer self.allocator.destroy(dylib);
 
@@ -329,7 +344,11 @@ fn parseInputFiles(self: *Zld, files: []const []const u8) !void {
                 dylib.ordinal = @intCast(u16, self.dylibs.items.len) + 1;
 
                 // TODO Defer parsing of the dylibs until they are actually needed
-                try dylib.parse();
+                if (input.stub) |stub| {
+                    try dylib.parseFromStub(stub);
+                } else {
+                    try dylib.parse();
+                }
                 try self.dylibs.append(self.allocator, dylib);
 
                 // Add LC_LOAD_DYLIB command
@@ -353,7 +372,7 @@ fn parseLibs(self: *Zld, libs: []const []const u8) !void {
     for (libs) |lib| {
         const file = try fs.cwd().openFile(lib, .{});
 
-        if (try Dylib.isDylib(file)) {
+        if (Dylib.isDylib(file)) {
             const dylib = try self.allocator.create(Dylib);
             errdefer self.allocator.destroy(dylib);
 
@@ -379,28 +398,55 @@ fn parseLibs(self: *Zld, libs: []const []const u8) !void {
             errdefer dylib_cmd.deinit(self.allocator);
 
             try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
-        } else if (try Archive.isArchive(file)) {
-            const archive = try self.allocator.create(Archive);
-            errdefer self.allocator.destroy(archive);
-
-            archive.* = Archive.init(self.allocator);
-            archive.arch = self.arch.?;
-            archive.name = try self.allocator.dupe(u8, lib);
-            archive.file = file;
-            try archive.parse();
-            try self.archives.append(self.allocator, archive);
         } else {
-            file.close();
-            log.warn("unknown filetype for a library: '{s}'", .{lib});
-        }
-    }
-}
+            // Try tbd stub file next.
+            if (LibStub.loadFromFile(self.allocator, file)) |*lib_stub| {
+                defer lib_stub.deinit();
+
+                const dylib = try self.allocator.create(Dylib);
+                errdefer self.allocator.destroy(dylib);
+
+                dylib.* = Dylib.init(self.allocator);
+                dylib.arch = self.arch.?;
+                dylib.name = try self.allocator.dupe(u8, lib);
+                dylib.file = file;
+                dylib.ordinal = @intCast(u16, self.dylibs.items.len) + 1;
+
+                try dylib.parseFromStub(lib_stub.*);
+                try self.dylibs.append(self.allocator, dylib);
+
+                // Add LC_LOAD_DYLIB command
+                const dylib_id = dylib.id orelse unreachable;
+                var dylib_cmd = try createLoadDylibCommand(
+                    self.allocator,
+                    dylib_id.name,
+                    dylib_id.timestamp,
+                    dylib_id.current_version,
+                    dylib_id.compatibility_version,
+                );
+                errdefer dylib_cmd.deinit(self.allocator);
 
-fn hasTarget(targets: []const []const u8, target: []const u8) bool {
-    for (targets) |t| {
-        if (mem.eql(u8, t, target)) return true;
+                try self.load_commands.append(self.allocator, .{ .Dylib = dylib_cmd });
+            } else |_| {
+                // TODO this entire logic has to be cleaned up.
+                try file.seekTo(0);
+                if (Archive.isArchive(file)) {
+                    const archive = try self.allocator.create(Archive);
+                    errdefer self.allocator.destroy(archive);
+
+                    archive.* = Archive.init(self.allocator);
+                    archive.arch = self.arch.?;
+                    archive.name = try self.allocator.dupe(u8, lib);
+                    archive.file = file;
+                    try archive.parse();
+                    try self.archives.append(self.allocator, archive);
+                } else {
+                    file.close();
+                    log.warn("unknown filetype for a library: '{s}'", .{lib});
+                }
+            }
+        }
     }
-    return false;
 }
 
 fn parseLibSystem(self: *Zld, lib_system_path: []const u8) !void {
@@ -418,73 +464,7 @@ fn parseLibSystem(self: *Zld, lib_system_path: []const u8) !void {
     dylib.file = file;
     dylib.ordinal = @intCast(u16, self.dylibs.items.len) + 1;
 
-    const umbrella_lib = lib_stub.inner[0];
-    dylib.id = .{
-        .name = try self.allocator.dupe(u8, umbrella_lib.install_name),
-        // TODO parse from the stub
-        .timestamp = 2,
-        .current_version = 0,
-        .compatibility_version = 0,
-    };
-
-    const target_string: []const u8 = switch (self.arch.?) {
-        .aarch64 => "arm64-macos",
-        .x86_64 => "x86_64-macos",
-        else => unreachable,
-    };
-
-    for (lib_stub.inner) |stub| {
-        if (!hasTarget(stub.targets, target_string)) continue;
-
-        if (stub.exports) |exports| {
-            for (exports) |exp| {
-                if (!hasTarget(exp.targets, target_string)) continue;
-
-                for (exp.symbols) |sym_name| {
-                    if (dylib.symbols.contains(sym_name)) continue;
-
-                    const name = try self.allocator.dupe(u8, sym_name);
-                    const proxy = try self.allocator.create(Symbol.Proxy);
-                    errdefer self.allocator.destroy(proxy);
-
-                    proxy.* = .{
-                        .base = .{
-                            .@"type" = .proxy,
-                            .name = name,
-                        },
-                        .dylib = dylib,
-                    };
-
-                    try dylib.symbols.putNoClobber(self.allocator, name, &proxy.base);
-                }
-            }
-        }
-
-        if (stub.reexports) |reexports| {
-            for (reexports) |reexp| {
-                if (!hasTarget(reexp.targets, target_string)) continue;
-
-                for (reexp.symbols) |sym_name| {
-                    if (dylib.symbols.contains(sym_name)) continue;
-
-                    const name = try self.allocator.dupe(u8, sym_name);
-                    const proxy = try self.allocator.create(Symbol.Proxy);
-                    errdefer self.allocator.destroy(proxy);
-
-                    proxy.* = .{
-                        .base = .{
-                            .@"type" = .proxy,
-                            .name = name,
-                        },
-                        .dylib = dylib,
-                    };
-
-                    try dylib.symbols.putNoClobber(self.allocator, name, &proxy.base);
-                }
-            }
-        }
-    }
-
+    try dylib.parseFromStub(lib_stub);
     try self.dylibs.append(self.allocator, dylib);
 
     // Add LC_LOAD_DYLIB command
src/link/tapi.zig
@@ -58,7 +58,17 @@ pub const LibStub = struct {
             .inner = undefined,
         };
 
-        lib_stub.inner = try lib_stub.yaml.parse([]Tbd);
+        lib_stub.inner = lib_stub.yaml.parse([]Tbd) catch |err| blk: {
+            switch (err) {
+                error.TypeMismatch => {
+                    // TODO clean this up.
+                    var out = try lib_stub.yaml.arena.allocator.alloc(Tbd, 1);
+                    out[0] = try lib_stub.yaml.parse(Tbd);
+                    break :blk out;
+                },
+                else => |e| return e,
+            }
+        };
 
         return lib_stub;
     }