diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 48804cf7b1ccb51e8a37052686d0a9afe670d116..1e49fb5ce8a8e749ce3b79df01974c58585b8f61 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1145,16 +1145,16 @@ pub const LC = enum(u32) { }; /// the mach magic number -pub const MH_MAGIC = 0xfeedface; +pub const MH_MAGIC: u32 = 0xfeedface; /// NXSwapInt(MH_MAGIC) -pub const MH_CIGAM = 0xcefaedfe; +pub const MH_CIGAM: u32 = 0xcefaedfe; /// the 64-bit mach magic number -pub const MH_MAGIC_64 = 0xfeedfacf; +pub const MH_MAGIC_64: u32 = 0xfeedfacf; /// NXSwapInt(MH_MAGIC_64) -pub const MH_CIGAM_64 = 0xcffaedfe; +pub const MH_CIGAM_64: u32 = 0xcffaedfe; /// relocatable object file pub const MH_OBJECT = 0x1; @@ -1281,16 +1281,16 @@ pub const MH_DYLIB_IN_CACHE = 0x80000000; // Constants for the flags field of the fat_header /// the fat magic number -pub const FAT_MAGIC = 0xcafebabe; +pub const FAT_MAGIC: u32 = 0xcafebabe; /// NXSwapLong(FAT_MAGIC) -pub const FAT_CIGAM = 0xbebafeca; +pub const FAT_CIGAM: u32 = 0xbebafeca; /// the 64-bit fat magic number -pub const FAT_MAGIC_64 = 0xcafebabf; +pub const FAT_MAGIC_64: u32 = 0xcafebabf; /// NXSwapLong(FAT_MAGIC_64) -pub const FAT_CIGAM_64 = 0xbfbafeca; +pub const FAT_CIGAM_64: u32 = 0xbfbafeca; /// Segment flags /// The file contents for this segment is for the high part of the VM space, the low part @@ -2212,3 +2212,11 @@ pub const CompactUnwindEncoding = packed struct(u32) { has_lsda: u1, start: u1, }; + +// Archive files start with the ARMAG identifying string. Then follows a +// `struct ar_hdr', and as many bytes of member file data as its `ar_size' +// member indicates, for each member file. +/// String that begins an archive file. +pub const ARMAG = "!\n"; +/// String in ar_fmag at the end of each header. +pub const ARFMAG = "`\n"; diff --git a/lib/std/zig/LibCDirs.zig b/lib/std/zig/LibCDirs.zig index 84002241bf9ea4fb1dbf5f8702a4723c05a01694..0c8873479420e6aca1db1e27e02f79600ee10950 100644 --- a/lib/std/zig/LibCDirs.zig +++ b/lib/std/zig/LibCDirs.zig @@ -11,15 +11,6 @@ libc_include_dir_list: []const []const u8, libc_installation: ?*const LibCInstallation, libc_framework_dir_list: []const []const u8, sysroot: ?[]const u8, -darwin_sdk_layout: ?DarwinSdkLayout, - -/// The filesystem layout of darwin SDK elements. -pub const DarwinSdkLayout = enum { - /// macOS SDK layout: TOP { /usr/include, /usr/lib, /System/Library/Frameworks }. - sdk, - /// Shipped libc layout: TOP { /lib/libc/include, /lib/libc/darwin, }. - vendored, -}; pub fn detect( arena: Allocator, @@ -37,7 +28,6 @@ pub fn detect( .libc_installation = null, .libc_framework_dir_list = &.{}, .sysroot = null, - .darwin_sdk_layout = null, }; } @@ -102,13 +92,11 @@ pub fn detect( .libc_installation = null, .libc_framework_dir_list = &.{}, .sysroot = null, - .darwin_sdk_layout = null, }; } fn detectFromInstallation(arena: Allocator, target: *const std.Target, lci: *const LibCInstallation) !LibCDirs { var list = try std.array_list.Managed([]const u8).initCapacity(arena, 5); - var framework_list = std.array_list.Managed([]const u8).init(arena); list.appendAssumeCapacity(lci.include_dir.?); @@ -149,21 +137,15 @@ fn detectFromInstallation(arena: Allocator, target: *const std.Target, lci: *con } } - var sysroot: ?[]const u8 = null; - - if (target.os.tag.isDarwin()) d: { - const down1 = std.fs.path.dirname(lci.sys_include_dir.?) orelse break :d; - const down2 = std.fs.path.dirname(down1) orelse break :d; - try framework_list.append(try std.fs.path.join(arena, &.{ down2, "System", "Library", "Frameworks" })); - sysroot = down2; - } + const frameworks: []const []const u8 = if (target.os.tag.isDarwin()) try arena.dupe([]const u8, &.{ + try std.fs.path.join(arena, &.{ lci.darwin_sdk_dir.?, "System", "Library", "Frameworks" }), + }) else &.{}; return .{ .libc_include_dir_list = list.items, .libc_installation = lci, - .libc_framework_dir_list = framework_list.items, - .sysroot = sysroot, - .darwin_sdk_layout = if (sysroot == null) null else .sdk, + .libc_framework_dir_list = frameworks, + .sysroot = if (target.os.tag.isDarwin()) lci.darwin_sdk_dir.? else null, }; } @@ -178,7 +160,6 @@ pub fn detectFromBuilding(arena: Allocator, zig_lib_dir: Path, target: *const st .libc_installation = null, .libc_framework_dir_list = &.{}, .sysroot = null, - .darwin_sdk_layout = .vendored, }; } @@ -230,7 +211,6 @@ pub fn detectFromBuilding(arena: Allocator, zig_lib_dir: Path, target: *const st .libc_installation = null, .libc_framework_dir_list = &.{}, .sysroot = null, - .darwin_sdk_layout = .vendored, }; } diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index fe44e3a307ccaa56671b2dbfe50b25afc4f4f935..5191bb323b5873465c17749502c6930cd336f630 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -23,6 +23,7 @@ crt_dir: ?[]const u8 = null, msvc_lib_dir: ?[]const u8 = null, kernel32_lib_dir: ?[]const u8 = null, cc_dir: ?[]const u8 = null, +darwin_sdk_dir: ?[]const u8 = null, pub const FindError = error{ OutOfMemory, @@ -96,6 +97,10 @@ pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const log.err("sys_include_dir may not be empty", .{}); return error.ParseError; } + if (self.crt_dir == null) { + log.err("crt_dir may not be empty", .{}); + return error.ParseError; + } const os_tag = target.os.tag; @@ -103,12 +108,6 @@ pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const log.err("cc_dir may not be empty for {s}", .{@tagName(os_tag)}); return error.ParseError; } - - if (self.crt_dir == null and !target.os.tag.isDarwin()) { - log.err("crt_dir may not be empty for {s}", .{@tagName(os_tag)}); - return error.ParseError; - } - if (self.msvc_lib_dir == null and os_tag == .windows and (target.abi == .msvc or target.abi == .itanium)) { log.err("msvc_lib_dir may not be empty for {s}-{s}", .{ @tagName(os_tag), @@ -123,19 +122,16 @@ pub fn parse(allocator: Allocator, io: Io, libc_file: []const u8, target: *const }); return error.ParseError; } + if (self.darwin_sdk_dir == null and os_tag.isDarwin()) { + log.err("darwin_sdk_dir may not be empty for {t}", .{os_tag}); + return error.ParseError; + } return self; } pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { @setEvalBranchQuota(4000); - const include_dir = self.include_dir orelse ""; - const sys_include_dir = self.sys_include_dir orelse ""; - const crt_dir = self.crt_dir orelse ""; - const msvc_lib_dir = self.msvc_lib_dir orelse ""; - const kernel32_lib_dir = self.kernel32_lib_dir orelse ""; - const cc_dir = self.cc_dir orelse ""; - try out.print( \\# The directory that contains `stdlib.h`. \\# On POSIX, can be found with: `cc -E -Wp,-v -xc /dev/null` @@ -153,7 +149,6 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { \\ \\# The directory that contains `crt1.o` or `crt2.o`. \\# On POSIX, can be found with `cc -print-file-name=crt1.o`. - \\# Not needed when targeting macOS. \\crt_dir={s} \\ \\# The directory that contains `vcruntime.lib`. @@ -164,13 +159,18 @@ pub fn render(self: LibCInstallation, out: *std.Io.Writer) !void { \\# Only needed when targeting MSVC on Windows. \\kernel32_lib_dir={s} \\ + \\# The directory that contains `SDKSettings.json` + \\# Only needed when targeting Darwin. + \\darwin_sdk_dir={s} + \\ , .{ - include_dir, - sys_include_dir, - crt_dir, - msvc_lib_dir, - kernel32_lib_dir, - cc_dir, + self.include_dir orelse "", + self.sys_include_dir orelse "", + self.cc_dir orelse "", + self.crt_dir orelse "", + self.msvc_lib_dir orelse "", + self.kernel32_lib_dir orelse "", + self.darwin_sdk_dir orelse "", }); } @@ -189,16 +189,13 @@ pub fn findNative(gpa: Allocator, io: Io, args: FindNativeOptions) FindError!Lib if (is_darwin and args.target.os.tag.isDarwin()) { if (!std.zig.system.darwin.isSdkInstalled(gpa, io)) return error.DarwinSdkNotFound; - const sdk = std.zig.system.darwin.getSdk(gpa, io, args.target) orelse + self.darwin_sdk_dir = std.zig.system.darwin.getSdk(gpa, io, args.target) orelse return error.DarwinSdkNotFound; - defer gpa.free(sdk); - self.include_dir = try fs.path.join(gpa, &.{ - sdk, "usr/include", - }); - self.sys_include_dir = try fs.path.join(gpa, &.{ - sdk, "usr/include", - }); + const sdk = self.darwin_sdk_dir.?; + self.include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" }); + self.sys_include_dir = try fs.path.join(gpa, &.{ sdk, "usr/include" }); + self.crt_dir = try fs.path.join(gpa, &.{ sdk, "usr/lib" }); return self; } else if (is_windows) { const sdk = std.zig.WindowsSdk.find(gpa, io, args.target.cpu.arch, args.environ_map) catch |err| switch (err) { diff --git a/src/Compilation.zig b/src/Compilation.zig index d73e142ffdad6067e611eb702072b4e77f6ef143..823763cef6f0af734eb86fcd1de4a1dd316e0025 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -83,7 +83,7 @@ zigc_strat: RtStrat, /// Resolved into known paths, any GNU ld scripts already resolved. link_inputs: []const link.Input, /// Needed only for passing -F args to clang. -framework_dirs: []const []const u8, +framework_dirs: []const Cache.Directory, /// These are only for DLLs dependencies fulfilled by the `.def` files shipped /// with Zig. Static libraries are provided as `link.Input` values. windows_libs: std.array_hash_map.String(void), @@ -1350,7 +1350,6 @@ pub const ClangPreprocessorMode = enum { version, }; -pub const Framework = link.File.MachO.Framework; pub const SystemLib = link.SystemLib; pub const CacheMode = enum { @@ -1487,6 +1486,7 @@ pub const CreateOptions = struct { /// The ELF implementation no longer uses this data, however the MachO and COFF /// implementations still do. lib_directories: []const Cache.Directory = &.{}, + framework_directories: []const Cache.Directory = &.{}, rpath_list: []const []const u8 = &[0][]const u8{}, symbol_wrap_set: std.array_hash_map.String(void) = .empty, c_source_files: []const CSourceFile = &.{}, @@ -1494,8 +1494,6 @@ pub const CreateOptions = struct { manifest_file: ?[]const u8 = null, rc_includes: std.zig.RcIncludes = .any, link_inputs: []const link.Input = &.{}, - framework_dirs: []const []const u8 = &[0][]const u8{}, - frameworks: []const Framework = &.{}, windows_lib_names: []const []const u8 = &.{}, /// This means that if the output mode is an executable it will be a /// Position Independent Executable. If the output mode is not an @@ -2146,7 +2144,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .ubsan_rt_strat = ubsan_rt_strat, .zigc_strat = zigc_strat, .link_inputs = options.link_inputs, - .framework_dirs = options.framework_dirs, + .framework_dirs = options.framework_directories, .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, .skip_linker_dependencies = options.skip_linker_dependencies, .queued_jobs = .{}, @@ -2208,10 +2206,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .z_relro = options.linker_z_relro, .z_common_page_size = options.linker_z_common_page_size, .z_max_page_size = options.linker_z_max_page_size, - .darwin_sdk_layout = libc_dirs.darwin_sdk_layout, - .frameworks = options.frameworks, .lib_directories = options.lib_directories, - .framework_dirs = options.framework_dirs, .rpath_list = options.rpath_list, .symbol_wrap_set = options.symbol_wrap_set, .repro = options.linker_repro orelse (options.root_mod.optimize_mode != .debug), @@ -2429,11 +2424,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, // If linking against host libc installation, instead queue up jobs // for loading those files in the linker. if (comp.config.link_libc and is_exe_or_dyn_lib) { - // If the "is darwin" check is moved below the libc_installation check below, - // error.LibCInstallationMissingCrtDir is returned from lci.resolveCrtPaths(). - if (target.isDarwinLibC()) { - // TODO delete logic from MachO flush() and queue up tasks here instead. - } else if (comp.libc_installation) |lci| { + if (comp.libc_installation) |lci| { const basenames = LibCInstallation.CrtBasenames.get(.{ .target = target, .link_libc = comp.config.link_libc, @@ -2456,6 +2447,18 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, } // Loads the libraries provided by `target_util.libcFullLinkFlags(target)`. comp.oneshot_prelink_tasks.appendAssumeCapacity(.load_host_libc); + } else if (target.isDarwinLibC()) { + try comp.oneshot_prelink_tasks.appendSlice(gpa, &.{ + .{ .load_tbd = .{ + .root_dir = comp.dirs.zig_lib, + .sub_path = "libc" ++ fs.path.sep_str ++ "darwin" ++ fs.path.sep_str ++ "libSystem.tbd", + } }, + .{ .load_darwin_sdk_settings = .{ + .root_dir = comp.dirs.zig_lib, + .sub_path = "libc" ++ fs.path.sep_str ++ "darwin" ++ fs.path.sep_str ++ "SDKSettings.json", + } }, + }); + // TODO delete logic from MachO flush() and queue up tasks here instead. } else if (target.isMuslLibC()) { if (!std.zig.target.canBuildLibC(target)) return diag.fail(.cross_libc_unavailable); @@ -3409,7 +3412,10 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes man.hash.add(comp.zigc_strat); man.hash.add(comp.rc_includes); man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); - man.hash.addListOfBytes(comp.framework_dirs); + man.hash.add(comp.framework_dirs.len); + for (comp.framework_dirs) |framework_dir| { + man.hash.addBytes(framework_dir.path orelse "."); + } man.hash.addListOfBytes(comp.windows_libs.keys()); man.hash.addListOfBytes(comp.global_cc_argv); @@ -3475,7 +3481,6 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes man.hash.add(opts.growable_table); // Mach-O specific stuff - try link.File.MachO.hashAddFrameworks(man, opts.frameworks); try man.addInputPathOptional(opts.entitlements, .{}); man.hash.addOptional(opts.pagezero_size); man.hash.addOptional(opts.headerpad_size); @@ -3485,7 +3490,6 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes man.hash.add(opts.discard_local_symbols); man.hash.addOptional(opts.compatibility_version); man.hash.addOptionalBytes(opts.install_name); - man.hash.addOptional(opts.darwin_sdk_layout); // COFF specific stuff man.hash.addOptional(opts.subsystem); @@ -6455,7 +6459,7 @@ fn addCommonCCArgs( try argv.ensureUnusedCapacity(comp.framework_dirs.len * 2); for (comp.framework_dirs) |framework_dir| { - try argv.appendSlice(&.{ "-F", framework_dir }); + try argv.appendSlice(&.{ "-F", framework_dir.path orelse "." }); } } } diff --git a/src/codegen.zig b/src/codegen.zig index a71f2e9d13710379a141de0b7969e31e92156cb4..2213e7ee506d2156be699188d8d666c1c22d4e7b 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -754,23 +754,15 @@ fn lowerUavRef( } const uav_align = Type.fromInterned(uav.orig_ty).ptrAlignment(zcu); - _ = try lf.lowerUav(pt, uav_val, uav_align); - const vaddr = lf.getUavVAddr(uav_val, .{ + try lf.relocSymAddr(.{ .parent = reloc_parent, .offset = w.end, + .target = try lf.uavSymbol(pt, uav_val, uav_align), .addend = @intCast(offset), - }) catch |err| switch (err) { - error.OutOfMemory => |e| return e, - else => |e| std.debug.panic("TODO rework lowerUav. internal error: {t}", .{e}), - }; - const endian = target.cpu.arch.endian(); - switch (ptr_width_bytes) { - 2 => try w.writeInt(u16, @intCast(vaddr), endian), - 4 => try w.writeInt(u32, @intCast(vaddr), endian), - 8 => try w.writeInt(u64, vaddr, endian), - else => unreachable, - } + }); + + try w.splatByteAll(0, ptr_width_bytes); // overwritten by relocation } fn lowerNavRef( @@ -805,68 +797,14 @@ fn lowerNavRef( else => {}, } - const vaddr = lf.getNavVAddr(pt, nav_index, .{ + try lf.relocSymAddr(.{ .parent = reloc_parent, .offset = w.end, + .target = try lf.navSymbol(nav_index), .addend = @intCast(offset), - }) catch @panic("TODO rework getNavVAddr"); - const endian = target.cpu.arch.endian(); - switch (ptr_width_bytes) { - 2 => try w.writeInt(u16, @intCast(vaddr), endian), - 4 => try w.writeInt(u32, @intCast(vaddr), endian), - 8 => try w.writeInt(u64, vaddr, endian), - else => unreachable, - } -} + }); -pub fn genNavRef( - lf: *link.File, - pt: Zcu.PerThread, - nav_index: InternPool.Nav.Index, -) Error!link.File.SymbolId { - const zcu = pt.zcu; - const ip = &zcu.intern_pool; - const nav = ip.getNav(nav_index); - log.debug("genNavRef({f})", .{nav.fqn.fmt(ip)}); - - const is_threadlocal = nav.resolved.?.@"threadlocal" and zcu.comp.config.any_non_single_threaded; - if (lf.cast(.elf)) |elf_file| { - const zo = elf_file.zigObjectPtr().?; - if (nav.getExtern(ip)) |e| { - const sym_index = try elf_file.getGlobalSymbol(nav.name.toSlice(ip), e.lib_name.toSlice(ip)); - switch (e.linkage) { - .strong => {}, - .weak => zo.symbol(sym_index).flags.weak = true, - } - if (is_threadlocal) zo.symbol(sym_index).flags.is_tls = true; - return @fromBackingInt(@intCast(sym_index)); - } else { - const sym_index = try zo.getOrCreateMetadataForNav(zcu, nav_index); - if (is_threadlocal) zo.symbol(sym_index).flags.is_tls = true; - return @fromBackingInt(@intCast(sym_index)); - } - } else if (lf.cast(.elf2)) |elf| { - return elf.navSymbol(nav_index); - } else if (lf.cast(.macho)) |macho_file| { - const zo = macho_file.getZigObject().?; - if (nav.getExtern(ip)) |e| { - const sym_index = try macho_file.getGlobalSymbol(nav.name.toSlice(ip), e.lib_name.toSlice(ip)); - switch (e.linkage) { - .strong => {}, - .weak => zo.symbols.items[sym_index].flags.weak = true, - } - if (is_threadlocal) zo.symbols.items[sym_index].flags.tlv = true; - return @fromBackingInt(@intCast(sym_index)); - } else { - const sym_index = try zo.getOrCreateMetadataForNav(macho_file, nav_index); - if (is_threadlocal) zo.symbols.items[sym_index].flags.tlv = true; - return @fromBackingInt(@intCast(sym_index)); - } - } else if (lf.cast(.coff2)) |coff| { - return @fromBackingInt(@intCast(@backingInt(try coff.navSymbol(zcu, nav_index)))); - } else { - std.debug.panic("TODO genNavRef for '{t}'", .{lf.tag}); - } + try w.splatByteAll(0, ptr_width_bytes); // overwritten by relocation } /// deprecated legacy type @@ -908,13 +846,13 @@ pub fn genTypedValue( .none => .none, .undef => .undef, .immediate => |imm| .{ .immediate = imm }, - .lea_nav => |nav| .{ .lea_symbol = try genNavRef(lf, pt, nav) }, - .load_uav => |uav| .{ .load_symbol = try lf.lowerUav( + .lea_nav => |nav| .{ .lea_symbol = try lf.navSymbol(nav) }, + .load_uav => |uav| .{ .load_symbol = try lf.uavSymbol( pt, uav.val, Type.fromInterned(uav.orig_ty).ptrAlignment(pt.zcu), ) }, - .lea_uav => |uav| .{ .lea_symbol = try lf.lowerUav( + .lea_uav => |uav| .{ .lea_symbol = try lf.uavSymbol( pt, uav.val, Type.fromInterned(uav.orig_ty).ptrAlignment(pt.zcu), diff --git a/src/codegen/aarch64/Mir.zig b/src/codegen/aarch64/Mir.zig index ad40e86902d4273b2b558fa1b1b97add12b29345..1233a8180db0e726d2d2904eb2246734ad18ab56 100644 --- a/src/codegen/aarch64/Mir.zig +++ b/src/codegen/aarch64/Mir.zig @@ -93,11 +93,7 @@ pub fn emit( lf, zcu, atom_index, - try @import("../../codegen.zig").genNavRef( - lf, - pt, - nav_reloc.nav, - ), + try lf.navSymbol(nav_reloc.nav), mir.body[nav_reloc.reloc.label], body_end - Instruction.size * (1 + nav_reloc.reloc.label), nav_reloc.reloc.addend, @@ -107,7 +103,7 @@ pub fn emit( lf, zcu, atom_index, - try lf.lowerUav( + try lf.uavSymbol( pt, uav_reloc.uav.val, ZigType.fromInterned(uav_reloc.uav.orig_ty).ptrAlignment(zcu), diff --git a/src/codegen/loongarch/Mir.zig b/src/codegen/loongarch/Mir.zig index f81843ff4e24ce3c395cd250844e1bed9bb368cb..e107984767b547ccb82c9ac1ab869c4ee4d02e24 100644 --- a/src/codegen/loongarch/Mir.zig +++ b/src/codegen/loongarch/Mir.zig @@ -83,11 +83,7 @@ pub fn emit( lf, zcu, atom_index, - try @import("../../codegen.zig").genNavRef( - lf, - pt, - nav_reloc.nav, - ), + try lf.navSymbol(nav_reloc.nav), nav_reloc.reloc.type, body_end - @sizeOf(Instruction) * (1 + nav_reloc.reloc.label), nav_reloc.reloc.addend, @@ -97,7 +93,7 @@ pub fn emit( lf, zcu, atom_index, - try lf.lowerUav( + try lf.uavSymbol( pt, uav_reloc.uav.val, ZigType.fromInterned(uav_reloc.uav.orig_ty).ptrAlignment(zcu), @@ -142,16 +138,11 @@ pub fn emit( ) catch |err| return zcu.codegenFail(func.owner_nav, "emit reloc failed: {t}", .{err}); - const func_nav = try @import("../../codegen.zig").genNavRef( - lf, - pt, - func.owner_nav, - ); for (mir.internal_relocs) |internal_reloc| emitReloc( lf, zcu, atom_index, - func_nav, + try lf.navSymbol(func.owner_nav), internal_reloc.reloc.type, body_end - @sizeOf(Instruction) * (1 + internal_reloc.reloc.label), @sizeOf(Instruction) * (@as(i64, @intCast(mir.prologue.len + mir.body.len - internal_reloc.target))), diff --git a/src/codegen/riscv64/CodeGen.zig b/src/codegen/riscv64/CodeGen.zig index 1db4dde0627e2ee00b6f65f9c48db0b53b29f8c2..aeb1dde892769fe1270542079b778ed6487d0cdf 100644 --- a/src/codegen/riscv64/CodeGen.zig +++ b/src/codegen/riscv64/CodeGen.zig @@ -8160,7 +8160,7 @@ fn genTypedValue(func: *Func, val: Value) InnerError!MCValue { const lf = func.bin_file; const result: codegen.MCValue = if (val.isUndef(pt.zcu)) - .{ .load_symbol = try lf.lowerUav(pt, val.toIntern(), .none) } + .{ .load_symbol = try lf.uavSymbol(pt, val.toIntern(), .none) } else try codegen.genTypedValue(lf, pt, val, func.target); const mcv: MCValue = switch (result) { diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig index eddc1f1b9341485ac0802140bbe0f1efbd08efa7..a82ae415ac502309854af8e9ca44636ee3c8ab43 100644 --- a/src/codegen/x86_64/Emit.zig +++ b/src/codegen/x86_64/Emit.zig @@ -100,11 +100,7 @@ pub fn emitMir(emit: *Emit) Error!void { .inst => |inst| .{ .inst = inst }, .table => .table, .nav => |nav| { - const symbol_id = try codegen.genNavRef( - emit.bin_file, - emit.pt, - nav, - ); + const symbol_id = try emit.bin_file.navSymbol(nav); const target_symbol: RelocInfo.Target.Symbol = if (ip.getNav(nav).getExtern(ip)) |@"extern"| .{ .symbol = symbol_id, .is_extern = switch (@"extern".visibility) { @@ -124,7 +120,7 @@ pub fn emitMir(emit: *Emit) Error!void { } }, .uav => |uav| .{ .symbol = .{ - .symbol = try emit.bin_file.lowerUav( + .symbol = try emit.bin_file.uavSymbol( emit.pt, uav.val, Type.fromInterned(uav.orig_ty).ptrAlignment(emit.pt.zcu), @@ -142,7 +138,7 @@ pub fn emitMir(emit: *Emit) Error!void { else if (emit.bin_file.cast(.macho)) |macho_file| @fromBackingInt(@intCast(macho_file.getZigObject().?.getOrCreateMetadataForLazySymbol(macho_file, emit.pt, lazy_sym) catch |err| return emit.fail("{s} creating lazy symbol", .{@errorName(err)}))) - else if (emit.bin_file.cast(.coff2)) |coff| + else if (emit.bin_file.cast(.coff)) |coff| @fromBackingInt(@intCast(@backingInt(try coff.lazySymbol(lazy_sym)))) else return emit.fail("lazy symbols unimplemented for {s}", .{@tagName(emit.bin_file.tag)}), @@ -157,7 +153,7 @@ pub fn emitMir(emit: *Emit) Error!void { .type = .FUNC, }) else if (emit.bin_file.cast(.macho)) |macho_file| @fromBackingInt(@intCast(try macho_file.getGlobalSymbol(extern_func.toSlice(&emit.lower.mir).?, null))) - else if (emit.bin_file.cast(.coff2)) |coff| @fromBackingInt(@intCast(@backingInt(try coff.globalSymbol(.{ + else if (emit.bin_file.cast(.coff)) |coff| @fromBackingInt(@intCast(@backingInt(try coff.globalSymbol(.{ .name = extern_func.toSlice(&emit.lower.mir).?, })))) else return emit.fail("external symbol unimplemented for {s}", .{@tagName(emit.bin_file.tag)}), .is_extern = true, @@ -171,7 +167,7 @@ pub fn emitMir(emit: *Emit) Error!void { switch (lowered_inst.encoding.mnemonic) { .call => { reloc.target = .{ .branch = target }; - if (target.is_dll_import and emit.bin_file.cast(.coff2) != null) { + if (target.is_dll_import and emit.bin_file.cast(.coff) != null) { try emit.encodeInst(try .new(.none, .call, &.{ .{ .mem = .initRip(.ptr, 0) }, }, emit.lower.target), reloc_info); @@ -252,7 +248,7 @@ pub fn emitMir(emit: *Emit) Error!void { }, emit.lower.target), reloc_info), else => unreachable, } - } else if (emit.bin_file.cast(.coff2)) |_| { + } else if (emit.bin_file.cast(.coff)) |_| { if (target.is_dll_import) switch (lowered_inst.encoding.mnemonic) { .lea => try emit.encodeInst(try .new(.none, .mov, &.{ lowered_inst.ops[0], @@ -372,7 +368,7 @@ pub fn emitMir(emit: *Emit) Error!void { }, emit.lower.target), &.{}); }, else => unreachable, - } else if (emit.bin_file.cast(.coff2)) |coff| { + } else if (emit.bin_file.cast(.coff)) |coff| { switch (emit.lower.target.cpu.arch) { else => unreachable, .x86 => { @@ -597,12 +593,12 @@ pub fn emitMir(emit: *Emit) Error!void { .nav => |nav| @unionInit( DwarfLoc, addr_loc, - try codegen.genNavRef(emit.bin_file, emit.pt, nav), + try emit.bin_file.navSymbol(nav), ), .uav => |uav| @unionInit( DwarfLoc, addr_loc, - try emit.bin_file.lowerUav( + try emit.bin_file.uavSymbol( emit.pt, uav.val, Type.fromInterned(uav.orig_ty) @@ -845,7 +841,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI if (target.is_extern and !target.force_pcrel_direct) break :rt .GOTPCREL; break :rt .PC32; } }, - ) else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc( + ) else if (emit.bin_file.cast(.coff)) |coff| try coff.addReloc( @fromBackingInt(@intCast(@backingInt(emit.atom_id))), end_offset - 4, @fromBackingInt(@intCast(@backingInt(target.symbol))), @@ -883,7 +879,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI .symbolnum = @intCast(@backingInt(target.symbol)), }, }); - } else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc( + } else if (emit.bin_file.cast(.coff)) |coff| try coff.addReloc( @fromBackingInt(@intCast(@backingInt(emit.atom_id))), end_offset - 4, @fromBackingInt(@intCast(@backingInt(target.symbol))), @@ -941,7 +937,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI .symbolnum = @intCast(@backingInt(target.symbol)), }, }); - } else if (emit.bin_file.cast(.coff2)) |coff| try coff.addReloc( + } else if (emit.bin_file.cast(.coff)) |coff| try coff.addReloc( @fromBackingInt(@intCast(@backingInt(emit.atom_id))), end_offset - 4, @fromBackingInt(@intCast(@backingInt(target.symbol))), diff --git a/src/dev.zig b/src/dev.zig index e8b62bd420f5d6e9472252cd021db4f122971f1d..6472f65f518bcc909f72ce3863c72dd0c6a2c4fc 100644 --- a/src/dev.zig +++ b/src/dev.zig @@ -109,10 +109,10 @@ pub const Env = enum { .spork8_backend, .lld_linker, .coff_linker, - .coff2_linker, .elf_linker, .elf2_linker, .macho_linker, + .macho2_linker, .c_linker, .wasm_linker, .spirv_linker, @@ -231,7 +231,7 @@ pub const Env = enum { .incremental, .legalize, .x86_64_backend, - .coff2_linker, + .coff_linker, => true, else => Env.sema.supports(feature), }, @@ -321,10 +321,10 @@ pub const Feature = enum { lld_linker, coff_linker, - coff2_linker, elf_linker, elf2_linker, macho_linker, + macho2_linker, c_linker, wasm_linker, spirv_linker, diff --git a/src/link.zig b/src/link.zig index 6b2d2eecdf7f644ddaa94b71a701de983173fda5..c1ad89e817611968fa3c61b12cd5b19ad317fdfe 100644 --- a/src/link.zig +++ b/src/link.zig @@ -481,7 +481,6 @@ pub const File = struct { // TODO: remove this. libraries are resolved by the frontend. lib_directories: []const Directory, - framework_dirs: []const []const u8, rpath_list: []const []const u8, /// Zig compiler development linker flags. @@ -501,8 +500,6 @@ pub const File = struct { headerpad_max_install_names: bool, /// Remove dylibs that are unreachable by the entry point or exported symbols dead_strip_dylibs: bool, - frameworks: []const MachO.Framework, - darwin_sdk_layout: ?MachO.SdkLayout, /// Force load all members of static archives that implement an /// Objective-C class or category force_load_objc: bool, @@ -587,7 +584,7 @@ pub const File = struct { pub fn startProgress(base: *File, prog_node: std.Progress.Node) void { switch (base.tag) { else => {}, - inline .elf2, .coff2 => |tag| { + inline .elf2, .coff, .macho2 => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).startProgress(prog_node); }, @@ -597,7 +594,7 @@ pub const File = struct { pub fn endProgress(base: *File) void { switch (base.tag) { else => {}, - inline .elf2, .coff2 => |tag| { + inline .elf2, .coff, .macho2 => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).endProgress(); }, @@ -651,11 +648,13 @@ pub const File = struct { } base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write }); }, - .elf2, .coff2 => if (base.file == null) { + .elf2, .coff, .macho2 => if (base.file == null) { const mf = if (base.cast(.elf2)) |elf| &elf.mf - else if (base.cast(.coff2)) |coff| + else if (base.cast(.coff)) |coff| &coff.mf + else if (base.cast(.macho2)) |macho| + &macho.mf else unreachable; mf.memory_map.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{ @@ -740,11 +739,13 @@ pub const File = struct { } } }, - .elf2, .coff2 => if (base.file) |f| { + .elf2, .coff, .macho2 => if (base.file) |f| { const mf = if (base.cast(.elf2)) |elf| &elf.mf - else if (base.cast(.coff2)) |coff| + else if (base.cast(.coff)) |coff| &coff.mf + else if (base.cast(.macho2)) |macho| + &macho.mf else unreachable; mf.unmap(); @@ -841,7 +842,7 @@ pub const File = struct { switch (base.tag) { .lld => unreachable, else => {}, - inline .elf, .elf2, .c, .coff2 => |tag| { + inline .elf, .elf2, .c, .coff => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).updateContainerType(pt, ty, success); }, @@ -899,7 +900,8 @@ pub const File = struct { .lld => unreachable, .plan9 => unreachable, .spirv => {}, - .coff2 => {}, + .coff => {}, + .macho2 => {}, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).updateLineNumber(pt, ti_id, line); @@ -951,7 +953,7 @@ pub const File = struct { pub fn idle(base: *File, tid: Zcu.PerThread.Id) Error!bool { switch (base.tag) { else => return false, - inline .elf2, .coff2 => |tag| { + inline .elf2, .coff, .macho2 => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).idle(tid); }, @@ -959,9 +961,10 @@ pub const File = struct { } pub fn updateErrorData(base: *File, pt: Zcu.PerThread) Error!void { + if (base.comp.zcu.?.llvm_object != null) return; switch (base.tag) { else => {}, - inline .elf2, .coff2 => |tag| { + inline .elf2, .coff, .macho2 => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).updateErrorData(pt); }, @@ -1039,6 +1042,7 @@ pub const File = struct { pub const RelocInfo = struct { parent: Parent, offset: u64, + target: SymbolId, addend: u32, pub const Parent = union(enum) { @@ -1048,16 +1052,9 @@ pub const File = struct { }; }; - /// Get allocated `Nav`'s address in virtual memory. - /// The linker is passed information about the containing atom, `parent_atom_index`, and offset within it's - /// memory buffer, `offset`, so that it can make a note of potential relocation sites, should the - /// `Nav`'s address was not yet resolved, or the containing atom gets moved in virtual memory. - /// May be called before or after updateFunc/updateNav therefore it is up to the linker to allocate - /// the block/atom. /// Never called when LLVM is codegenning the ZCU. - pub fn getNavVAddr(base: *File, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: RelocInfo) Error!u64 { - assert(pt.zcu.llvm_object == null); - + pub fn relocSymAddr(base: *File, reloc_info: RelocInfo) Error!void { + assert(base.comp.zcu.?.llvm_object == null); switch (base.tag) { .lld => unreachable, .c => unreachable, @@ -1067,20 +1064,19 @@ pub const File = struct { .spork8 => unreachable, inline else => |tag| { dev.check(tag.devFeature()); - return @as(*tag.Type(), @fieldParentPtr("base", base)).getNavVAddr(pt, nav_index, reloc_info); + return @as(*tag.Type(), @fieldParentPtr("base", base)).relocSymAddr(reloc_info); }, } } /// Never called when LLVM is codegenning the ZCU. - pub fn lowerUav( + pub fn uavSymbol( base: *File, pt: Zcu.PerThread, - decl_val: InternPool.Index, - decl_align: InternPool.Alignment, + uav_val: InternPool.Index, + uav_align: InternPool.Alignment, ) Error!SymbolId { assert(pt.zcu.llvm_object == null); - switch (base.tag) { .lld => unreachable, .c => unreachable, @@ -1090,15 +1086,14 @@ pub const File = struct { .spork8 => unreachable, inline else => |tag| { dev.check(tag.devFeature()); - return @as(*tag.Type(), @fieldParentPtr("base", base)).lowerUav(pt, decl_val, decl_align); + return @as(*tag.Type(), @fieldParentPtr("base", base)).uavSymbol(pt, uav_val, uav_align); }, } } /// Never called when LLVM is codegenning the ZCU. - pub fn getUavVAddr(base: *File, decl_val: InternPool.Index, reloc_info: RelocInfo) Error!u64 { + pub fn navSymbol(base: *File, nav: InternPool.Nav.Index) Error!SymbolId { assert(base.comp.zcu.?.llvm_object == null); - switch (base.tag) { .lld => unreachable, .c => unreachable, @@ -1108,7 +1103,7 @@ pub const File = struct { .spork8 => unreachable, inline else => |tag| { dev.check(tag.devFeature()); - return @as(*tag.Type(), @fieldParentPtr("base", base)).getUavVAddr(decl_val, reloc_info); + return @as(*tag.Type(), @fieldParentPtr("base", base)).navSymbol(nav); }, } } @@ -1238,15 +1233,68 @@ pub const File = struct { } pub fn loadInput(base: *File, input: Input) anyerror!void { - if (base.tag == .lld) return; - assert(!base.post_prelink); - + if (base.tag != .lld) { + assert(!base.post_prelink); + } switch (base.tag) { - inline .coff2, .elf, .elf2, .wasm, .spirv => |tag| { + inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).loadInput(input); }, - else => {}, + .c, .spork8, .plan9, .lld => {}, + } + } + + fn loadDarwinSdkSettings(base: *File, sdk_settings_path: Path) Error!void { + const comp = base.comp; + const io = comp.io; + const arena = comp.arena; + const diags = &comp.link_diags; + + const contents = sdk_settings_path.root_dir.handle.readFileAlloc( + io, + sdk_settings_path.sub_path, + arena, + .limited(1024 * 1024 * 256), + ) catch |err| switch (err) { + error.OutOfMemory, error.Canceled => |e| return e, + else => |e| return diags.failParse(sdk_settings_path, "failed to parse Darwin SDK settings: {t}", .{e}), + }; + + const parsed = std.json.parseFromSlice(std.json.Value, arena, contents, .{}) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + else => |e| return diags.failParse(sdk_settings_path, "failed to parse Darwin SDK settings: {t}", .{e}), + }; + const parsed_object = switch (parsed.value) { + .object => |obj| obj, + else => return diags.failParse(sdk_settings_path, "failed to parse Darwin SDK settings: file is not a JSON object", .{}), + }; + + const version_json = parsed_object.get("MinimalDisplayName") orelse return diags.failParse( + sdk_settings_path, + "failed to parse Darwin SDK settings: 'MinimalDisplayName' missing", + .{}, + ); + const version_str: []const u8 = switch (version_json) { + .string => |str| str, + else => return diags.failParse( + sdk_settings_path, + "failed to parse Darwin SDK settings: 'MinimalDisplayName' not a string", + .{}, + ), + }; + const version: DarwinSdkVersion = try .parse(diags, sdk_settings_path, version_str); + try base.setDarwinSdkVersion(version); + } + + fn setDarwinSdkVersion(base: *File, version: DarwinSdkVersion) Error!void { + assert(!base.post_prelink); + switch (base.tag) { + inline .macho, .macho2 => |tag| { + dev.check(tag.devFeature()); + return @as(*tag.Type(), @fieldParentPtr("base", base)).setDarwinSdkVersion(version); + }, + else => unreachable, } } @@ -1261,7 +1309,7 @@ pub const File = struct { } switch (base.tag) { - inline .elf2, .coff2, .wasm, .c => |tag| { + inline .elf2, .coff, .macho2, .wasm, .c => |tag| { dev.check(tag.devFeature()); try @as(*tag.Type(), @fieldParentPtr("base", base)).prelink(base.comp.link_prog_node); }, @@ -1302,10 +1350,11 @@ pub const File = struct { } pub const Tag = enum { - coff2, + coff, elf, elf2, macho, + macho2, c, wasm, spirv, @@ -1315,10 +1364,11 @@ pub const File = struct { pub fn Type(comptime tag: Tag) type { return switch (tag) { - .coff2 => Coff2, + .coff => Coff, .elf => Elf, .elf2 => Elf2, .macho => MachO, + .macho2 => MachO2, .c => C, .wasm => Wasm, .spirv => Spirv, @@ -1330,9 +1380,9 @@ pub const File = struct { fn fromObjectFormat(ofmt: std.Target.ObjectFormat, use_new_linker: bool) Tag { return switch (ofmt) { - .coff => .coff2, + .coff => .coff, .elf => if (use_new_linker) .elf2 else .elf, - .macho => .macho, + .macho => if (use_new_linker) .macho2 else .macho, .wasm => .wasm, .plan9 => .plan9, .c => .c, @@ -1420,11 +1470,12 @@ pub const File = struct { pub const Lld = @import("link/Lld.zig"); pub const C = @import("link/C.zig"); - pub const Coff2 = @import("link/Coff.zig"); + pub const Coff = @import("link/Coff.zig"); pub const Spork8 = @import("link/Spork8.zig"); pub const Elf = @import("link/Elf.zig"); pub const Elf2 = @import("link/Elf2.zig"); pub const MachO = @import("link/MachO.zig"); + pub const MachO2 = @import("link/MachO2.zig"); pub const Spirv = @import("link/Spirv.zig"); pub const Wasm = @import("link/Wasm.zig"); pub const Dwarf = @import("link/Dwarf.zig"); @@ -1449,6 +1500,8 @@ pub const PrelinkTask = union(enum) { /// Tells the linker to load a shared library, possibly one that is a /// GNU ld script. load_dso: Path, + load_tbd: Path, + load_darwin_sdk_settings: Path, }; pub const ZcuTask = union(enum) { /// Sent once per update, as the very first `ZcuTask` in the update. Indicates that all per-file @@ -1503,6 +1556,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { error.AlreadyReported => return, // error reported via diags else => |e| switch (input) { .dso => |dso| diags.addParseError(dso.path, "failed to parse shared library: {t}", .{e}), + .tbd => |tbd| diags.addParseError(tbd.path, "failed to parse tbd: {t}", .{e}), .object => |obj| diags.addParseError(obj.path, "failed to parse object: {t}", .{e}), .archive => |obj| diags.addParseError(obj.path, "failed to parse archive: {t}", .{e}), .res => |res| diags.addParseError(res.path, "failed to parse Windows resource: {t}", .{e}), @@ -1524,34 +1578,72 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { assert(mem.startsWith(u8, flag, "-l")); const lib_name = flag["-l".len..]; switch (comp.config.link_mode) { - .dynamic => { - const dso_path = Path.initCwd( - std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ - crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix(), - }) catch return diags.setAllocFailure(), - ); - base.openLoadDso(dso_path, .{ + .dynamic => loaded: { + if (target.os.tag.isDarwin()) { + // Prefer .tbd over .dylib. + const tbd_path: Path = .initCwd(std.fmt.allocPrint( + comp.arena, + "{s}" ++ sep ++ "{s}{s}.tbd", + .{ crt_dir, target.libPrefix(), lib_name }, + ) catch return diags.setAllocFailure()); + if (tbd_path.root_dir.handle.openFile(io, tbd_path.sub_path, .{})) |file| { + errdefer file.close(io); + base.loadInput(.{ .tbd = .{ + .path = tbd_path, + .file = file, + .needed = false, + .weak = false, + .reexport = false, + } }) catch |err| switch (err) { + error.AlreadyReported => return, + error.Canceled => io.recancel(), + else => |e| diags.addParseError(tbd_path, "failed to parse tbd: {t}", .{e}), + }; + break :loaded; + } else |err| switch (err) { + error.FileNotFound => {}, + error.Canceled => io.recancel(), + else => |e| diags.addParseError(tbd_path, "failed to parse tbd: {t}", .{e}), + } + } + + // Try dynamic library. + const dso_path: Path = .initCwd(std.fmt.allocPrint( + comp.arena, + "{s}" ++ sep ++ "{s}{s}{s}", + .{ crt_dir, target.libPrefix(), lib_name, target.dynamicLibSuffix() }, + ) catch return diags.setAllocFailure()); + if (base.openLoadDso(dso_path, .{ .preferred_mode = .dynamic, .search_strategy = .paths_first, - }) catch |err| switch (err) { - error.FileNotFound => { - // Also try static. - const archive_path = Path.initCwd( - std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ - crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(), - }) catch return diags.setAllocFailure(), - ); - base.openLoadArchiveQuery(archive_path, .{ - .preferred_mode = .dynamic, - .search_strategy = .paths_first, - }) catch |archive_err| switch (archive_err) { - error.AlreadyReported => return, // error reported via diags - else => |e| diags.addParseError(dso_path, "failed to parse archive {f}: {s}", .{ archive_path, @errorName(e) }), - }; - }, - error.AlreadyReported => return, // error reported via diags + })) { + break :loaded; + } else |err| switch (err) { + error.FileNotFound => {}, + error.AlreadyReported => return, + error.Canceled => io.recancel(), else => |e| diags.addParseError(dso_path, "failed to parse shared library: {s}", .{@errorName(e)}), - }; + } + + // Also try static. + const archive_path = Path.initCwd( + std.fmt.allocPrint(comp.arena, "{s}" ++ sep ++ "{s}{s}{s}", .{ + crt_dir, target.libPrefix(), lib_name, target.staticLibSuffix(), + }) catch return diags.setAllocFailure(), + ); + if (base.openLoadArchiveQuery(archive_path, .{ + .preferred_mode = .dynamic, + .search_strategy = .paths_first, + })) { + break :loaded; + } else |archive_err| switch (archive_err) { + error.FileNotFound => {}, + error.AlreadyReported => return, + error.Canceled => io.recancel(), + else => |e| diags.addParseError(archive_path, "failed to parse archive: {s}", .{@errorName(e)}), + } + + diags.addError("failed to find library '{s}'", .{lib_name}); }, .static => { const path = Path.initCwd( @@ -1571,6 +1663,19 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { } } + if (target.os.tag.isDarwin()) { + const sdk_settings_path: Path = .initCwd(std.fmt.allocPrint( + comp.arena, + "{s}" ++ sep ++ "SDKSettings.json", + .{libc_installation.darwin_sdk_dir.?}, + ) catch return diags.setAllocFailure()); + base.loadDarwinSdkSettings(sdk_settings_path) catch |err| switch (err) { + error.OutOfMemory => return diags.setAllocFailure(), + error.Canceled => return io.recancel(), + error.AlreadyReported => return, + }; + } + if (target.os.tag == .windows and target.abi == .msvc) { const inputs: []const struct { dir: enum { crt, msvc_lib, kernel32_lib }, @@ -1646,6 +1751,37 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { else => |e| diags.addParseError(path, "failed to parse shared library: {s}", .{@errorName(e)}), }; }, + .load_tbd => |path| { + const prog_node = comp.link_prog_node.start("Parse Shared Library Stub", 0); + defer prog_node.end(); + if (path.root_dir.handle.openFile(io, path.sub_path, .{})) |file| { + errdefer file.close(io); + base.loadInput(.{ .tbd = .{ + .path = path, + .file = file, + .needed = false, + .weak = false, + .reexport = false, + } }) catch |err| switch (err) { + error.AlreadyReported => return, + error.Canceled => io.recancel(), + else => |e| diags.addParseError(path, "failed to parse tbd: {t}", .{e}), + }; + } else |err| switch (err) { + error.FileNotFound => {}, + error.Canceled => io.recancel(), + else => |e| diags.addParseError(path, "failed to parse tbd: {t}", .{e}), + } + }, + .load_darwin_sdk_settings => |path| { + const prog_node = comp.link_prog_node.start("Parse SDK Settings", 0); + defer prog_node.end(); + base.loadDarwinSdkSettings(path) catch |err| switch (err) { + error.OutOfMemory => return diags.setAllocFailure(), + error.Canceled => return io.recancel(), + error.AlreadyReported => return, + }; + }, } } pub fn doZcuTask(comp: *Compilation, tid: Zcu.PerThread.Id, task: ZcuTask) void { @@ -1820,6 +1956,7 @@ pub const UnresolvedInput = union(enum) { /// Strings that come from GNU ld scripts. Is it a filename? Is it a path? /// Who knows! Fuck around and find out. ambiguous_name: AmbiguousNameQuery, + framework_query: FrameworkQuery, pub const NameQuery = struct { name: []const u8, @@ -1858,6 +1995,12 @@ pub const UnresolvedInput = union(enum) { } }; + pub const FrameworkQuery = struct { + name: []const u8, + needed: bool, + weak: bool, + }; + pub const SearchStrategy = enum { paths_first, mode_first, @@ -1872,6 +2015,8 @@ pub const Input = union(enum) { /// May not be a GNU ld script. Those are resolved when converting from /// `UnresolvedInput` to `Input` values. dso: Dso, + /// Only possible when targeting Darwin. + tbd: Tbd, pub const Object = struct { path: Path, @@ -1896,24 +2041,32 @@ pub const Input = union(enum) { pub const FallbackSoname = enum { basename, full_path }; }; + pub const Tbd = struct { + path: Path, + file: Io.File, + needed: bool, + weak: bool, + reexport: bool, + }; + pub fn path(input: Input) Path { return switch (input) { .object, .archive => |obj| obj.path, - inline .res, .dso => |x| x.path, + inline .res, .dso, .tbd => |x| x.path, }; } pub fn pathAndFile(input: Input) struct { Path, Io.File } { return switch (input) { .object, .archive => |obj| .{ obj.path, obj.file }, - inline .res, .dso => |x| .{ x.path, x.file }, + inline .res, .dso, .tbd => |x| .{ x.path, x.file }, }; } pub fn taskName(input: Input) []const u8 { return switch (input) { .object, .archive => |obj| obj.path.basename(), - inline .res, .dso => |x| x.path.basename(), + inline .res, .dso, .tbd => |x| x.path.basename(), }; } }; @@ -1946,6 +2099,15 @@ pub fn hashInputs(man: *Cache.Manifest, link_inputs: []const Input) !void { man.hash.add(dso.reexport); man.hash.add(dso.fallback_soname); }, + .tbd => |tbd| { + _ = try man.addInputPath(tbd.path, .{ + .handle = .{ .file = tbd.file }, + .request_handle = true, + }); + man.hash.add(tbd.needed); + man.hash.add(tbd.weak); + man.hash.add(tbd.reexport); + }, } } } @@ -1961,6 +2123,7 @@ pub fn resolveInputs( /// Allocated with `gpa`. resolved_inputs: *std.ArrayList(Input), lib_directories: []const Cache.Directory, + framework_directories: []const Cache.Directory, color: std.zig.Color, ) Allocator.Error!void { var checked_paths: std.ArrayList(u8) = .empty; @@ -1972,6 +2135,7 @@ pub fn resolveInputs( var archive_dedup: ArchiveDedupMap = .empty; defer archive_dedup.deinit(gpa); + // Allocated with `arena`. var failed_libs: std.ArrayList(struct { name: []const u8, strategy: UnresolvedInput.SearchStrategy, @@ -1979,6 +2143,12 @@ pub fn resolveInputs( preferred_mode: std.lang.LinkMode, }) = .empty; + // Allocated with `arena`. + var failed_frameworks: std.ArrayList(struct { + name: []const u8, + checked_paths: []const u8, + }) = .empty; + // Convert external system libs into a stack so that items can be // pushed to it. // @@ -2180,17 +2350,47 @@ pub fn resolveInputs( } continue; }, + .framework_query => |framework_query| { + checked_paths.clearRetainingCapacity(); + for (framework_directories) |framework_directory| { + switch (try resolveFrameworkInput( + gpa, + arena, + io, + resolved_inputs, + &checked_paths, + &ld_script_bytes, + &archive_dedup, + framework_directory, + framework_query, + )) { + .ok => continue :syslib, + .no_match => {}, + } + } + try failed_frameworks.append(arena, .{ + .name = framework_query.name, + .checked_paths = try arena.dupe(u8, checked_paths.items), + }); + continue :syslib; + }, } comptime unreachable; } - if (failed_libs.items.len > 0) { + if (failed_libs.items.len > 0 or failed_frameworks.items.len > 0) { for (failed_libs.items) |f| { const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths; std.log.err("unable to find {t} system library {q} using strategy {t}. searched paths:{s}", .{ f.preferred_mode, f.name, f.strategy, searched_paths, }); } + for (failed_frameworks.items) |f| { + const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths; + std.log.err("unable to find system framework {q}. searched paths:{s}", .{ + f.name, searched_paths, + }); + } std.process.exit(1); } } @@ -2235,16 +2435,14 @@ fn resolveLibInput( else => |e| fatal("searching for tbd library {qf}: {t}", .{ test_path, e }), }; errdefer file.close(io); - return finishResolveLibInput( - io, - resolved_inputs, - archive_dedup, - test_path, - file, - link_mode, - name_query.query, - .basename, - ); + resolved_inputs.appendAssumeCapacity(.{ .tbd = .{ + .path = test_path, + .file = file, + .needed = name_query.query.needed, + .weak = name_query.query.weak, + .reexport = name_query.query.reexport, + } }); + return .ok; } { @@ -2282,16 +2480,15 @@ fn resolveLibInput( else => |e| fatal("unable to search for so library {qf}: {t}", .{ test_path, e }), }; errdefer file.close(io); - return finishResolveLibInput( - io, - resolved_inputs, - archive_dedup, - test_path, - file, - link_mode, - name_query.query, - .basename, - ); + resolved_inputs.appendAssumeCapacity(.{ .dso = .{ + .path = test_path, + .file = file, + .needed = name_query.query.needed, + .weak = name_query.query.weak, + .reexport = name_query.query.reexport, + .fallback_soname = .basename, + } }); + return .ok; } // In the case of MinGW, the main check will be .lib but we also need to @@ -2307,16 +2504,13 @@ fn resolveLibInput( else => |e| fatal("unable to search for static library {qf}: {t}", .{ test_path, e }), }; errdefer file.close(io); - return finishResolveLibInput( - io, - resolved_inputs, - archive_dedup, - test_path, - file, - link_mode, - name_query.query, - .basename, - ); + addResolvedStaticLibInput(io, resolved_inputs, archive_dedup, .{ + .path = test_path, + .file = file, + .must_link = name_query.query.must_link, + .hidden = name_query.query.hidden, + }); + return .ok; } // In the case of OpenBSD, dynamic libraries are always versioned, without @@ -2378,6 +2572,145 @@ fn resolveLibInput( return .no_match; } +fn resolveFrameworkInput( + gpa: Allocator, + arena: Allocator, + io: Io, + /// Allocated via `gpa`. + resolved_inputs: *std.ArrayList(Input), + /// Allocated via `gpa`. + checked_paths: *std.ArrayList(u8), + /// Allocated via `gpa`. + ld_script_bytes: *std.ArrayList(u8), + /// Allocated via `gpa`. + archive_dedup: *ArchiveDedupMap, + framework_directory: Directory, + framework_query: UnresolvedInput.FrameworkQuery, +) Allocator.Error!ResolveLibInputResult { + const sep = std.fs.path.sep_str; + + tbd: { + const test_path: Path = .{ + .root_dir = framework_directory, + .sub_path = try std.fmt.allocPrint( + arena, + "{s}.framework" ++ sep ++ "{s}.tbd", + .{ framework_query.name, framework_query.name }, + ), + }; + try checked_paths.print(gpa, "\n {f}", .{test_path}); + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => break :tbd, + else => |e| fatal("unable to search for tbd library {qf}: {t}", .{ test_path, e }), + }; + errdefer file.close(io); + resolved_inputs.appendAssumeCapacity(.{ .tbd = .{ + .path = test_path, + .file = file, + .needed = framework_query.needed, + .weak = framework_query.weak, + .reexport = false, + } }); + return .ok; + } + + dylib: { + const test_path: Path = .{ + .root_dir = framework_directory, + .sub_path = try std.fmt.allocPrint( + arena, + "{s}.framework" ++ sep ++ "{s}.dylib", + .{ framework_query.name, framework_query.name }, + ), + }; + try checked_paths.print(gpa, "\n {f}", .{test_path}); + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => break :dylib, + else => |e| fatal("unable to search for dynamic library {qf}: {t}", .{ test_path, e }), + }; + errdefer file.close(io); + resolved_inputs.appendAssumeCapacity(.{ .dso = .{ + .path = test_path, + .file = file, + .needed = framework_query.needed, + .weak = framework_query.weak, + .reexport = false, + .fallback_soname = .basename, + } }); + return .ok; + } + + ambiguous: { + const test_path: Path = .{ + .root_dir = framework_directory, + .sub_path = try std.fmt.allocPrint( + arena, + "{s}.framework" ++ sep ++ "{s}", + .{ framework_query.name, framework_query.name }, + ), + }; + try checked_paths.print(gpa, "\n {f}", .{test_path}); + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => break :ambiguous, + else => |e| fatal("unable to search for framework library {qf}: {t}", .{ test_path, e }), + }; + errdefer file.close(io); + + const macho_magics: []const [4]u8 = &.{ + @bitCast(std.macho.MH_MAGIC), + @bitCast(std.macho.MH_CIGAM), + @bitCast(std.macho.MH_MAGIC_64), + @bitCast(std.macho.MH_CIGAM_64), + @bitCast(std.macho.FAT_MAGIC), + @bitCast(std.macho.FAT_CIGAM), + @bitCast(std.macho.FAT_MAGIC_64), + @bitCast(std.macho.FAT_CIGAM_64), + }; + try ld_script_bytes.resize(gpa, @max(4, std.macho.ARMAG.len)); + const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err| + fatal("failed to read {qf}: {t}", .{ test_path, err }); + const buf = ld_script_bytes.items[0..n]; + + for (macho_magics) |*macho_magic| { + if (mem.startsWith(u8, buf, macho_magic)) { + // Appears to be a Mach-O file, so a dylib. + resolved_inputs.appendAssumeCapacity(.{ .dso = .{ + .path = test_path, + .file = file, + .needed = framework_query.needed, + .weak = framework_query.weak, + .reexport = false, + .fallback_soname = .basename, + } }); + return .ok; + } + } + + if (mem.startsWith(u8, buf, std.macho.ARMAG)) { + // Appears to be an archive file. + addResolvedStaticLibInput(io, resolved_inputs, archive_dedup, .{ + .path = test_path, + .file = file, + .must_link = false, + .hidden = false, + }); + return .ok; + } + + // It doesn't look like a dylib or archive, so assume it's a tbd. + resolved_inputs.appendAssumeCapacity(.{ .tbd = .{ + .path = test_path, + .file = file, + .needed = framework_query.needed, + .weak = framework_query.weak, + .reexport = false, + } }); + return .ok; + } + + return .no_match; +} + /// Deduplicates static archive link inputs based on their path. This is done for efficiency, so /// that linker implementations do not need to open and scan the archive just to determine that they /// need not extract any objects. At the time of writing, it also helps avoid "multiple definitions @@ -2401,45 +2734,21 @@ const ArchiveDedupAdapter = struct { } }; -fn finishResolveLibInput( +fn addResolvedStaticLibInput( io: Io, resolved_inputs: *std.ArrayList(Input), archive_dedup: *ArchiveDedupMap, - path: Path, - file: Io.File, - link_mode: std.lang.LinkMode, - query: UnresolvedInput.Query, - fallback_soname: Input.Dso.FallbackSoname, -) ResolveLibInputResult { - switch (link_mode) { - .static => { - const ctx: ArchiveDedupAdapter = .{ .resolved_inputs = resolved_inputs.items }; - const gop = archive_dedup.getOrPutAssumeCapacityAdapted(path, ctx); - if (gop.found_existing) { - // Ignore duplicate archive input - file.close(io); - return .ok; - } - gop.key_ptr.* = @intCast(resolved_inputs.items.len); - resolved_inputs.appendAssumeCapacity(.{ .archive = .{ - .path = path, - .file = file, - .must_link = query.must_link, - .hidden = query.hidden, - } }); - }, - .dynamic => resolved_inputs.appendAssumeCapacity(.{ - .dso = .{ - .path = path, - .file = file, - .needed = query.needed, - .weak = query.weak, - .reexport = query.reexport, - .fallback_soname = fallback_soname, - }, - }), + archive: Input.Object, +) void { + const ctx: ArchiveDedupAdapter = .{ .resolved_inputs = resolved_inputs.items }; + const gop = archive_dedup.getOrPutAssumeCapacityAdapted(archive.path, ctx); + if (gop.found_existing) { + // Ignore duplicate archive input + archive.file.close(io); + } else { + gop.key_ptr.* = @intCast(resolved_inputs.items.len); + resolved_inputs.appendAssumeCapacity(.{ .archive = archive }); } - return .ok; } fn resolvePathInput( @@ -2535,19 +2844,24 @@ fn resolvePathInputLib( try archive_dedup.ensureUnusedCapacity(gpa, 1); const test_path: Path = pq.path; + + var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { + error.FileNotFound => return .no_match, + else => |e| fatal("unable to search for {t} library {qf}: {t}", .{ + link_mode, std.fmt.alt(test_path, .formatEscapeChar), e, + }), + }; + errdefer file.close(io); + // In the case of shared libraries, they might actually be "linker scripts" // that contain references to other libraries. - if (pq.query.allow_so_scripts and target.ofmt == .elf and switch (Compilation.classifyFileExt(test_path.sub_path)) { - .static_library, .shared_library => true, - else => false, - }) { - var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { - error.FileNotFound => return .no_match, - else => |e| fatal("unable to search for {t} library {qf}: {t}", .{ - link_mode, std.fmt.alt(test_path, .formatEscapeChar), e, - }), - }; - errdefer file.close(io); + ld_script: { + if (!pq.query.allow_so_scripts) break :ld_script; + if (target.ofmt != .elf) break :ld_script; + switch (Compilation.classifyFileExt(test_path.sub_path)) { + .static_library, .shared_library => {}, + else => break :ld_script, + } try ld_script_bytes.resize(gpa, @max(std.elf.MAGIC.len, std.elf.ARMAG.len)); const n = file.readPositionalAll(io, ld_script_bytes.items, 0) catch |err| fatal("failed to read {qf}: {t}", .{ test_path, err }); @@ -2557,16 +2871,7 @@ fn resolvePathInputLib( mem.startsWith(u8, buf, std.elf.ARMAG_THIN)) { // Appears to be an ELF or archive file. - return finishResolveLibInput( - io, - resolved_inputs, - archive_dedup, - test_path, - file, - link_mode, - pq.query, - fallback_soname, - ); + break :ld_script; } const stat = file.stat(io) catch |err| fatal("failed to stat {f}: {t}", .{ test_path, err }); @@ -2630,21 +2935,23 @@ fn resolvePathInputLib( return .ok; } - var file = test_path.root_dir.handle.openFile(io, test_path.sub_path, .{}) catch |err| switch (err) { - error.FileNotFound => return .no_match, - else => |e| fatal("unable to search for {t} library {f}: {t}", .{ link_mode, test_path, e }), - }; - errdefer file.close(io); - return finishResolveLibInput( - io, - resolved_inputs, - archive_dedup, - test_path, - file, - link_mode, - pq.query, - fallback_soname, - ); + switch (link_mode) { + .dynamic => resolved_inputs.appendAssumeCapacity(.{ .dso = .{ + .path = test_path, + .file = file, + .needed = pq.query.needed, + .weak = pq.query.weak, + .reexport = pq.query.reexport, + .fallback_soname = fallback_soname, + } }), + .static => addResolvedStaticLibInput(io, resolved_inputs, archive_dedup, .{ + .path = test_path, + .file = file, + .must_link = pq.query.must_link, + .hidden = pq.query.hidden, + }), + } + return .ok; } pub fn openObject(io: Io, path: Path, must_link: bool, hidden: bool) !Input.Object { @@ -2699,7 +3006,7 @@ pub fn anyObjectInputs(inputs: []const Input) bool { pub fn countObjectInputs(inputs: []const Input) usize { var count: usize = 0; for (inputs) |input| switch (input) { - .dso => continue, + .dso, .tbd => continue, .res, .object, .archive => count += 1, }; return count; @@ -2709,7 +3016,80 @@ pub fn countObjectInputs(inputs: []const Input) usize { pub fn firstObjectInput(inputs: []const Input) ?Input.Object { for (inputs) |input| switch (input) { .object, .archive => |obj| return obj, - .res, .dso => continue, + .res, .dso, .tbd => continue, }; return null; } + +pub const DarwinSdkVersion = packed struct(u32) { + patch: u8, + minor: u8, + major: u16, + + fn parse( + diags: *Diags, + /// Passed to `Diags.failParse` on error. + sdk_settings_path: Path, + version_str: []const u8, + ) error{AlreadyReported}!DarwinSdkVersion { + var it = std.mem.splitScalar(u8, version_str, '.'); + + const major = try parseComponent( + .major, + diags, + sdk_settings_path, + version_str, + it.first(), + ); + const minor = if (it.next()) |component_str| try parseComponent( + .minor, + diags, + sdk_settings_path, + version_str, + component_str, + ) else return diags.failParse( + sdk_settings_path, + "failed to parse Darwin SDK version {q}: missing minor version", + .{version_str}, + ); + const patch = if (it.next()) |component_str| try parseComponent( + .patch, + diags, + sdk_settings_path, + version_str, + component_str, + ) else 0; // Apple sometimes omit the patch version + + if (it.next() != null) return diags.failParse( + sdk_settings_path, + "failed to parse Darwin SDK version {q}: too many version components", + .{version_str}, + ); + + return .{ .major = major, .minor = minor, .patch = patch }; + } + + fn parseComponent( + comptime component: enum { major, minor, patch }, + diags: *Diags, + /// Passed to `Diags.failParse` on error. + sdk_settings_path: Path, + /// Only used for error messages. + version_str: []const u8, + component_str: []const u8, + ) error{AlreadyReported}!@FieldType(DarwinSdkVersion, @tagName(component)) { + const Int = @FieldType(DarwinSdkVersion, @tagName(component)); + return std.fmt.parseInt(Int, component_str, 10) catch |err| switch (err) { + error.Overflow => return diags.failParse( + sdk_settings_path, + "failed to parse Darwin SDK version {q}: {t} version {q} too large", + .{ version_str, component, component_str }, + ), + error.InvalidCharacter => return diags.failParse( + sdk_settings_path, + "failed to parse Darwin SDK version {q}: invalid {t} version {q}", + .{ version_str, component, component_str }, + ), + }; + } +}; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 250dbd388658148ff196567923b5e0de593f72eb..754c3ad9a830078489427c9794455642527b7f20 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1607,7 +1607,7 @@ fn create( errdefer file.close(io); coff.* = .{ .base = .{ - .tag = .coff2, + .tag = .coff, .comp = comp, .emit = path, @@ -2935,18 +2935,19 @@ fn navMapIndex(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Node.Na if (!sym_gop.found_existing) sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity(); return @fromBackingInt(@intCast(sym_gop.index)); } -pub fn navSymbol(coff: *Coff, zcu: *Zcu, nav_index: InternPool.Nav.Index) !Symbol.Index { +pub fn navSymbol(coff: *Coff, nav_index: InternPool.Nav.Index) link.Error!link.File.SymbolId { + const zcu = coff.base.comp.zcu.?; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - if (nav.getExtern(ip)) |@"extern"| return coff.globalSymbol(.{ + if (nav.getExtern(ip)) |@"extern"| return @bitCast(try coff.globalSymbol(.{ .name = @"extern".name.toSlice(ip), .lib_name = @"extern".lib_name.toSlice(ip), // TODO: Threadlocal as well? .type = if (ip.isFunctionType(nav.resolved.?.type)) .code else .data, .dll_storage_class = if (@"extern".is_dll_import) .dllimport else .default, - }); + })); const nmi = try coff.navMapIndex(zcu, nav_index); - return nmi.symbol(coff); + return @bitCast(nmi.symbol(coff)); } fn uavMapIndex(coff: *Coff, uav_val: InternPool.Index) !Node.UavMapIndex { @@ -2956,10 +2957,6 @@ fn uavMapIndex(coff: *Coff, uav_val: InternPool.Index) !Node.UavMapIndex { if (!sym_gop.found_existing) sym_gop.value_ptr.* = coff.addSymbolAssumeCapacity(); return @fromBackingInt(@intCast(sym_gop.index)); } -pub fn uavSymbol(coff: *Coff, uav_val: InternPool.Index) !Symbol.Index { - const umi = try coff.uavMapIndex(uav_val); - return umi.symbol(coff); -} pub fn lazySymbol(coff: *Coff, lazy: link.File.LazySymbol) !Symbol.Index { const gpa = coff.base.comp.gpa; @@ -2972,24 +2969,8 @@ pub fn lazySymbol(coff: *Coff, lazy: link.File.LazySymbol) !Symbol.Index { return sym_gop.value_ptr.*; } -pub fn getNavVAddr( - coff: *Coff, - pt: Zcu.PerThread, - nav: InternPool.Nav.Index, - reloc_info: link.File.RelocInfo, -) link.Error!u64 { - return coff.getVAddr(reloc_info, try coff.navSymbol(pt.zcu, nav)); -} - -pub fn getUavVAddr( - coff: *Coff, - uav: InternPool.Index, - reloc_info: link.File.RelocInfo, -) link.Error!u64 { - return coff.getVAddr(reloc_info, try coff.uavSymbol(uav)); -} - -pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol.Index) link.Error!u64 { +pub fn relocSymAddr(coff: *Coff, reloc_info: link.File.RelocInfo) link.Error!void { + const target_si: Symbol.Index = @bitCast(reloc_info.target); try coff.addReloc( @fromBackingInt(@intCast(@backingInt(reloc_info.parent.atom_index))), reloc_info.offset, @@ -3001,10 +2982,6 @@ pub fn getVAddr(coff: *Coff, reloc_info: link.File.RelocInfo, target_si: Symbol. .I386 => .{ .I386 = .DIR32 }, }, ); - - var vaddr: u64 = target_si.get(coff).rva; - if (coff.isImage()) vaddr += coff.optionalHeaderField(.image_base); - return vaddr; } /// Caller guarantees there is capacity for one member and two nodes @@ -3935,6 +3912,7 @@ pub fn loadInput(coff: *Coff, input: link.Input) link.Error!void { coff.loadDll(dso.path, &fr) catch |err| return coff.failLoadInput(err, &fr, dso.path); }, + .tbd => unreachable, } } @@ -5580,13 +5558,15 @@ pub fn updateContainerType( }; } -pub fn lowerUav( +pub fn uavSymbol( coff: *Coff, pt: Zcu.PerThread, uav_val: InternPool.Index, uav_align: InternPool.Alignment, ) link.Error!link.File.SymbolId { - const zcu = pt.zcu; + _ = pt; + + const zcu = coff.base.comp.zcu.?; const gpa = zcu.gpa; try coff.pending_uavs.ensureUnusedCapacity(gpa, 1); @@ -7471,8 +7451,8 @@ fn updateExportInner( try coff.symbols.ensureUnusedCapacity(gpa, 1); const exported_si: Symbol.Index = switch (exp.exported) { - .nav => |nav| try coff.navSymbol(zcu, nav), - .uav => |uav| @fromBackingInt(@intCast(@backingInt(try coff.lowerUav( + .nav => |nav| @bitCast(try coff.navSymbol(nav)), + .uav => |uav| @fromBackingInt(@intCast(@backingInt(try coff.uavSymbol( pt, uav, Type.fromInterned(ip.typeOf(uav)).abiAlignment(zcu), diff --git a/src/link/Elf.zig b/src/link/Elf.zig index b0e188390f24c3000d382aae4b76646fd6306644..5a3aa2648a58c61e59be964f0058525efa650fd9 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -463,21 +463,21 @@ pub fn deinit(self: *Elf) void { self.dump_argv_list.deinit(gpa); } -pub fn getNavVAddr(self: *Elf, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: link.File.RelocInfo) !u64 { - return self.zigObjectPtr().?.getNavVAddr(self, pt, nav_index, reloc_info); +pub fn navSymbol(self: *Elf, nav: InternPool.Nav.Index) link.Error!link.File.SymbolId { + return self.zigObjectPtr().?.navSymbol(self, nav); } -pub fn lowerUav( +pub fn relocSymAddr(self: *Elf, reloc_info: link.File.RelocInfo) !void { + return self.zigObjectPtr().?.relocSymAddr(self, reloc_info); +} + +pub fn uavSymbol( self: *Elf, pt: Zcu.PerThread, uav: InternPool.Index, explicit_alignment: InternPool.Alignment, ) !link.File.SymbolId { - return self.zigObjectPtr().?.lowerUav(self, pt, uav, explicit_alignment); -} - -pub fn getUavVAddr(self: *Elf, uav: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 { - return self.zigObjectPtr().?.getUavVAddr(self, uav, reloc_info); + return self.zigObjectPtr().?.uavSymbol(self, pt, uav, explicit_alignment); } /// Returns end pos of collision, if any. @@ -722,6 +722,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { const argv = &self.dump_argv_list; switch (input) { .res => unreachable, + .tbd => unreachable, .object, .archive => |obj| try argv.append(gpa, try obj.path.toString(comp.arena)), .dso => |dso| try argv.append(gpa, try dso.path.toString(comp.arena)), } @@ -729,6 +730,7 @@ pub fn loadInput(self: *Elf, input: link.Input) !void { switch (input) { .res => unreachable, + .tbd => unreachable, .object => |obj| try parseObject(self, obj), .archive => |obj| if (self.base.isStaticLib()) { // Ignore static library inputs when generating a static library. diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index ea092197086be61f652433464a06f31a481e7ebe..551f4c626ddb3a0e819eb158a4290c271d5d34be 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -917,24 +917,42 @@ pub fn codeAlloc(self: *ZigObject, elf_file: *Elf, atom_index: Atom.Index) ![]u8 return code; } -pub fn getNavVAddr( - self: *ZigObject, +pub fn navSymbol( + zo: *ZigObject, elf_file: *Elf, - pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, - reloc_info: link.File.RelocInfo, -) !u64 { - const zcu = pt.zcu; +) link.Error!link.File.SymbolId { + const zcu = elf_file.base.comp.zcu.?; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - log.debug("getNavVAddr {f}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const this_sym_index = if (nav.getExtern(ip)) |@"extern"| try self.getGlobalSymbol( - elf_file, - nav.name.toSlice(ip), - @"extern".lib_name.toSlice(ip), - ) else try self.getOrCreateMetadataForNav(zcu, nav_index); - const this_sym = self.symbol(this_sym_index); - const vaddr = this_sym.address(.{}, elf_file); + if (nav.getExtern(ip)) |@"extern"| { + const sym_index = try zo.getGlobalSymbol( + elf_file, + nav.name.toSlice(ip), + @"extern".lib_name.toSlice(ip), + ); + if (@"extern".linkage == .weak) { + zo.symbol(sym_index).flags.weak = true; + } + if (nav.resolved.?.@"threadlocal") { + zo.symbol(sym_index).flags.is_tls = true; + } + return @fromBackingInt(sym_index); + } else { + const sym_index = try zo.getOrCreateMetadataForNav(zcu, nav_index); + if (nav.resolved.?.@"threadlocal") { + zo.symbol(sym_index).flags.is_tls = true; + } + return @fromBackingInt(sym_index); + } +} + +pub fn relocSymAddr( + self: *ZigObject, + elf_file: *Elf, + reloc_info: link.File.RelocInfo, +) !void { + const this_sym_index = @backingInt(reloc_info.target); switch (reloc_info.parent) { .none => unreachable, .atom_index => |atom_index| { @@ -952,39 +970,9 @@ pub fn getNavVAddr( .target_off = reloc_info.addend, }), } - return @intCast(vaddr); } -pub fn getUavVAddr( - self: *ZigObject, - elf_file: *Elf, - uav: InternPool.Index, - reloc_info: link.File.RelocInfo, -) !u64 { - const sym_index = self.uavs.get(uav).?.symbol_index; - const sym = self.symbol(sym_index); - const vaddr = sym.address(.{}, elf_file); - switch (reloc_info.parent) { - .none => unreachable, - .atom_index => |atom_index| { - const parent_atom = self.symbol(@backingInt(atom_index)).atom(elf_file).?; - const r_type = relocation.encode(.abs, elf_file.getTarget().cpu.arch); - try parent_atom.addReloc(elf_file.base.comp.gpa, .{ - .r_offset = reloc_info.offset, - .r_info = (@as(u64, @intCast(sym_index)) << 32) | r_type, - .r_addend = reloc_info.addend, - }, self); - }, - .debug_output => |debug_output| try debug_output.dwarf.infoExternalReloc(.{ - .source_off = @intCast(reloc_info.offset), - .target_sym = @fromBackingInt(@intCast(sym_index)), - .target_off = reloc_info.addend, - }), - } - return @intCast(vaddr); -} - -pub fn lowerUav( +pub fn uavSymbol( self: *ZigObject, elf_file: *Elf, pt: Zcu.PerThread, @@ -1889,7 +1877,7 @@ pub fn updateExports( break :blk self.navs.getPtr(nav).?; }, .uav => |uav| self.uavs.getPtr(uav) orelse blk: { - _ = try self.lowerUav(elf_file, pt, uav, .none); + _ = try self.uavSymbol(elf_file, pt, uav, .none); break :blk self.uavs.getPtr(uav).?; }, }; diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index 10043990722a7482f2a537aa8191b6c81020787f..37d7dfe0499aeb8a9b80e983e6eb8785739eb75e 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -4324,36 +4324,7 @@ pub fn navSymbol(elf: *Elf, nav_index: InternPool.Nav.Index) link.Error!link.Fil const s: Symbol.Id = .local(nmi.symbol(elf)); return s.toTypeErased(); } -pub fn uavSymbol( - elf: *Elf, - uav_val: InternPool.Index, - uav_align: InternPool.Alignment, -) link.Error!link.File.SymbolId { - const diags = &elf.base.comp.link_diags; - const umi = elf.uavMapIndex(uav_val, uav_align) catch |err| switch (err) { - else => |e| return e, - error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{elf.mf.io_err.?}), - }; - const s: Symbol.Id = .local(umi.symbol(elf)); - return s.toTypeErased(); -} -pub fn getNavVAddr( - elf: *Elf, - pt: Zcu.PerThread, - nav: InternPool.Nav.Index, - reloc_info: link.File.RelocInfo, -) link.Error!u64 { - _ = pt; - return elf.getVAddr(reloc_info, try elf.navSymbol(nav)); -} -pub fn getUavVAddr( - elf: *Elf, - uav_val: InternPool.Index, - reloc_info: link.File.RelocInfo, -) link.Error!u64 { - return elf.getVAddr(reloc_info, try elf.uavSymbol(uav_val, .none)); -} -pub fn getVAddr(elf: *Elf, reloc_info: link.File.RelocInfo, target: link.File.SymbolId) link.Error!u64 { +pub fn relocSymAddr(elf: *Elf, reloc_info: link.File.RelocInfo) link.Error!void { try elf.addReloc( switch (reloc_info.parent) { .none => unreachable, @@ -4361,13 +4332,12 @@ pub fn getVAddr(elf: *Elf, reloc_info: link.File.RelocInfo, target: link.File.Sy .debug_output => |debug_output| Node.toAtom(debug_output.dwarf2.info_writer.ni), }, reloc_info.offset, - target, + reloc_info.target, reloc_info.addend, .absAddr(elf), ); - return Symbol.Id.fromTypeErased(target).value(elf); } -pub fn lowerUav( +pub fn uavSymbol( elf: *Elf, pt: Zcu.PerThread, uav_val: InternPool.Index, @@ -7176,7 +7146,6 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void }, }; }, - .res => unreachable, .dso => |dso| { try elf.needed.ensureUnusedCapacity(elf.base.comp.gpa, 1); var fr = dso.file.reader(io, &buf); @@ -7193,6 +7162,8 @@ fn loadInputInner(elf: *Elf, input: link.Input) (Error || error{BadMagic})!void }, }; }, + .res => unreachable, + .tbd => unreachable, } } fn loadArchive(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) (LoadParseInputError || error{BadMagic})!void { @@ -12384,7 +12355,7 @@ pub fn printNode( mf_node.flags.alignment.toByteUnits(), mf_node.flags.position, if (mf_node.flags.bubbles_moved) " bubbles_moved" else "", - if (mf_node.flags.resized) " moved" else "", + if (mf_node.flags.moved) " moved" else "", if (mf_node.flags.resized) " resized" else "", if (mf_node.flags.enable_next_moved) " enable_next_moved" else "", if (mf_node.flags.next_moved) " next_moved" else "", diff --git a/src/link/Lld.zig b/src/link/Lld.zig index c56557dfb02336a36ab88ec32170b705a64d5e53..64a9aeeebf2e94889cd258ba0edc877a4518c1f4 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -309,7 +309,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) link.Error!void { try object_files.ensureUnusedCapacity(arena, comp.link_inputs.len); for (comp.link_inputs) |input| switch (input) { - .dso, .archive => {}, // static archives should not contain shared libraries or other static archives + .dso, .tbd, .archive => {}, // static archives should not contain shared libraries or other static archives .res, .object => { const path = try input.path().toStringZ(arena); object_files.appendAssumeCapacity(path); @@ -561,6 +561,7 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { argv.appendAssumeCapacity(try obj.path.toString(arena)); } }, + .tbd => unreachable, }; for (comp.c_objects.items) |c_object| { @@ -1102,6 +1103,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { for (base.comp.link_inputs) |link_input| switch (link_input) { .res => unreachable, // Windows-only + .tbd => unreachable, // Darwin-only .dso => continue, .object, .archive => |obj| { if (obj.must_link and !whole_archive) { @@ -1163,6 +1165,7 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { for (base.comp.link_inputs) |link_input| switch (link_input) { .res => unreachable, // Windows-only + .tbd => unreachable, // Darwin-only .object, .archive => continue, .dso => |dso| { const lib_as_needed = !dso.needed; @@ -1596,6 +1599,7 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { .dso => |dso| { try argv.append(try dso.path.toString(arena)); }, + .tbd => unreachable, .res => unreachable, }; if (whole_archive) { diff --git a/src/link/MachO.zig b/src/link/MachO.zig index d321d45939cd883d40a3dadd32462071b442fe67..eb3e6d05432ca0c7af4a86b2468041229bccf414 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -4,6 +4,8 @@ pub const Relocation = @import("MachO/Relocation.zig"); base: link.File, +all_inputs: std.ArrayList(link.Input), + rpath_list: []const []const u8, /// Debug symbols bundle (or dSym). @@ -104,9 +106,7 @@ has_tlv: AtomicBool = AtomicBool.init(false), binds_to_weak: AtomicBool = AtomicBool.init(false), weak_defines: AtomicBool = AtomicBool.init(false), -/// Options -/// SDK layout -sdk_layout: ?SdkLayout, +// Options /// Size of the __PAGEZERO segment. pagezero_size: ?u64, /// Minimum space for future expansion of the load commands. @@ -119,10 +119,6 @@ dead_strip_dylibs: bool, undefined_treatment: UndefinedTreatment, /// TODO: delete this, libraries need to be resolved by the frontend instead lib_directories: []const Directory, -/// Resolved list of framework search directories -framework_dirs: []const []const u8, -/// List of input frameworks -frameworks: []const Framework, /// Install name for the dylib. /// TODO: unify with soname install_name: ?[]const u8, @@ -132,7 +128,7 @@ compatibility_version: ?std.SemanticVersion, /// Entry name entry_name: ?[]const u8, platform: Platform, -sdk_version: ?std.SemanticVersion, +sdk_version: ?link.DarwinSdkVersion, /// When set to true, the linker will hoist all dylibs including system dependent dylibs. no_implicit_dylibs: bool = false, /// Whether the linker should parse and always force load objects containing ObjC in archives. @@ -190,13 +186,12 @@ pub fn createEmpty( .file = null, .build_id = options.build_id, }, + .all_inputs = .empty, .rpath_list = options.rpath_list, .pagezero_size = options.pagezero_size, .headerpad_size = options.headerpad_size, .headerpad_max_install_names = options.headerpad_max_install_names, .dead_strip_dylibs = options.dead_strip_dylibs, - .sdk_layout = options.darwin_sdk_layout, - .frameworks = options.frameworks, .install_name = options.install_name, .entitlements = options.entitlements, .compatibility_version = options.compatibility_version, @@ -207,11 +202,10 @@ pub fn createEmpty( .named => |name| name, }, .platform = Platform.fromTarget(target), - .sdk_version = if (options.darwin_sdk_layout) |layout| inferSdkVersion(comp, layout) else null, + .sdk_version = null, .undefined_treatment = if (allow_shlib_undefined) .dynamic_lookup else .@"error", // TODO delete this, directories must instead be resolved by the frontend .lib_directories = options.lib_directories, - .framework_dirs = options.framework_dirs, .force_load_objc = options.force_load_objc, .discard_local_symbols = options.discard_local_symbols, }; @@ -271,6 +265,8 @@ pub fn deinit(self: *MachO) void { const gpa = comp.gpa; const io = comp.io; + self.all_inputs.deinit(gpa); + if (self.d_sym) |*d_sym| { d_sym.deinit(); } @@ -334,6 +330,11 @@ pub fn deinit(self: *MachO) void { self.thunks.deinit(gpa); } +pub fn loadInput(self: *MachO, input: link.Input) link.Error!void { + const gpa = self.base.comp.gpa; + try self.all_inputs.append(gpa, input); +} + pub fn flush( self: *MachO, arena: Allocator, @@ -343,6 +344,8 @@ pub fn flush( const tracy = trace(@src()); defer tracy.end(); + _ = arena; + const comp = self.base.comp; const gpa = comp.gpa; const io = comp.io; @@ -351,146 +354,40 @@ pub fn flush( const sub_prog_node = prog_node.start("MachO Flush", 0); defer sub_prog_node.end(); - const zcu_obj_path: ?Path = p: { - const zcu = comp.zcu orelse break :p null; - const llvm_object = zcu.llvm_object orelse break :p null; - break :p try comp.resolveEmitPathFlush(arena, .temp, llvm_object.out_bin_basename); - }; + // TODO: due to https://codeberg.org/ziglang/zig/issues/36946, we receive link inputs in a + // non-deterministic order. To ensure that this linker behaves deterministically, sort the + // inputs into a consistent order now. + { + const InputSortContext = struct { + fn lessThan(ctx: @This(), lhs: link.Input, rhs: link.Input) bool { + _ = ctx; + // We can't sort by full path because that could vary between runs where we still + // want reproducibility (e.g. the path is affected by cwd). So instead, let's sort + // by basename---that's good enough for our purposes since we never link two + // vendored libraries/objects with the same name. + const lhs_name = fs.path.basename(lhs.path().sub_path); + const rhs_name = fs.path.basename(rhs.path().sub_path); + return std.mem.order(u8, lhs_name, rhs_name).compare(.lt); + } + }; + std.mem.sort( + link.Input, + self.all_inputs.items, + @as(InputSortContext, .{}), + InputSortContext.lessThan, + ); + } // --verbose-link if (comp.verbose_link) try self.dumpArgv(comp); if (self.getZigObject()) |zo| try zo.flush(self, tid); - if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, zcu_obj_path); - if (self.base.isObject()) return relocatable.flushObject(self, comp, zcu_obj_path); + if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp); + if (self.base.isObject()) return relocatable.flushObject(self, comp); - var positionals = std.array_list.Managed(link.Input).init(gpa); - defer positionals.deinit(); - - try positionals.ensureUnusedCapacity(comp.link_inputs.len); - - for (comp.link_inputs) |link_input| switch (link_input) { - .dso => continue, // handled below - .object, .archive => positionals.appendAssumeCapacity(link_input), - .res => unreachable, - }; - - // This is a set of object files emitted by clang in a single `build-exe` invocation. - // For instance, the implicit `a.o` as compiled by `zig build-exe a.c` will end up - // in this set. - try positionals.ensureUnusedCapacity(comp.c_objects.items.len); - for (comp.c_objects.items) |c_object| { - positionals.appendAssumeCapacity(try link.openObjectInput(io, diags, c_object.status.success.object_path)); - } - - if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); - - if (comp.config.any_sanitize_thread) { - try positionals.append(try link.openObjectInput(io, diags, comp.tsan_lib.?.full_object_path)); - } - - if (comp.config.any_fuzz) { - try positionals.append(try link.openArchiveInput(io, diags, comp.fuzzer_lib.?.full_object_path, false, false)); - } - - if (comp.ubsan_rt_lib) |crt_file| { - const path = crt_file.full_object_path; - self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err| - diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); - } else if (comp.ubsan_rt_obj) |crt_file| { - const path = crt_file.full_object_path; - self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err| - diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); - } - - for (positionals.items) |link_input| { - self.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err}); - } - - var system_libs = std.array_list.Managed(SystemLib).init(gpa); - defer system_libs.deinit(); - - // frameworks - try system_libs.ensureUnusedCapacity(self.frameworks.len); - for (self.frameworks) |info| { - system_libs.appendAssumeCapacity(.{ - .needed = info.needed, - .weak = info.weak, - .path = info.path, - }); - } - - // libc++ dep - if (comp.config.link_libcpp) { - try system_libs.ensureUnusedCapacity(2); - system_libs.appendAssumeCapacity(.{ .path = comp.libcxxabi_static_lib.?.full_object_path }); - system_libs.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path }); - } - - const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or - (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic); - - if (comp.config.link_libc and is_exe_or_dyn_lib) { - if (comp.zigc_static_lib) |zigc| { - const path = zigc.full_object_path; - self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err| - diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); - } - } - - // libc/libSystem dep - self.resolveLibSystem(arena, comp, &system_libs) catch |err| switch (err) { - error.MissingLibSystem => {}, // already reported - else => |e| return diags.fail("failed to resolve libSystem: {s}", .{@errorName(e)}), - }; - - for (comp.link_inputs) |link_input| switch (link_input) { - .object, .archive => continue, - .res => unreachable, - .dso => { - self.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path(), "failed to parse input file: {t}", .{err}); - }, - }; - - for (system_libs.items) |lib| { - switch (Compilation.classifyFileExt(lib.path.sub_path)) { - .shared_library => { - const dso_input = try link.openDsoInput(io, diags, lib.path, lib.needed, lib.weak, lib.reexport); - self.classifyInputFile(dso_input) catch |err| - diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); - }, - .static_library => { - const archive_input = try link.openArchiveInput(io, diags, lib.path, lib.must_link, lib.hidden); - self.classifyInputFile(archive_input) catch |err| - diags.addParseError(lib.path, "failed to parse input file: {s}", .{@errorName(err)}); - }, - else => { - dso: { - const dso_input = link.openDsoInput(io, diags, lib.path, lib.needed, lib.weak, lib.reexport) catch break :dso; - self.classifyInputFile(dso_input) catch break :dso; - continue; - } - ar: { - const archive_input = link.openArchiveInput(io, diags, lib.path, lib.must_link, lib.hidden) catch break :ar; - self.classifyInputFile(archive_input) catch break :ar; - continue; - } - diags.addParseError(lib.path, "unknown file extension", .{}); - }, - } - } - - // Finally, link against compiler_rt. - if (comp.compiler_rt_lib) |crt_file| { - const path = crt_file.full_object_path; - self.classifyInputFile(try link.openArchiveInput(io, diags, path, false, false)) catch |err| - diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); - } else if (comp.compiler_rt_obj) |crt_file| { - const path = crt_file.full_object_path; - self.classifyInputFile(try link.openObjectInput(io, diags, path)) catch |err| - diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)}); + for (self.all_inputs.items) |input| { + self.classifyInputFile(input) catch |err| + diags.addParseError(input.path(), "failed to read input file: {t}", .{err}); } try self.parseInputFiles(); @@ -666,9 +563,8 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { for (comp.link_inputs) |link_input| switch (link_input) { .object, .archive => |obj| try argv.append(try obj.path.toString(arena)), .res => |res| try argv.append(try res.path.toString(arena)), - .dso => |dso| { - try argv.append(try dso.path.toString(arena)); - }, + .dso => |dso| try argv.append(try dso.path.toString(arena)), + .tbd => |tbd| try argv.append(try tbd.path.toString(arena)), }; for (comp.c_objects.items) |c_object| { @@ -694,12 +590,20 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { try argv.append("-platform_version"); try argv.append(@tagName(self.platform.os_tag)); - try argv.append(try std.fmt.allocPrint(arena, "{f}", .{self.platform.version})); + try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}.{d}", .{ + self.platform.version.major, + self.platform.version.minor, + self.platform.version.patch, + })); if (self.sdk_version) |ver| { try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}", .{ ver.major, ver.minor })); } else { - try argv.append(try std.fmt.allocPrint(arena, "{f}", .{self.platform.version})); + try argv.append(try std.fmt.allocPrint(arena, "{d}.{d}.{d}", .{ + self.platform.version.major, + self.platform.version.minor, + self.platform.version.patch, + })); } if (comp.sysroot) |syslibroot| { @@ -754,7 +658,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { } for (comp.link_inputs) |link_input| switch (link_input) { - .dso => continue, // handled below + .dso, .tbd => continue, // handled below .res => unreachable, // windows only .object, .archive => |obj| { if (obj.must_link) try argv.append("-force_load"); // TODO: verify this @@ -788,7 +692,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { for (comp.link_inputs) |link_input| switch (link_input) { .object, .archive => continue, // handled above .res => unreachable, // windows only - .dso => |dso| { + inline .dso, .tbd => |dso| { try argv.ensureUnusedCapacity(2); if (dso.needed) { argv.appendAssumeCapacity("-needed-l"); @@ -801,20 +705,9 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { }, }; - for (self.framework_dirs) |f_dir| { + for (comp.framework_dirs) |f_dir| { try argv.append("-F"); - try argv.append(f_dir); - } - - for (self.frameworks) |framework| { - const name = framework.path.stem(); - const arg = if (framework.needed) - try std.fmt.allocPrint(arena, "-needed_framework {s}", .{name}) - else if (framework.weak) - try std.fmt.allocPrint(arena, "-weak_framework {s}", .{name}) - else - try std.fmt.allocPrint(arena, "-framework {s}", .{name}); - try argv.append(arg); + try argv.append(f_dir.path orelse "."); } if (comp.config.link_libcpp) { @@ -836,46 +729,6 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void { try Compilation.dumpArgv(io, argv.items); } -/// TODO delete this, libsystem must be resolved when setting up the compilation pipeline -pub fn resolveLibSystem( - self: *MachO, - arena: Allocator, - comp: *Compilation, - out_libs: anytype, -) !void { - const io = comp.io; - const diags = &comp.link_diags; - - var test_path = std.array_list.Managed(u8).init(arena); - var checked_paths = std.array_list.Managed([]const u8).init(arena); - - success: { - if (self.sdk_layout) |sdk_layout| switch (sdk_layout) { - .sdk => { - const dir = try fs.path.join(arena, &.{ comp.sysroot.?, "usr", "lib" }); - if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success; - }, - .vendored => { - const dir = try comp.dirs.zig_lib.join(arena, &.{ "libc", "darwin" }); - if (try accessLibPath(arena, io, &test_path, &checked_paths, dir, "System")) break :success; - }, - }; - - for (self.lib_directories) |directory| { - if (try accessLibPath(arena, io, &test_path, &checked_paths, directory.path orelse ".", "System")) break :success; - } - - diags.addMissingLibraryError(checked_paths.items, "unable to find libSystem system library", .{}); - return error.MissingLibSystem; - } - - const libsystem_path = Path.initCwd(try arena.dupe(u8, test_path.items)); - try out_libs.append(.{ - .needed = true, - .path = libsystem_path, - }); -} - pub fn classifyInputFile(self: *MachO, input: link.Input) !void { const tracy = trace(@src()); defer tracy.end(); @@ -888,7 +741,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { log.debug("classifying input file {f}", .{path}); const fh = try self.addFileHandle(file); - var buffer: [Archive.SARMAG]u8 = undefined; + var buffer: [macho.ARMAG.len]u8 = undefined; const fat_arch: ?fat.Arch = try self.parseFatFile(file, path); const offset = if (fat_arch) |fa| fa.offset else 0; @@ -903,7 +756,7 @@ pub fn classifyInputFile(self: *MachO, input: link.Input) !void { return; } if (readArMagic(io, file, offset, &buffer) catch null) |ar_magic| blk: { - if (!mem.eql(u8, ar_magic, Archive.ARMAG)) break :blk; + if (!mem.eql(u8, ar_magic, macho.ARMAG)) break :blk; try self.addArchive(input.archive, fh, fat_arch); return; } @@ -933,10 +786,10 @@ pub fn readMachHeader(io: Io, file: Io.File, offset: usize) !macho.mach_header_6 return hdr; } -pub fn readArMagic(io: Io, file: Io.File, offset: usize, buffer: *[Archive.SARMAG]u8) ![]const u8 { +pub fn readArMagic(io: Io, file: Io.File, offset: usize, buffer: *[macho.ARMAG.len]u8) ![]const u8 { const nread = try file.readPositionalAll(io, buffer, offset); if (nread != buffer.len) return error.InputOutput; - return buffer[0..Archive.SARMAG]; + return buffer[0..macho.ARMAG.len]; } fn addObject(self: *MachO, path: Path, handle_index: File.HandleIndex, offset: u64) !void { @@ -1159,10 +1012,6 @@ fn parseDependentDylibs(self: *MachO) !void { const comp = self.base.comp; const gpa = comp.gpa; const io = comp.io; - const framework_dirs = self.framework_dirs; - - // TODO delete this, directories must instead be resolved by the frontend - const lib_directories = self.lib_directories; var arena_alloc = std.heap.ArenaAllocator.init(gpa); defer arena_alloc.deinit(); @@ -1197,14 +1046,14 @@ fn parseDependentDylibs(self: *MachO) !void { const stem = fs.path.stem(id.name); // Framework - for (framework_dirs) |dir| { + for (comp.framework_dirs) |dir| { test_path.clearRetainingCapacity(); - if (try accessFrameworkPath(arena, io, &test_path, &checked_paths, dir, stem)) break :full_path test_path.items; + if (try accessFrameworkPath(arena, io, &test_path, &checked_paths, dir.path orelse ".", stem)) break :full_path test_path.items; } // Library const lib_name = eatPrefix(stem, "lib") orelse stem; - for (lib_directories) |lib_directory| { + for (self.lib_directories) |lib_directory| { test_path.clearRetainingCapacity(); if (try accessLibPath(arena, io, &test_path, &checked_paths, lib_directory.path orelse ".", lib_name)) break :full_path test_path.items; } @@ -3124,21 +2973,21 @@ pub fn freeNav(self: *MachO, nav: InternPool.Nav.Index) void { return self.getZigObject().?.freeNav(nav); } -pub fn getNavVAddr(self: *MachO, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, reloc_info: link.File.RelocInfo) !u64 { - return self.getZigObject().?.getNavVAddr(self, pt, nav_index, reloc_info); +pub fn navSymbol(self: *MachO, nav: InternPool.Nav.Index) link.Error!link.File.SymbolId { + return self.getZigObject().?.navSymbol(self, nav); } -pub fn lowerUav( +pub fn relocSymAddr(self: *MachO, reloc_info: link.File.RelocInfo) link.Error!void { + return self.getZigObject().?.relocSymAddr(self, reloc_info); +} + +pub fn uavSymbol( self: *MachO, pt: Zcu.PerThread, uav: InternPool.Index, explicit_alignment: InternPool.Alignment, ) !link.File.SymbolId { - return self.getZigObject().?.lowerUav(self, pt, uav, explicit_alignment); -} - -pub fn getUavVAddr(self: *MachO, uav: InternPool.Index, reloc_info: link.File.RelocInfo) !u64 { - return self.getZigObject().?.getUavVAddr(self, uav, reloc_info); + return self.getZigObject().?.uavSymbol(self, pt, uav, explicit_alignment); } pub fn getGlobalSymbol(self: *MachO, name: []const u8, lib_name: ?[]const u8) !u32 { @@ -4231,7 +4080,7 @@ pub const null_sym = macho.nlist_64{ pub const Platform = struct { os_tag: std.Target.Os.Tag, abi: std.Target.Abi, - version: std.SemanticVersion, + version: link.DarwinSdkVersion, /// Using Apple's ld64 as our blueprint, `min_version` as well as `sdk_version` are set to /// the extracted minimum platform version. @@ -4258,7 +4107,7 @@ pub const Platform = struct { => .simulator, else => .none, }, - .version = appleVersionToSemanticVersion(cmd.minos), + .version = @fromBackingInt(cmd.minos), }; }, .VERSION_MIN_IPHONEOS, @@ -4277,7 +4126,7 @@ pub const Platform = struct { else => unreachable, }, .abi = .none, - .version = appleVersionToSemanticVersion(cmd.version), + .version = @fromBackingInt(cmd.version), }; }, else => unreachable, @@ -4285,17 +4134,18 @@ pub const Platform = struct { } pub fn fromTarget(target: *const std.Target) Platform { + const semver = target.os.version_range.semver.min; return .{ .os_tag = target.os.tag, .abi = target.abi, - .version = target.os.version_range.semver.min, + .version = .{ + .major = @intCast(semver.major), + .minor = @intCast(semver.minor), + .patch = @intCast(semver.patch), + }, }; } - pub fn toAppleVersion(plat: Platform) u32 { - return semanticVersionToAppleVersion(plat.version); - } - pub fn toApplePlatform(plat: Platform) macho.PLATFORM { return switch (plat.os_tag) { .driverkit => .DRIVERKIT, @@ -4312,7 +4162,7 @@ pub const Platform = struct { pub fn isBuildVersionCompatible(plat: Platform) bool { inline for (supported_platforms) |sup_plat| { if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) { - return sup_plat[2] <= plat.toAppleVersion(); + return sup_plat[2] <= @backingInt(plat.version); } } return false; @@ -4321,7 +4171,7 @@ pub const Platform = struct { pub fn isVersionMinCompatible(plat: Platform) bool { inline for (supported_platforms) |sup_plat| { if (sup_plat[0] == plat.os_tag and sup_plat[1] == plat.abi) { - return sup_plat[3] <= plat.toAppleVersion(); + return sup_plat[3] <= @backingInt(plat.version); } } return false; @@ -4380,86 +4230,8 @@ const supported_platforms = [_]SupportedPlatforms{ }; // zig fmt: on -pub inline fn semanticVersionToAppleVersion(version: std.SemanticVersion) u32 { - const major = version.major; - const minor = version.minor; - const patch = version.patch; - return (@as(u32, @intCast(major)) << 16) | (@as(u32, @intCast(minor)) << 8) | @as(u32, @intCast(patch)); -} - -pub inline fn appleVersionToSemanticVersion(version: u32) std.SemanticVersion { - return .{ - .major = @as(u16, @truncate(version >> 16)), - .minor = @as(u8, @truncate(version >> 8)), - .patch = @as(u8, @truncate(version)), - }; -} - -fn inferSdkVersion(comp: *Compilation, sdk_layout: SdkLayout) ?std.SemanticVersion { - const gpa = comp.gpa; - - var arena_allocator = std.heap.ArenaAllocator.init(gpa); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const io = comp.io; - - const sdk_dir = switch (sdk_layout) { - .sdk => comp.sysroot.?, - .vendored => fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "libc", "darwin" }) catch return null, - }; - if (readSdkVersionFromSettings(arena, io, sdk_dir)) |ver| { - return parseSdkVersion(ver); - } else |_| { - // Read from settings should always succeed when vendored. - // TODO: convert to fatal linker error - if (sdk_layout == .vendored) @panic("zig installation bug: unable to parse SDK version"); - } - - // infer from pathname - const stem = fs.path.stem(sdk_dir); - const start = for (stem, 0..) |c, i| { - if (std.ascii.isDigit(c)) break i; - } else stem.len; - const end = for (stem[start..], start..) |c, i| { - if (std.ascii.isDigit(c) or c == '.') continue; - break i; - } else stem.len; - return parseSdkVersion(stem[start..end]); -} - -// Official Apple SDKs ship with a `SDKSettings.json` located at the top of SDK fs layout. -// Use property `MinimalDisplayName` to determine version. -// The file/property is also available with vendored libc. -fn readSdkVersionFromSettings(arena: Allocator, io: Io, dir: []const u8) ![]const u8 { - const sdk_path = try fs.path.join(arena, &.{ dir, "SDKSettings.json" }); - const contents = try Io.Dir.cwd().readFileAlloc(io, sdk_path, arena, .limited(std.math.maxInt(u16))); - const parsed = try std.json.parseFromSlice(std.json.Value, arena, contents, .{}); - if (parsed.value.object.get("MinimalDisplayName")) |ver| return ver.string; - return error.SdkVersionFailure; -} - -// Versions reported by Apple aren't exactly semantically valid as they usually omit -// the patch component, so we parse SDK value by hand. -fn parseSdkVersion(raw: []const u8) ?std.SemanticVersion { - var parsed: std.SemanticVersion = .{ - .major = 0, - .minor = 0, - .patch = 0, - }; - - const parseNext = struct { - fn parseNext(it: anytype) ?u16 { - const nn = it.next() orelse return null; - return std.fmt.parseInt(u16, nn, 10) catch null; - } - }.parseNext; - - var it = std.mem.splitAny(u8, raw, "."); - parsed.major = parseNext(&it) orelse return null; - parsed.minor = parseNext(&it) orelse return null; - parsed.patch = parseNext(&it) orelse 0; - return parsed; +pub fn setDarwinSdkVersion(self: *MachO, version: link.DarwinSdkVersion) link.Error!void { + self.sdk_version = version; } /// When allocating, the ideal_capacity is calculated by @@ -4497,7 +4269,7 @@ const SystemLib = struct { .must_link = obj.must_link, .hidden = obj.hidden, }, - .dso => |dso| .{ + inline .dso, .tbd => |dso| .{ .path = dso.path, .needed = dso.needed, .weak = dso.weak, @@ -4507,8 +4279,6 @@ const SystemLib = struct { } }; -pub const SdkLayout = std.zig.LibCDirs.DarwinSdkLayout; - const UndefinedTreatment = enum { @"error", warn, diff --git a/src/link/MachO/Archive.zig b/src/link/MachO/Archive.zig index 91860f45986b106bed46fbf37e9ac35133ff5b49..4c2ad4187a01830b5d93b7cc2ff8ddc68de3c6e3 100644 --- a/src/link/MachO/Archive.zig +++ b/src/link/MachO/Archive.zig @@ -17,7 +17,7 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File const offset = if (fat_arch) |ar| ar.offset else 0; const end_pos = if (fat_arch) |ar| offset + ar.size else (try handle.stat(io)).size; - var pos: usize = offset + SARMAG; + var pos: usize = offset + macho.ARMAG.len; while (true) { if (pos >= end_pos) break; if (!mem.isAligned(pos, 2)) pos += 1; @@ -30,9 +30,9 @@ pub fn unpack(self: *Archive, macho_file: *MachO, path: Path, handle_index: File const hdr = @as(*align(1) const ar_hdr, @ptrCast(&hdr_buffer)).*; pos += @sizeOf(ar_hdr); - if (!mem.eql(u8, &hdr.ar_fmag, ARFMAG)) { + if (!mem.eql(u8, &hdr.ar_fmag, macho.ARFMAG)) { return diags.failParse(path, "invalid header delimiter: expected '{f}', found '{f}'", .{ - std.ascii.hexEscape(ARFMAG, .lower), std.ascii.hexEscape(&hdr.ar_fmag, .lower), + std.ascii.hexEscape(macho.ARFMAG, .lower), std.ascii.hexEscape(&hdr.ar_fmag, .lower), }); } @@ -116,17 +116,6 @@ pub fn writeHeader( } } -// Archive files start with the ARMAG identifying string. Then follows a -// `struct ar_hdr', and as many bytes of member file data as its `ar_size' -// member indicates, for each member file. -/// String that begins an archive file. -pub const ARMAG: *const [SARMAG:0]u8 = "!\n"; -/// Size of that string. -pub const SARMAG: u4 = 8; - -/// String in ar_fmag at the end of each header. -const ARFMAG: *const [2:0]u8 = "`\n"; - pub const SYMDEF = "__.SYMDEF"; pub const SYMDEF64 = "__.SYMDEF_64"; pub const SYMDEF_SORTED = "__.SYMDEF SORTED"; @@ -146,7 +135,7 @@ pub const ar_hdr = extern struct { /// File size, in ASCII decimal. ar_size: [10]u8 = "0\x20\x20\x20\x20\x20\x20\x20\x20\x20".*, /// Always contains ARFMAG. - ar_fmag: [2]u8 = ARFMAG.*, + ar_fmag: [2]u8 = macho.ARFMAG.*, fn date(self: ar_hdr) !u64 { const value = mem.trimEnd(u8, &self.ar_date, &[_]u8{@as(u8, 0x20)}); diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index 6d7f9d19d3a63a102ec137c4a29f896e03b83572..a44846211834c8b96a52324ce218c7ea479de48f 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -611,60 +611,43 @@ pub fn flush(self: *ZigObject, macho_file: *MachO, tid: Zcu.PerThread.Id) link.E assert(!self.debug_strtab_dirty); } -pub fn getNavVAddr( - self: *ZigObject, +pub fn navSymbol( + zo: *ZigObject, macho_file: *MachO, - pt: Zcu.PerThread, nav_index: InternPool.Nav.Index, - reloc_info: link.File.RelocInfo, -) !u64 { - const zcu = pt.zcu; +) link.Error!link.File.SymbolId { + const zcu = macho_file.base.comp.zcu.?; const ip = &zcu.intern_pool; const nav = ip.getNav(nav_index); - log.debug("getNavVAddr {f}({d})", .{ nav.fqn.fmt(ip), nav_index }); - const sym_index = if (nav.getExtern(ip)) |@"extern"| try self.getGlobalSymbol( - macho_file, - nav.name.toSlice(ip), - @"extern".lib_name.toSlice(ip), - ) else try self.getOrCreateMetadataForNav(macho_file, nav_index); - const sym = self.symbols.items[sym_index]; - const vaddr = sym.getAddress(.{}, macho_file); - switch (reloc_info.parent) { - .none => unreachable, - .atom_index => |atom_index| { - const parent_atom = self.symbols.items[@backingInt(atom_index)].getAtom(macho_file).?; - try parent_atom.addReloc(macho_file, .{ - .tag = .@"extern", - .offset = @intCast(reloc_info.offset), - .target = sym_index, - .addend = reloc_info.addend, - .type = .unsigned, - .meta = .{ - .pcrel = false, - .has_subtractor = false, - .length = 3, - .symbolnum = @intCast(sym.nlist_idx), - }, - }); - }, - .debug_output => |debug_output| try debug_output.dwarf.infoExternalReloc(.{ - .source_off = @intCast(reloc_info.offset), - .target_sym = @fromBackingInt(@intCast(sym_index)), - .target_off = reloc_info.addend, - }), + if (nav.getExtern(ip)) |@"extern"| { + const sym_index = try zo.getGlobalSymbol( + macho_file, + nav.name.toSlice(ip), + @"extern".lib_name.toSlice(ip), + ); + if (@"extern".linkage == .weak) { + zo.symbols.items[sym_index].flags.weak = true; + } + if (nav.resolved.?.@"threadlocal") { + zo.symbols.items[sym_index].flags.tlv = true; + } + return @fromBackingInt(sym_index); + } else { + const sym_index = try zo.getOrCreateMetadataForNav(macho_file, nav_index); + if (nav.resolved.?.@"threadlocal") { + zo.symbols.items[sym_index].flags.tlv = true; + } + return @fromBackingInt(sym_index); } - return vaddr; } -pub fn getUavVAddr( +pub fn relocSymAddr( self: *ZigObject, macho_file: *MachO, - uav: InternPool.Index, reloc_info: link.File.RelocInfo, -) !u64 { - const sym_index = self.uavs.get(uav).?.symbol_index; +) !void { + const sym_index = @backingInt(reloc_info.target); const sym = self.symbols.items[sym_index]; - const vaddr = sym.getAddress(.{}, macho_file); switch (reloc_info.parent) { .none => unreachable, .atom_index => |atom_index| { @@ -689,10 +672,9 @@ pub fn getUavVAddr( .target_off = reloc_info.addend, }), } - return vaddr; } -pub fn lowerUav( +pub fn uavSymbol( self: *ZigObject, macho_file: *MachO, pt: Zcu.PerThread, @@ -1281,7 +1263,7 @@ pub fn updateExports( break :blk self.navs.getPtr(nav).?; }, .uav => |uav| self.uavs.getPtr(uav) orelse blk: { - _ = try self.lowerUav(macho_file, pt, uav, .none); + _ = try self.uavSymbol(macho_file, pt, uav, .none); break :blk self.uavs.getPtr(uav).?; }, }; diff --git a/src/link/MachO/load_commands.zig b/src/link/MachO/load_commands.zig index 62bbc751cb58e82aee019f07065eabf1b8cf8917..f6b97406849f031a235a51f31c64cb8e3b65b545 100644 --- a/src/link/MachO/load_commands.zig +++ b/src/link/MachO/load_commands.zig @@ -9,6 +9,7 @@ const Allocator = std.mem.Allocator; const DebugSymbols = @import("DebugSymbols.zig"); const Dylib = @import("Dylib.zig"); const MachO = @import("../MachO.zig"); +const link = @import("../../link.zig"); pub const default_dyld_path: [*:0]const u8 = "/usr/lib/dyld"; @@ -296,7 +297,7 @@ pub fn writeRpathLC(rpath: []const u8, writer: *Writer) !void { } } -pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: *Writer) !void { +pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?link.DarwinSdkVersion, writer: *Writer) !void { const cmd: macho.LC = switch (platform.os_tag) { .macos => .VERSION_MIN_MACOSX, .ios, .maccatalyst => .VERSION_MIN_IPHONEOS, @@ -306,24 +307,18 @@ pub fn writeVersionMinLC(platform: MachO.Platform, sdk_version: ?std.SemanticVer }; try writer.writeAll(mem.asBytes(&macho.version_min_command{ .cmd = cmd, - .version = platform.toAppleVersion(), - .sdk = if (sdk_version) |ver| - MachO.semanticVersionToAppleVersion(ver) - else - platform.toAppleVersion(), + .version = @backingInt(platform.version), + .sdk = @backingInt(sdk_version orelse platform.version), })); } -pub fn writeBuildVersionLC(platform: MachO.Platform, sdk_version: ?std.SemanticVersion, writer: *Writer) !void { +pub fn writeBuildVersionLC(platform: MachO.Platform, sdk_version: ?link.DarwinSdkVersion, writer: *Writer) !void { const cmdsize = @sizeOf(macho.build_version_command) + @sizeOf(macho.build_tool_version); try writer.writeStruct(@as(macho.build_version_command, .{ .cmdsize = cmdsize, .platform = platform.toApplePlatform(), - .minos = platform.toAppleVersion(), - .sdk = if (sdk_version) |ver| - MachO.semanticVersionToAppleVersion(ver) - else - platform.toAppleVersion(), + .minos = @backingInt(platform.version), + .sdk = @backingInt(sdk_version orelse platform.version), .ntools = 1, }), .little); try writer.writeAll(mem.asBytes(&macho.build_tool_version{ diff --git a/src/link/MachO/relocatable.zig b/src/link/MachO/relocatable.zig index 38420414382928237d93b16b5c8648e4c91fb6f5..4f3cc6c66cebd98a9d79dcaf5c5818f39cce00ce 100644 --- a/src/link/MachO/relocatable.zig +++ b/src/link/MachO/relocatable.zig @@ -1,25 +1,25 @@ -pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.Error!void { - const gpa = comp.gpa; +pub fn flushObject(macho_file: *MachO, comp: *Compilation) link.Error!void { const io = comp.io; const diags = &comp.link_diags; - // TODO: "positional arguments" is a CLI concept, not a linker concept. Delete this unnecessary array list. - var positionals = std.array_list.Managed(link.Input).init(gpa); - defer positionals.deinit(); - try positionals.ensureUnusedCapacity(comp.link_inputs.len); - positionals.appendSliceAssumeCapacity(comp.link_inputs); - - for (comp.c_objects.items) |c_object| { - try positionals.append(try link.openObjectInput(io, diags, c_object.status.success.object_path)); - } - - if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); - - if (macho_file.getZigObject() == null and positionals.items.len == 1) { + one_input: { + if (macho_file.getZigObject() != null) { + break :one_input; + } + var only_input: ?link.Input = null; + for (macho_file.all_inputs.items) |input| switch (input) { + .res => unreachable, + .dso, .tbd => {}, + .object, .archive => if (only_input == null) { + only_input = input; + } else { + break :one_input; + }, + }; // Instead of invoking a full-blown `-r` mode on the input which sadly will strip all // debug info segments/sections (this is apparently by design by Apple), we copy // the *only* input file over. - const path = positionals.items[0].path(); + const path = (only_input orelse break :one_input).path(); const in_file = path.root_dir.handle.openFile(io, path.sub_path, .{}) catch |err| return diags.fail("failed to open {f}: {s}", .{ path, @errorName(err) }); const stat = in_file.stat(io) catch |err| @@ -29,10 +29,12 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat return; } - for (positionals.items) |link_input| { - macho_file.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err}); - } + for (macho_file.all_inputs.items) |input| switch (input) { + .res => unreachable, + .dso, .tbd => {}, + .object, .archive => macho_file.classifyInputFile(input) catch |err| + diags.addParseError(input.path(), "failed to read input file: {t}", .{err}), + }; if (diags.hasErrors()) return error.AlreadyReported; @@ -75,35 +77,16 @@ pub fn flushObject(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Pat try writeHeader(macho_file, ncmds, sizeofcmds); } -pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ?Path) link.Error!void { +pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation) link.Error!void { const gpa = comp.gpa; - const io = comp.io; const diags = &macho_file.base.comp.link_diags; - var positionals = std.array_list.Managed(link.Input).init(gpa); - defer positionals.deinit(); - - try positionals.ensureUnusedCapacity(comp.link_inputs.len); - positionals.appendSliceAssumeCapacity(comp.link_inputs); - - for (comp.c_objects.items) |c_object| { - try positionals.append(try link.openObjectInput(io, diags, c_object.status.success.object_path)); - } - - if (module_obj_path) |path| try positionals.append(try link.openObjectInput(io, diags, path)); - - if (comp.compiler_rt_strat == .obj) { - try positionals.append(try link.openObjectInput(io, diags, comp.compiler_rt_obj.?.full_object_path)); - } - - if (comp.ubsan_rt_strat == .obj) { - try positionals.append(try link.openObjectInput(io, diags, comp.ubsan_rt_obj.?.full_object_path)); - } - - for (positionals.items) |link_input| { - macho_file.classifyInputFile(link_input) catch |err| - diags.addParseError(link_input.path(), "failed to read input file: {t}", .{err}); - } + for (macho_file.all_inputs.items) |input| switch (input) { + .res => unreachable, + .dso, .tbd => {}, + .object, .archive => macho_file.classifyInputFile(input) catch |err| + diags.addParseError(input.path(), "failed to read input file: {t}", .{err}), + }; if (diags.hasErrors()) return error.AlreadyReported; @@ -168,7 +151,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? // Update file offsets of contributing objects const total_size: usize = blk: { - var pos: usize = Archive.SARMAG; + var pos: usize = macho.ARMAG.len; pos += @sizeOf(Archive.ar_hdr); pos += Archive.SYMDEF.len + 1; pos = mem.alignForward(usize, pos, 8); @@ -211,7 +194,7 @@ pub fn flushStaticLib(macho_file: *MachO, comp: *Compilation, module_obj_path: ? var writer: Writer = .fixed(buffer); // Write magic - writer.writeAll(Archive.ARMAG) catch unreachable; + writer.writeAll(macho.ARMAG) catch unreachable; // Write symtab ar_symtab.write(format, macho_file, &writer) catch |err| diff --git a/src/link/MachO2.zig b/src/link/MachO2.zig new file mode 100644 index 0000000000000000000000000000000000000000..f5583cb7915a24b4a658636d35a77f76e7923cfb --- /dev/null +++ b/src/link/MachO2.zig @@ -0,0 +1,1357 @@ +const MachO = @This(); + +base: link.File, +options: link.File.OpenOptions, + +const_prog_node: std.Progress.Node, +synth_prog_node: std.Progress.Node, +input_prog_node: std.Progress.Node, + +mf: MappedFile, +nodes: std.MultiArrayList(Node), + +node: struct { + macho: MappedFile.Node.Index, + header: MappedFile.Node.Index, + symtab: MappedFile.Node.Index, + strtab: MappedFile.Node.Index, +}, + +lc: struct { + offsets: std.ArrayList(u32), + pagezero: LoadCommand, + symtab: LoadCommand, + entry_point: ?LoadCommand, +}, +/// Does not contain an entry for the `__PAGEZERO` segment. +segments: std.MultiArrayList(struct { + lc: LoadCommand, + node: MappedFile.Node.Index, +}), +sections: std.MultiArrayList(struct { + lc: LoadCommand, + node: MappedFile.Node.Index, + first_symbol: Symbol.Index.Optional, + index: u8, +}), +/// Accessed with `Segment.NameAdapter` using `[]const u8` keys. +segments_by_name: std.array_hash_map.Custom(void, void, void, true), +/// Accessed with `Section.NameAdapter` using `Section.NameAdapter.SegmentAndSection`. +sections_by_name: std.array_hash_map.Custom(void, void, void, true), + +/// Accessed with `String.Adapter` using `[]const u8` keys. +strtab: std.array_hash_map.Custom(String, void, void, true), + +symtab: std.ArrayList(Symbol), + +sections_changed_index: std.array_hash_map.Auto(Section, void), + +const Error = link.Error || error{MappedFileIo}; + +/// Stable reference to a particular load command (or `std.macho.section_64`, despite those not +/// technically being load commands). Underlying value is an index into `MachO.lc.offsets`. +const LoadCommand = enum(u32) { + _, + fn offset(lc: LoadCommand, macho: *const MachO) u32 { + return macho.lc.offsets.items[@backingInt(lc)]; + } + fn ptrConst(lc: LoadCommand, macho: *const MachO, comptime Cmd: type) *align(8) const Cmd { + return @ptrCast(@alignCast( + macho.node.header.sliceConst(&macho.mf)[lc.offset(macho)..][0..@sizeOf(Cmd)], + )); + } + fn ptr(lc: LoadCommand, macho: *MachO, comptime Cmd: type) *align(8) Cmd { + return @ptrCast(@alignCast( + macho.node.header.slice(&macho.mf)[lc.offset(macho)..][0..@sizeOf(Cmd)], + )); + } +}; + +/// Stable reference to a segment defined by a `std.macho.segment_command_64` load command. +/// +/// This type is backed by `u8` instead of a larger type because Mach-O itself uses 8-bit indices to +/// refer to sections, so the limitation exists regardless. +const Segment = enum(u8) { + _, + + fn lc(seg: Segment, macho: *const MachO) LoadCommand { + return macho.segments.items(.lc)[@backingInt(seg)]; + } + fn lcPtrConst(seg: Segment, macho: *const MachO) *const std.macho.segment_command_64 { + return seg.lc(macho).ptrConst(macho, std.macho.segment_command_64); + } + fn lcPtr(seg: Segment, macho: *MachO) *std.macho.segment_command_64 { + return seg.lc(macho).ptr(macho, std.macho.segment_command_64); + } + + fn name(seg: Segment, macho: *const MachO) []const u8 { + return std.mem.sliceTo(&seg.lcPtrConst(macho).segname, 0); + } + + fn vaddr(seg: Segment, macho: *const MachO) u64 { + return macho.targetLoad(&seg.lcPtrConst(macho).vmaddr); + } + + fn node(seg: Segment, macho: *const MachO) MappedFile.Node.Index { + return macho.segments.items(.node)[@backingInt(seg)]; + } + + const NameAdapter = struct { + macho: *const MachO, + pub fn eql(ctx: NameAdapter, a_seg_name: []const u8, _: void, b_seg_raw: usize) bool { + const b_seg: Segment = @fromBackingInt(@intCast(b_seg_raw)); + return std.mem.eql(u8, a_seg_name, b_seg.name(ctx.macho)); + } + pub fn hash(ctx: NameAdapter, a_seg_name: []const u8) u32 { + _ = ctx; + return std.array_hash_map.hashString(a_seg_name); + } + }; +}; + +/// Stable reference to a section defined by a `std.macho.section_64` in the load commands. +/// +/// This type is backed by `u8` instead of a larger type because Mach-O itself uses 8-bit indices to +/// refer to sections, so the limitation exists regardless. +/// +/// However, the backing integer of a `Section` is *not* the same as the section index, because +/// section indices can change. For the index of a section, see `Section.index`. +const Section = enum(u8) { + _, + + fn lc(sect: Section, macho: *const MachO) LoadCommand { + return macho.sections.items(.lc)[@backingInt(sect)]; + } + fn lcPtrConst(sect: Section, macho: *const MachO) *const std.macho.section_64 { + return sect.lc(macho).ptrConst(macho, std.macho.section_64); + } + fn lcPtr(sect: Section, macho: *MachO) *std.macho.section_64 { + return sect.lc(macho).ptr(macho, std.macho.section_64); + } + + fn name(sect: Section, macho: *const MachO) []const u8 { + return std.mem.sliceTo(§.lcPtrConst(macho).sectname, 0); + } + + fn segmentName(sect: Section, macho: *const MachO) []const u8 { + return std.mem.sliceTo(§.lcPtrConst(macho).segname, 0); + } + + fn node(sect: Section, macho: *const MachO) MappedFile.Node.Index { + return macho.sections.items(.node)[@backingInt(sect)]; + } + + /// Returns this section's index in the Mach-O file. Section indices are implicitly assigned + /// based on the order of load commands, so this value may change in response to segments or + /// sections being added. When this value changes for a given `Section`, it is added to + /// `MachO.sections_changed_index`, so that references to the section index can be updated. + fn index(sect: Section, macho: *const MachO) u8 { + return macho.sections.items(.index)[@backingInt(sect)]; + } + + /// Returns the head of a linked list of symbols defined relative to this section. + fn firstSymbol(sect: Section, macho: *MachO) *Symbol.Index.Optional { + return &macho.sections.items(.first_symbol)[@backingInt(sect)]; + } + + const NameAdapter = struct { + macho: *const MachO, + const SegmentAndSection = struct { + segment: []const u8, + section: []const u8, + }; + pub fn eql(ctx: NameAdapter, a_name: SegmentAndSection, _: void, b_sect_raw: usize) bool { + const b_sect: Section = @fromBackingInt(@intCast(b_sect_raw)); + return std.mem.eql(u8, a_name.segment, b_sect.segmentName(ctx.macho)) and + std.mem.eql(u8, a_name.section, b_sect.name(ctx.macho)); + } + pub fn hash(ctx: NameAdapter, a_name: SegmentAndSection) u32 { + _ = ctx; + var h: std.hash.Wyhash = .init(a_name.section.len); + h.update(a_name.section); + h.update(a_name.segment); + return @truncate(h.final()); + } + }; +}; + +const Symbol = struct { + next_in_section: Symbol.Index.Optional, + + const Index = enum(u32) { + _, + fn ptr(i: Index, macho: *MachO) *Symbol { + return &macho.symtab.items[@backingInt(i)]; + } + fn nlist(i: Index, macho: *MachO) *std.macho.nlist_64 { + return &macho.nlistSlice()[@backingInt(i)]; + } + const Optional = enum(u32) { + none = std.math.maxInt(u32), + _, + fn unwrap(o: Optional) ?Index { + return switch (o) { + .none => null, + _ => @fromBackingInt(@backingInt(o)), + }; + } + fn wrap(i: Index) Optional { + return @bitCast(i); + } + }; + }; +}; + +const Node = union(enum) { + macho, + /// Contains the `mach_header_64` structure and all load commands. Inside of the `.segment` node for the `__TEXT` segment. + macho_header, + + symtab, + strtab, + + segment: Segment, + section: Section, +}; +fn getNode(macho: *const MachO, ni: MappedFile.Node.Index) Node { + return macho.nodes.get(@backingInt(ni)); +} + +pub fn open( + arena: Allocator, + comp: *Compilation, + path: std.Build.Cache.Path, + options: link.File.OpenOptions, +) !*MachO { + return create(arena, comp, path, options); +} +pub fn createEmpty( + arena: Allocator, + comp: *Compilation, + path: std.Build.Cache.Path, + options: link.File.OpenOptions, +) !*MachO { + return create(arena, comp, path, options); +} +fn create( + arena: Allocator, + comp: *Compilation, + path: std.Build.Cache.Path, + options: link.File.OpenOptions, +) !*MachO { + const io = comp.io; + + const macho = try arena.create(MachO); + const file = try path.root_dir.handle.createFile(io, path.sub_path, .{ + .read = true, + .permissions = link.File.determinePermissions(comp.config.output_mode, comp.config.link_mode), + }); + + macho.* = .{ + .base = .{ + .tag = .macho2, + + .comp = comp, + .emit = path, + + .file = file, + .gc_sections = false, + .print_gc_sections = false, + .build_id = .none, + .allow_shlib_undefined = false, + .stack_size = 0, + }, + .options = options, + + .const_prog_node = .none, + .synth_prog_node = .none, + .input_prog_node = .none, + + .mf = try .init(file, comp.gpa, io), + .nodes = .empty, + + .node = undefined, + .lc = .{ + .offsets = .empty, + .pagezero = undefined, + .symtab = undefined, + .entry_point = null, + }, + .segments = .empty, + .sections = .empty, + .segments_by_name = .empty, + .sections_by_name = .empty, + + .strtab = .empty, + .symtab = .empty, + + .sections_changed_index = .empty, + }; + errdefer macho.deinit(); + + try macho.initInner(); + + return macho; +} +fn initInner(macho: *MachO) !void { + const comp = macho.base.comp; + const gpa = comp.gpa; + + macho.node.macho = .root; + try macho.nodes.append(gpa, .macho); + + const arch: enum { aarch64, x86_64 } = switch (comp.getTarget().cpu.arch) { + .aarch64 => .aarch64, + .x86_64 => .x86_64, + else => |arch| std.debug.panic("TODO: unsupported target arch {t}", .{arch}), + }; + + const mach_header_64 = std.macho.mach_header_64; + + // The `__TEXT` segment is special because it contains the mach header and load commands, so we + // must create the text segment node before we can actually call `addSegment`. + const text_seg_node = try macho.node.macho.addOnlyHeaderChild(gpa, &macho.mf, .{ + .alignment = macho.mf.flags.block_size.max(macho.targetPageAlign()), + .moved = true, // required by `addSegment` + .bubbles_moved = false, + }); + try macho.nodes.append(gpa, .{ .segment = undefined }); // populated by `addSegment` + + macho.node.header = try text_seg_node.addOnlyHeaderChild(gpa, &macho.mf, .{ + .size = @sizeOf(mach_header_64), + .alignment = .@"8", + }); + try macho.nodes.append(gpa, .macho_header); + + const mach_header: *mach_header_64 = @ptrCast(@alignCast( + macho.node.header.slice(&macho.mf)[0..@sizeOf(mach_header_64)], + )); + mach_header.* = .{ + .cputype = switch (arch) { + .aarch64 => std.macho.CPU_TYPE_ARM64, + .x86_64 => std.macho.CPU_TYPE_X86_64, + }, + .cpusubtype = switch (arch) { + .aarch64 => std.macho.CPU_SUBTYPE_ARM_ALL, + .x86_64 => std.macho.CPU_SUBTYPE_X86_64_ALL, + }, + .filetype = switch (comp.config.output_mode) { + .Exe => std.macho.MH_EXECUTE, + .Lib => switch (comp.config.link_mode) { + .static => std.macho.MH_OBJECT, + .dynamic => std.macho.MH_DYLIB, + }, + .Obj => std.macho.MH_OBJECT, + }, + .ncmds = 0, + .sizeofcmds = 0, + .flags = std.macho.MH_DYLDLINK | std.macho.MH_PIE, + }; + if (macho.targetEndian() != std.lang.Endian.native) { + std.mem.byteSwapAllFields(mach_header_64, mach_header); + } + + // The "__LINKEDIT" segment is special in that it needs to be at the end of the Mach-O file, so + // like "__TEXT", we'll construct its node here instead of in `addSegment`. + const linkedit_seg_node = try macho.node.macho.addFooterChildBefore(gpa, &macho.mf, .none, .{ + .alignment = macho.mf.flags.block_size.max(macho.targetPageAlign()), + .moved = true, // required by `addSegment` + .bubbles_moved = false, + }); + try macho.nodes.append(gpa, .{ .segment = undefined }); // populated by `addSegment` + + // The "__PAGEZERO" segment must come before any other segment load commands. It is inaccessible + // and occupies 4 GiB (apparently to catch invalid 32-bit pointer accesses). + macho.lc.pagezero = try macho.appendSimpleLoadCommand(std.macho.segment_command_64, .{ + .cmdsize = @sizeOf(std.macho.segment_command_64), + .segname = "__PAGEZERO\x00\x00\x00\x00\x00\x00".*, + .vmaddr = 0, + .vmsize = 0x1_0000_0000, + .fileoff = 0, + .filesize = 0, + .maxprot = .{}, + .initprot = .{}, + .nsects = 0, + .flags = 0, + }); + const text_seg = try macho.addSegment(.{ + .name = "__TEXT", + .prot = .{ .READ = true, .EXEC = true }, + .existing_node = text_seg_node, + }); + const data_seg = try macho.addSegment(.{ + .name = "__DATA", + .prot = .{ .READ = true, .WRITE = true }, + }); + const linkedit_seg = try macho.addSegment(.{ + .name = "__LINKEDIT", + .prot = .{ .READ = true }, + .existing_node = linkedit_seg_node, + }); + + _ = try macho.addSection(.{ + .segment = text_seg, + .name = "__text", + .@"align" = .@"4", + .flags = .{ .some_instructions = true }, + }); + _ = try macho.addSection(.{ + .segment = text_seg, + .name = "__const", + .@"align" = .@"2", + .flags = .{}, + }); + _ = try macho.addSection(.{ + .segment = data_seg, + .name = "__data", + .@"align" = .@"2", + .flags = .{}, + }); + + macho.node.symtab = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{ + .alignment = macho.mf.flags.block_size.max(.of(std.macho.nlist_64)), + .moved = true, // so that `symoff` in the symtab load command will be set + }); + try macho.nodes.append(gpa, .symtab); + + macho.node.strtab = try linkedit_seg.node(macho).addFloatingChild(gpa, &macho.mf, .{ + .alignment = macho.mf.flags.block_size, + .moved = true, // so that `stroff` in the symtab load command will be set + }); + try macho.nodes.append(gpa, .strtab); + + try macho.node.strtab.ensureMinimumSize(gpa, &macho.mf, 1); + macho.node.strtab.slice(&macho.mf)[0] = 0; + + _ = try macho.appendSimpleLoadCommand(std.macho.dyld_info_command, .{}); + + macho.lc.symtab = try macho.appendSimpleLoadCommand(std.macho.symtab_command, .{ + .symoff = 0, + .nsyms = 0, + .stroff = 0, + .strsize = 1, // empty string (null terminator) + }); + + const want_dyld_path: bool = switch (comp.config.output_mode) { + .Obj => false, + .Lib => switch (comp.config.link_mode) { + .static => false, + .dynamic => comp.root_mod.resolved_target.is_explicit_dynamic_linker, + }, + .Exe => true, + }; + if (want_dyld_path) { + if (comp.getTarget().dynamic_linker.get()) |dyld_path| { + const cmd = try macho.appendLoadCommand(std.macho.dylinker_command, .{ + .cmd = .LOAD_DYLINKER, + .cmdsize = std.mem.alignForward(u32, @intCast( + @sizeOf(std.macho.dylinker_command) + dyld_path.len + 1, + ), 8), + .name = @sizeOf(std.macho.dylinker_command), + }); + @memcpy(cmd.trailing[0..dyld_path.len], dyld_path); + @memset(cmd.trailing[dyld_path.len..], 0); + } + } + + switch (comp.config.output_mode) { + .Obj, .Lib => {}, + .Exe => macho.lc.entry_point = try macho.appendSimpleLoadCommand(std.macho.entry_point_command, .{ + .entryoff = 0, + .stacksize = macho.options.stack_size orelse 0, + }), + } + + _ = try macho.appendSimpleLoadCommand(std.macho.uuid_command, .{ + .uuid = @splat(0), + }); + + try macho.addSimpleSymbol("some_function", 0x1234, "__TEXT", "__text"); + try macho.addSimpleSymbol("my_cool_data", 0x5678, "__DATA", "__data"); + + try macho.updateSegmentLoadAddresses(); +} +/// TODO: this function doesn't actually do anything particularly useful, it's just a placeholder +/// for actual symbol creation logic so that we can test adding stuff to the symtab. +fn addSimpleSymbol( + macho: *MachO, + name: []const u8, + value: u64, + segment_name: []const u8, + section_name: []const u8, +) Error!void { + const gpa = macho.base.comp.gpa; + try macho.symtab.ensureUnusedCapacity(gpa, 1); + try macho.node.symtab.ensureMinimumSize(gpa, &macho.mf, (macho.symtab.items.len + 1) * @sizeOf(std.macho.nlist_64)); + + const strx = try macho.string(name); + const section = macho.sectionByName(segment_name, section_name).?; + + const symtab_ptr = macho.lc.symtab.ptr(macho, std.macho.symtab_command); + assert(macho.targetLoad(&symtab_ptr.nsyms) == macho.symtab.items.len); + macho.targetStore(&symtab_ptr.nsyms, @intCast(macho.symtab.items.len + 1)); + + const sym_index: Symbol.Index = @fromBackingInt(@intCast(macho.symtab.items.len)); + macho.symtab.appendAssumeCapacity(.{ + .next_in_section = section.firstSymbol(macho).*, + }); + section.firstSymbol(macho).* = .wrap(sym_index); + + sym_index.nlist(macho).* = .{ + .n_strx = @backingInt(strx), + .n_type = .{ .bits = .{ + .ext = true, + .type = .sect, + .pext = false, + .is_stab = 0, + } }, + .n_sect = section.index(macho), + .n_desc = .{ + .arm_thumb_def = false, + .referenced_dynamically = false, + .discarded_or_no_dead_strip = false, + .weak_ref = false, + .weak_def_or_ref_to_weak = false, + .symbol_resolver = false, + .alt_entry = false, + }, + .n_value = value, + }; + if (macho.targetEndian() != std.lang.Endian.native) { + std.mem.byteSwapAllFields(std.macho.nlist_64, sym_index.nlist(macho)); + } +} + +fn addSegment(macho: *MachO, opts: struct { + name: []const u8, + prot: std.macho.vm_prot_t, + /// The caller is allowed to create the section's node themselves, in which case it is passed in + /// here. The caller must initialize the `MachO.nodes` entry to `.{ .segment = undefined }`, and + /// the node must be created with the option `.moved = true`. + existing_node: ?MappedFile.Node.Index = null, +}) Error!Segment { + const gpa = macho.base.comp.gpa; + + const segment_command_64 = std.macho.segment_command_64; + + // We limit the number of segments to 255, because there can only be 255 *sections*, so there's + // no need to allow more. See corresponding check in `addSection`. + if (macho.segments.len == 255) { + return macho.base.comp.link_diags.fail("maximum segment count exceeded", .{}); + } + + try macho.nodes.ensureUnusedCapacity(gpa, 1); + try macho.segments.ensureUnusedCapacity(gpa, 1); + try macho.segments_by_name.ensureUnusedCapacity(gpa, 1); + + const root_ni: MappedFile.Node.Index = .root; + + const lc = try macho.appendSimpleLoadCommand(std.macho.segment_command_64, .{ + .cmdsize = @sizeOf(segment_command_64), + .segname = name: { + var name: [16]u8 = undefined; + @memcpy(name[0..opts.name.len], opts.name); + @memset(name[opts.name.len..], 0); + break :name name; + }, + .vmaddr = 0, + .vmsize = 0, + .fileoff = 0, // to be set by `flushMoved` + .filesize = 0, + .maxprot = opts.prot, + .initprot = opts.prot, + .nsects = 0, + .flags = 0, + }); + + const segment: Segment = @fromBackingInt(@intCast(macho.segments.len)); + const segment_node: MappedFile.Node.Index = node: { + if (opts.existing_node) |node| { + assert(node.hasMoved(&macho.mf)); // so that `fileoff` will be set + assert(macho.nodes.items(.tags)[@backingInt(node)] == .segment); + macho.nodes.set(@backingInt(node), .{ .segment = segment }); + break :node node; + } + const node = try root_ni.addFloatingChild(gpa, &macho.mf, .{ + .alignment = macho.mf.flags.block_size.max(macho.targetPageAlign()), + .moved = true, // so that `fileoff` will be set + .bubbles_moved = false, + }); + macho.nodes.appendAssumeCapacity(.{ .segment = segment }); + break :node node; + }; + macho.segments.appendAssumeCapacity(.{ + .lc = lc, + .node = segment_node, + }); + + { + const gop = macho.segments_by_name.getOrPutAssumeCapacityAdapted( + opts.name, + @as(Segment.NameAdapter, .{ .macho = macho }), + ); + assert(!gop.found_existing); + assert(gop.index == @backingInt(segment)); + } + + return segment; +} + +const String = enum(u32) { + empty = 0, + _, + fn slice(s: String, macho: *const MachO) [:0]const u8 { + const overlong = macho.node.strtab.slice(&macho.mf)[@backingInt(s)..]; + return overlong[0..std.mem.findScalar(u8, overlong, 0).? :0]; + } + + const Adapter = struct { + macho: *const MachO, + pub fn eql(ctx: Adapter, lhs_slice: []const u8, rhs: String, _: usize) bool { + const macho = ctx.macho; + return std.mem.eql(u8, lhs_slice, rhs.slice(macho)); + } + pub fn hash(ctx: Adapter, s: []const u8) u32 { + _ = ctx; + return std.array_hash_map.hashString(s); + } + }; +}; + +fn string(macho: *MachO, slice: []const u8) Error!String { + const gpa = macho.base.comp.gpa; + + const old_strsize = macho.targetLoad( + &macho.lc.symtab.ptr(macho, std.macho.symtab_command).strsize, + ); + try macho.node.strtab.ensureMinimumSize(gpa, &macho.mf, old_strsize + slice.len + 1); + + try macho.strtab.ensureUnusedCapacity(gpa, 1); + + errdefer comptime unreachable; + + const adapter: String.Adapter = .{ .macho = macho }; + const gop = macho.strtab.getOrPutAssumeCapacityAdapted(slice, adapter); + if (!gop.found_existing) { + macho.targetStore( + &macho.lc.symtab.ptr(macho, std.macho.symtab_command).strsize, + @intCast(old_strsize + slice.len + 1), + ); + gop.key_ptr.* = @fromBackingInt(old_strsize); + const dest_slice = macho.node.strtab.slice(&macho.mf)[old_strsize..]; + @memcpy(dest_slice[0..slice.len], slice); + dest_slice[slice.len] = 0; + } + return gop.key_ptr.*; +} + +fn nlistSlice(macho: *MachO) []std.macho.nlist_64 { + const nsyms = macho.targetLoad( + &macho.lc.symtab.ptr(macho, std.macho.symtab_command).nsyms, + ); + return @ptrCast(@alignCast( + macho.node.symtab.slice(&macho.mf)[0 .. nsyms * @sizeOf(std.macho.nlist_64)], + )); +} + +fn updateSegmentLoadAddresses(macho: *MachO) Allocator.Error!void { + // TODO: this segment load address allocation logic is bad because it moves segments quite + // frequently. We should avoid that by introducing padding between segments so that they have + // space to grow in-place. We could also consider allowing a segment which can't fit to jump + // over another segment, like in `Elf2.allocateSegmentLoadAddress`. + var cur_addr: u64 = start_addr: { + const pagezero_ptr = macho.lc.pagezero.ptr(macho, std.macho.segment_command_64); + assert(macho.targetLoad(&pagezero_ptr.vmaddr) == 0); + break :start_addr macho.targetLoad(&pagezero_ptr.vmsize); + }; + for (0..macho.segments.len) |segment_raw| { + const segment: Segment = @fromBackingInt(@intCast(segment_raw)); + + _, const size: u64 = segment.node(macho).location(&macho.mf).resolve(&macho.mf); + const cmd_ptr = segment.lcPtr(macho); + + macho.targetStore(&cmd_ptr.vmsize, size); + + const old_addr = macho.targetLoad(&cmd_ptr.vmaddr); + if (old_addr >= cur_addr) { + cur_addr = old_addr; + } else { + macho.targetStore(&cmd_ptr.vmaddr, cur_addr); + try segment.node(macho).childrenMoved(macho.base.comp.gpa, &macho.mf); + } + + cur_addr = macho.targetPageAlign().forward(cur_addr + size); + } +} + +/// Like `appendLoadCommand`, but asserts that `cmd.cmdsize` exactly equals `@sizeOf(Cmd)` and does +/// not return the (empty) trailing slice. +fn appendSimpleLoadCommand( + macho: *MachO, + comptime Cmd: type, + cmd: Cmd, +) Error!LoadCommand { + comptime assert(@sizeOf(Cmd) % 8 == 0); + assert(cmd.cmdsize == @sizeOf(Cmd)); + return (try macho.appendLoadCommand(Cmd, cmd)).lc; +} + +/// Asserts that the first two fields of `Cmd` are `cmd: LC` and `cmdsize: u32`, and that the value +/// of `lc.cmdsize` is 8-byte aligned. +fn appendLoadCommand( + macho: *MachO, + comptime Cmd: type, + cmd: Cmd, +) Error!struct { + lc: LoadCommand, + trailing: []u8, +} { + const gpa = macho.base.comp.gpa; + + try macho.lc.offsets.ensureUnusedCapacity(gpa, 1); + + comptime { + const s = @typeInfo(Cmd).@"struct"; + assert(s.layout == .@"extern"); + // First field of load command should be `cmd: LC` + assert(std.mem.eql(u8, s.field_names[0], "cmd")); + assert(s.field_types[0] == std.macho.LC); + // Second field of load command should be `cmdsize: u32` + assert(std.mem.eql(u8, s.field_names[1], "cmdsize")); + assert(s.field_types[1] == u32); + } + + const cmd_size = @as(*const std.macho.load_command, @ptrCast(&cmd)).cmdsize; + assert(cmd_size % 8 == 0); + + const mach_header_64 = std.macho.mach_header_64; + const old_cmds_size: u32 = size: { + const mach_header: *const mach_header_64 = @ptrCast(@alignCast( + macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(mach_header_64)], + )); + break :size macho.targetLoad(&mach_header.sizeofcmds); + }; + const new_cmds_size = old_cmds_size + cmd_size; + try macho.node.header.ensureMinimumSize(gpa, &macho.mf, @sizeOf(mach_header_64) + new_cmds_size); + + const header_slice = macho.node.header.slice(&macho.mf); + const mach_header: *mach_header_64 = @ptrCast(@alignCast( + header_slice[0..@sizeOf(mach_header_64)], + )); + macho.targetStore(&mach_header.sizeofcmds, new_cmds_size); + macho.targetStore(&mach_header.ncmds, macho.targetLoad(&mach_header.ncmds) + 1); + + const cmd_slice: []align(8) u8 = @alignCast( + header_slice[@sizeOf(mach_header_64) + old_cmds_size ..][0..cmd_size], + ); + + const cmd_ptr: *Cmd = @ptrCast(cmd_slice[0..@sizeOf(Cmd)]); + cmd_ptr.* = cmd; + if (macho.targetEndian() != std.lang.Endian.native) { + std.mem.byteSwapAllFields(Cmd, cmd_ptr); + } + + const lc: LoadCommand = @fromBackingInt(@intCast(macho.lc.offsets.items.len)); + macho.lc.offsets.appendAssumeCapacity(@sizeOf(mach_header_64) + old_cmds_size); + + return .{ .lc = lc, .trailing = cmd_slice[@sizeOf(Cmd)..] }; +} + +fn addSection(macho: *MachO, opts: struct { + name: []const u8, + segment: Segment, + @"align": MappedFile.Alignment, + /// This matches the layout of `std.macho.section_64.flags`. + flags: packed struct(u32) { + _unused0: u10 = 0, + some_instructions: bool = false, + _unused1: u21 = 0, + }, +}) Error!Section { + const gpa = macho.base.comp.gpa; + + const mach_header_64 = std.macho.mach_header_64; + const segment_command_64 = std.macho.segment_command_64; + const section_64 = std.macho.section_64; + + // Check if we can actually fit another section. The limit is 255 because Mach-O implicitly + // assigns 8-bit indices to sections starting from 1 (0 is reserved). + if (macho.sections.len == 255) { + return macho.base.comp.link_diags.fail("maximum section count exceeded", .{}); + } + + const old_cmds_size: u32 = size: { + const mach_header: *const mach_header_64 = @ptrCast(@alignCast( + macho.node.header.sliceConst(&macho.mf)[0..@sizeOf(mach_header_64)], + )); + break :size macho.targetLoad(&mach_header.sizeofcmds); + }; + const new_cmds_size = old_cmds_size + @sizeOf(section_64); + try macho.node.header.ensureMinimumSize(gpa, &macho.mf, @sizeOf(mach_header_64) + new_cmds_size); + + try macho.nodes.ensureUnusedCapacity(gpa, 1); + try macho.lc.offsets.ensureUnusedCapacity(gpa, 1); + try macho.sections.ensureUnusedCapacity(gpa, 1); + try macho.sections_by_name.ensureUnusedCapacity(gpa, 1); + + const section: Section = @fromBackingInt(@intCast(macho.sections.len)); + const section_node = try opts.segment.node(macho).addFloatingChild(gpa, &macho.mf, .{ + .alignment = opts.@"align", + .moved = true, // so that `offset` will be set + }); + macho.nodes.appendAssumeCapacity(.{ .section = section }); + macho.sections.appendAssumeCapacity(.{ + .lc = @fromBackingInt(@intCast(macho.lc.offsets.items.len)), + .node = section_node, + .first_symbol = .none, + .index = undefined, // populated later + }); + + { + const adapter: Section.NameAdapter = .{ .macho = macho }; + const key: Section.NameAdapter.SegmentAndSection = .{ + .segment = opts.segment.name(macho), + .section = opts.name, + }; + const gop = macho.sections_by_name.getOrPutAssumeCapacityAdapted(key, adapter); + assert(!gop.found_existing); + assert(gop.index == @backingInt(section)); + } + + const header_slice = macho.node.header.slice(&macho.mf); + + { + const mach_header: *mach_header_64 = @ptrCast(@alignCast( + header_slice[0..@sizeOf(mach_header_64)], + )); + macho.targetStore(&mach_header.sizeofcmds, new_cmds_size); + } + + const seg_cmd = opts.segment.lcPtr(macho); + const old_nsects = macho.targetLoad(&seg_cmd.nsects); + const old_cmdsize = macho.targetLoad(&seg_cmd.cmdsize); + assert(old_cmdsize == @sizeOf(segment_command_64) + old_nsects * @sizeOf(section_64)); + const new_cmdsize = old_cmdsize + @sizeOf(section_64); + macho.targetStore(&seg_cmd.cmdsize, new_cmdsize); + macho.targetStore(&seg_cmd.nsects, old_nsects + 1); + + const seg_cmd_off = opts.segment.lc(macho).offset(macho); + const offset = seg_cmd_off + old_cmdsize; + + // Shift forward every load command which comes after this segment command, to make space + // for the new `section_64` (and update the offsets in `macho.lc.offsets` accordingly). + @memmove( + header_slice[seg_cmd_off + new_cmdsize .. @sizeOf(mach_header_64) + new_cmds_size], + header_slice[seg_cmd_off + old_cmdsize .. @sizeOf(mach_header_64) + old_cmds_size], + ); + for (macho.lc.offsets.items) |*other_off| { + if (other_off.* >= offset) { + other_off.* += @sizeOf(section_64); + } + } + // Only now do we append our *own* offset (otherwise the above loop would have changed it!). + macho.lc.offsets.appendAssumeCapacity(offset); + + // Increment the index of every section after where we've inserted ourselves. + { + var new_section_index: u8 = 1; + try macho.sections_changed_index.ensureUnusedCapacity(gpa, macho.sections.len); + for (0..macho.sections.len) |other_section_raw| { + const other_section: Section = @fromBackingInt(@intCast(other_section_raw)); + if (other_section == section) continue; + if (other_section.lc(macho).offset(macho) > offset) { + macho.sections.items(.index)[@backingInt(other_section)] += 1; + if (other_section.firstSymbol(macho).* != .none) { + macho.sections_changed_index.putAssumeCapacity(other_section, {}); + } + } else { + new_section_index += 1; + } + } + macho.sections.items(.index)[@backingInt(section)] = new_section_index; + } + + // Write the new `section_64`. + const section_cmd: *section_64 = @ptrCast(@alignCast( + header_slice[offset..][0..@sizeOf(section_64)], + )); + section_cmd.* = .{ + .sectname = name: { + var name: [16]u8 = undefined; + @memcpy(name[0..opts.name.len], opts.name); + @memset(name[opts.name.len..], 0); + break :name name; + }, + .segname = seg_cmd.segname, + .addr = 0, + .size = 0, + .offset = 0, + .@"align" = opts.@"align".toLog2Units(), + .reloff = 0, + .nreloc = 0, + .flags = @bitCast(opts.flags), + }; + if (macho.targetEndian() != std.lang.Endian.native) { + std.mem.byteSwapAllFields(section_64, section_cmd); + } + + return section; +} + +fn segmentByName(macho: *const MachO, name: []const u8) ?Segment { + const adapter: Segment.NameAdapter = .{ .macho = macho }; + if (macho.segments_by_name.getIndexAdapted(name, adapter)) |segment_raw| { + return @fromBackingInt(@intCast(segment_raw)); + } else { + return null; + } +} + +fn sectionByName(macho: *const MachO, segment_name: []const u8, section_name: []const u8) ?Section { + const adapter: Section.NameAdapter = .{ .macho = macho }; + const key: Section.NameAdapter.SegmentAndSection = .{ + .segment = segment_name, + .section = section_name, + }; + if (macho.sections_by_name.getIndexAdapted(key, adapter)) |section_raw| { + return @fromBackingInt(@intCast(section_raw)); + } else { + return null; + } +} + +fn targetEndian(macho: *const MachO) std.lang.Endian { + _ = macho; + return .little; +} +fn targetPageAlign(macho: *const MachO) MappedFile.Alignment { + _ = macho; + return .fromByteUnits(0x4000); +} +fn targetLoad(macho: *const MachO, ptr: anytype) @typeInfo(@TypeOf(ptr)).pointer.child { + const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer; + const Child = pointer_ty.child; + const alignment = pointer_ty.attrs.@"align" orelse @alignOf(Child); + return switch (@typeInfo(Child)) { + else => @compileError(@typeName(Child)), + .int => std.mem.toNative(Child, ptr.*, macho.targetEndian()), + .@"enum" => |@"enum"| @fromBackingInt(macho.targetLoad(@as(*align(alignment) const @"enum".tag_type, @ptrCast(ptr)))), + .@"struct" => |@"struct"| @bitCast( + macho.targetLoad(@as(*align(alignment) @"struct".backing_integer.?, @ptrCast(ptr))), + ), + }; +} +fn targetStore(macho: *const MachO, ptr: anytype, val: @typeInfo(@TypeOf(ptr)).pointer.child) void { + const pointer_ty = @typeInfo(@TypeOf(ptr)).pointer; + const Child = pointer_ty.child; + const alignment = pointer_ty.attrs.@"align" orelse @alignOf(Child); + return switch (@typeInfo(Child)) { + else => @compileError(@typeName(Child)), + .int => ptr.* = std.mem.nativeTo(Child, val, macho.targetEndian()), + .@"enum" => |@"enum"| macho.targetStore( + @as(*align(alignment) @"enum".tag_type, @ptrCast(ptr)), + @backingInt(val), + ), + .@"struct" => |@"struct"| macho.targetStore( + @as(*align(alignment) @"struct".backing_integer.?, @ptrCast(ptr)), + @bitCast(val), + ), + }; +} + +pub fn deinit(macho: *MachO) void { + const gpa = macho.base.comp.gpa; + macho.mf.deinit(gpa); + macho.nodes.deinit(gpa); + macho.segments.deinit(gpa); + macho.sections.deinit(gpa); + macho.segments_by_name.deinit(gpa); + macho.sections_by_name.deinit(gpa); + macho.strtab.deinit(gpa); + macho.symtab.deinit(gpa); + macho.sections_changed_index.deinit(gpa); + macho.* = undefined; +} + +pub fn startProgress(macho: *MachO, prog_node: std.Progress.Node) void { + prog_node.increaseEstimatedTotalItems(4); + macho.const_prog_node = prog_node.start("Constants", 0); + macho.synth_prog_node = prog_node.start("Synthetics", 0); + macho.input_prog_node = prog_node.start("Inputs", 0); + macho.mf.update_prog_node = prog_node.start("Relocations", macho.mf.updates.items.len); +} +pub fn endProgress(macho: *MachO) void { + macho.const_prog_node.end(); + macho.const_prog_node = .none; + macho.synth_prog_node.end(); + macho.synth_prog_node = .none; + macho.input_prog_node.end(); + macho.input_prog_node = .none; + macho.mf.update_prog_node.end(); + macho.mf.update_prog_node = .none; +} + +pub fn navSymbol(macho: *MachO, nav_id: InternPool.Nav.Index) link.Error!link.File.SymbolId { + _ = macho; + _ = nav_id; + @panic("TODO"); +} +pub fn uavSymbol( + macho: *MachO, + pt: Zcu.PerThread, + uav_val: InternPool.Index, + uav_align: InternPool.Alignment, +) link.Error!link.File.SymbolId { + _ = macho; + _ = pt; + _ = uav_val; + _ = uav_align; + @panic("TODO"); +} +pub fn relocSymAddr( + macho: *MachO, + reloc_info: link.File.RelocInfo, +) link.Error!void { + _ = macho; + _ = reloc_info; + @panic("TODO"); +} + +pub fn loadInput(macho: *MachO, input: link.Input) link.Error!void { + const diags = &macho.base.comp.link_diags; + macho.loadInputInner(input) catch |err| switch (err) { + error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}), + else => |e| return e, + }; +} +fn loadInputInner(macho: *MachO, input: link.Input) Error!void { + switch (input) { + .res => unreachable, + .object => @panic("TODO(MachO2): load object"), + .archive => @panic("TODO(MachO2): load archive"), + .dso => @panic("TODO(MachO2): load dso"), + .tbd => |tbd| { + log.debug("load tbd {qf}", .{tbd.path.fmtEscapeString()}); + + // TODO: obviously this is completely wrong, we need to parse the tbd to determine the + // dylib install name; but for now this lets us link libSystem + const prefix = "/usr/lib/"; + const name = std.fs.path.stem(tbd.path.sub_path); + const suffix = ".dylib"; + + const cmd = try macho.appendLoadCommand(std.macho.dylib_command, .{ + .cmd = .LOAD_DYLIB, + .cmdsize = std.mem.alignForward(u32, @intCast( + @sizeOf(std.macho.dylib_command) + prefix.len + name.len + suffix.len + 1, + ), 8), + .dylib = .{ + .name = @sizeOf(std.macho.dylib_command), + .timestamp = 0, + .current_version = 0, + .compatibility_version = 0, + }, + }); + @memcpy(cmd.trailing[0..prefix.len], prefix); + @memcpy(cmd.trailing[prefix.len..][0..name.len], name); + @memcpy(cmd.trailing[prefix.len + name.len ..][0..suffix.len], suffix); + @memset(cmd.trailing[prefix.len + name.len + suffix.len ..], 0); + }, + } +} +pub fn setDarwinSdkVersion(macho: *MachO, sdk_version: link.DarwinSdkVersion) link.Error!void { + const comp = macho.base.comp; + const diags = &comp.link_diags; + const target = comp.getTarget(); + + const os_min_version: link.DarwinSdkVersion = .{ + .major = @intCast(target.os.version_range.semver.min.major), + .minor = @intCast(target.os.version_range.semver.min.minor), + .patch = @intCast(target.os.version_range.semver.min.patch), + }; + + _ = macho.appendSimpleLoadCommand(std.macho.version_min_command, .{ + .cmd = switch (target.os.tag) { + .macos => .VERSION_MIN_MACOSX, + .ios => .VERSION_MIN_IPHONEOS, + .tvos => .VERSION_MIN_TVOS, + .watchos => .VERSION_MIN_WATCHOS, + .maccatalyst, .driverkit, .visionos => |os_tag| std.debug.panic( + "TODO(MachO2): sdk version for '{t}'", + .{os_tag}, + ), + else => unreachable, + }, + .version = @backingInt(os_min_version), + .sdk = @backingInt(sdk_version), + }) catch |err| switch (err) { + error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}), + else => |e| return e, + }; +} +pub fn prelink(macho: *MachO, prog_node: std.Progress.Node) link.Error!void { + _ = macho; + _ = prog_node; +} +pub fn flush( + macho: *MachO, + arena: Allocator, + tid: Zcu.PerThread.Id, + prog_node: std.Progress.Node, +) link.Error!void { + const diags = &macho.base.comp.link_diags; + _ = arena; + _ = prog_node; + + try macho.updateSegmentLoadAddresses(); + while (try macho.idle(tid)) {} + + if (macho.lc.entry_point) |entry_point_lc| { + // TODO: actually find the entry point. + // For now, we just set the entry point to the start of the "__TEXT,__text" section. + const entry_node = macho.sectionByName("__TEXT", "__text").?.node(macho); + const entry_file_off: u64, _ = entry_node.location(&macho.mf).resolve(&macho.mf); + const entry_point_ptr = entry_point_lc.ptr(macho, std.macho.entry_point_command); + macho.targetStore(&entry_point_ptr.entryoff, @intCast(entry_file_off)); + } + + macho.mf.flush() catch |err| switch (err) { + error.MappedFileIo => return diags.fail("failed to write output file: {t}", .{macho.mf.io_err.?}), + else => |e| return e, + }; + + if (macho.options.enable_link_snapshots) + macho.dumpStderr(tid) catch |err| + return diags.fail("dumping link snapshot failed: {t}", .{err}); +} +pub fn updateErrorData(macho: *MachO, pt: Zcu.PerThread) link.Error!void { + _ = macho; + _ = pt; + @panic("TODO"); +} +pub fn idle(macho: *MachO, tid: Zcu.PerThread.Id) link.Error!bool { + _ = tid; + + macho.mf.nodes_lock.lock(); + defer macho.mf.nodes_lock.unlock(); + + task: { + if (macho.sections_changed_index.pop()) |kv| { + const section = kv.key; + var sym_it = section.firstSymbol(macho).*; + while (sym_it.unwrap()) |sym| : (sym_it = sym.ptr(macho).next_in_section) { + macho.targetStore(&sym.nlist(macho).n_sect, section.index(macho)); + } + break :task; + } + + while (macho.mf.updates.pop()) |ni| { + const clean_moved = ni.cleanMoved(&macho.mf); + const clean_resized = ni.cleanResized(&macho.mf); + const clean_next_moved = ni.cleanNextMoved(&macho.mf); + if (!clean_moved and !clean_resized and !clean_next_moved) continue; + + const sub_prog_node = macho.mf.update_prog_node.start(@tagName(macho.getNode(ni)), 0); + defer sub_prog_node.end(); + + if (clean_moved) try macho.flushMoved(ni); + if (clean_resized) try macho.flushResized(ni); + + break :task; + } + } + + return macho.sections_changed_index.count() > 0 or + macho.mf.updates.items.len > 0; +} +fn flushMoved(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void { + macho.flushMachOFileOffset(ni); + switch (macho.getNode(ni)) { + .macho => unreachable, + .macho_header => {}, + .segment => return, // segments don't bubble moved + .section => |section| { + const segment_vaddr: u64 = macho.getNode(ni.parent(&macho.mf).unwrap().?).segment.vaddr(macho); + const section_offset = ni.location(&macho.mf).resolve(&macho.mf)[0]; + macho.targetStore(§ion.lcPtr(macho).addr, segment_vaddr + section_offset); + }, + .symtab => {}, + .strtab => {}, + } + try ni.childrenMoved(macho.base.comp.gpa, &macho.mf); +} +fn flushMachOFileOffset(macho: *MachO, ni: MappedFile.Node.Index) void { + const offset = ni.fileLocation(&macho.mf, false).offset; + switch (macho.getNode(ni)) { + .macho => unreachable, + .macho_header => { + assert(offset == 0); + }, + .segment => |segment| { + macho.targetStore(&segment.lcPtr(macho).fileoff, @intCast(offset)); + var child_oni = ni.first(&macho.mf); + while (child_oni.unwrap()) |child_ni| : (child_oni = child_ni.next(&macho.mf)) { + macho.flushMachOFileOffset(child_ni); + } + }, + .section => |section| { + macho.targetStore(§ion.lcPtr(macho).offset, @intCast(offset)); + }, + .symtab => { + const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command); + macho.targetStore(&symtab_cmd.symoff, @intCast(offset)); + }, + .strtab => { + const symtab_cmd = macho.lc.symtab.ptr(macho, std.macho.symtab_command); + macho.targetStore(&symtab_cmd.stroff, @intCast(offset)); + }, + } +} +fn flushResized(macho: *MachO, ni: MappedFile.Node.Index) link.Error!void { + const size = ni.location(&macho.mf).resolve(&macho.mf)[1]; + switch (macho.getNode(ni)) { + .macho => {}, + .macho_header => {}, + .segment => |segment| { + macho.targetStore(&segment.lcPtr(macho).filesize, size); + }, + .section => |section| { + macho.targetStore(§ion.lcPtr(macho).size, size); + }, + .symtab, .strtab => { + // Don't modify the symtab load command's `nsyms` or `strsize` field here; we only do + // that when we actually add strings or symbols. + }, + } +} + +pub fn updateFunc( + macho: *MachO, + pt: Zcu.PerThread, + func_index: InternPool.Index, + mir: *const codegen.AnyMir, +) link.Error!void { + _ = macho; + _ = pt; + _ = func_index; + _ = mir; + @panic("TODO"); +} +pub fn updateNav(macho: *MachO, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.Error!void { + _ = macho; + _ = pt; + _ = nav_index; + @panic("TODO"); +} +pub fn updateExports( + macho: *MachO, + pt: Zcu.PerThread, + export_indices: []const Zcu.Export.Index, +) link.Error!void { + _ = macho; + _ = pt; + _ = export_indices; + @panic("TODO"); +} + +fn dumpStderr(macho: *MachO, tid: Zcu.PerThread.Id) Io.File.Writer.Error!void { + const comp = macho.base.comp; + const io = comp.io; + var buffer: [512]u8 = undefined; + const stderr = try io.lockStderr(&buffer, null); + defer io.unlockStderr(); + const w = &stderr.file_writer.interface; + _ = macho.dump(w, tid) catch |err| switch (err) { + error.WriteFailed => return stderr.file_writer.err.?, + }; +} + +pub fn dump(macho: *MachO, w: *Io.Writer, tid: Zcu.PerThread.Id) !link.File.DumpResult { + if (macho.options.enable_link_snapshots) { + try macho.printNode(tid, w, .root, 0); + return .enabled; + } + return .disabled; +} +fn printNode( + macho: *const MachO, + tid: Zcu.PerThread.Id, + w: *Io.Writer, + ni: MappedFile.Node.Index, + indent: usize, +) Io.Writer.Error!void { + const node = macho.getNode(ni); + try w.splatByteAll(' ', indent); + switch (node) { + .macho => try w.writeAll("macho"), + .macho_header => try w.writeAll("macho_header"), + .section => |section| try w.print("section({q})", .{section.name(macho)}), + .segment => |segment| try w.print("segment({q})", .{segment.name(macho)}), + .symtab => try w.writeAll("symtab"), + .strtab => try w.writeAll("strtab"), + } + { + const mf_node = &macho.mf.nodes.items[@backingInt(ni)]; + const off, const size = mf_node.location().resolve(&macho.mf); + try w.print(" index={d} offset=0x{x} size=0x{x} align=0x{x} {t}{s}{s}{s}{s}{s}{s}\n", .{ + @backingInt(ni), + off, + size, + mf_node.flags.alignment.toByteUnits(), + mf_node.flags.position, + if (mf_node.flags.bubbles_moved) " bubbles_moved" else "", + if (mf_node.flags.moved) " moved" else "", + if (mf_node.flags.resized) " resized" else "", + if (mf_node.flags.enable_next_moved) " enable_next_moved" else "", + if (mf_node.flags.next_moved) " next_moved" else "", + if (mf_node.flags.has_content) " has_content" else "", + }); + } + if (ni.first(&macho.mf).unwrap()) |first_ni| { + // non-leaf, just print children + var child_ni = first_ni; + while (true) { + try macho.printNode(tid, w, child_ni, indent + 1); + child_ni = child_ni.next(&macho.mf).unwrap() orelse break; + } + return; + } + const start_address: usize, const end_address: usize = file_loc: { + const file_loc = ni.fileLocation(&macho.mf, false); + break :file_loc .{ @intCast(file_loc.offset), @intCast(file_loc.offset + file_loc.size) }; + }; + var address = start_address; + const line_len = 0x10; + while (true) : (address = @min(std.mem.alignForward(usize, address + 1, line_len), end_address)) { + try w.splatByteAll(' ', indent + 1); + try w.print("{x:0>8}", .{address}); + if (address == end_address) break try w.writeByte('\n'); + try w.splatByteAll(' ', 2); + const start_byte_address = std.mem.alignBackward(usize, address, line_len); + const end_byte_address = start_byte_address + line_len; + for (start_byte_address..end_byte_address) |byte_address| + if (byte_address < start_address or byte_address >= end_address) + try w.splatByteAll(' ', 3) + else + try w.print("{x:0>2} ", .{macho.mf.memory_map.memory[byte_address]}); + try w.writeByte(' '); + for (start_byte_address..@min(end_address, end_byte_address)) |byte_address| + try w.writeByte(if (byte_address < start_address or byte_address >= end_address) ' ' else char: { + const byte = macho.mf.memory_map.memory[byte_address]; + break :char if (std.ascii.isPrint(byte)) byte else '.'; + }); + try w.writeByte('\n'); + } +} + +const std = @import("std"); +const assert = std.debug.assert; +const log = std.log.scoped(.link); +const Allocator = std.mem.Allocator; +const Io = std.Io; + +const link = @import("../link.zig"); +const codegen = @import("../codegen.zig"); +const Compilation = @import("../Compilation.zig"); +const InternPool = @import("../InternPool.zig"); +const MappedFile = @import("MappedFile.zig"); +const Zcu = @import("../Zcu.zig"); diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 22b6f529accf3ba22eac77e9574677140260bfcd..5db69a96c00af9a0a6ad9fc0f987d5a37fab3b9e 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -495,12 +495,12 @@ pub const Node = extern struct { } pub fn hasMoved(ni: Node.Index, mf: *const MappedFile) bool { - var parent_ni = ni; - while (parent_ni != .root) { - const parent_node = parent_ni.get(mf); - if (!parent_node.flags.bubbles_moved) break; - if (parent_node.flags.moved) return true; - parent_ni = parent_node.parent.unwrap().?; + var cur_ni = ni; + while (cur_ni != .root) { + if (cur_ni.get(mf).flags.moved) return true; + const parent_ni = cur_ni.parent(mf).unwrap().?; + if (!parent_ni.get(mf).flags.bubbles_moved) break; + cur_ni = parent_ni; } return false; } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index e83fa056802e10d9c961ebdd3f77ea9355de6739..ea00a2f31b74610c151499948658af9e85002903 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -3783,6 +3783,7 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void { switch (input) { .res => unreachable, .dso => unreachable, + .tbd => unreachable, .object, .archive => |obj| { try argv.append(gpa, try obj.path.toString(comp.arena)); }, @@ -3792,6 +3793,7 @@ pub fn loadInput(wasm: *Wasm, input: link.Input) !void { switch (input) { .res => unreachable, .dso => unreachable, + .tbd => unreachable, .object => |obj| try parseObject(wasm, obj), .archive => |obj| try parseArchive(wasm, obj), } diff --git a/src/main.zig b/src/main.zig index 5ad21b548e12b1453aa4e1345c74fa7c17af5036..34ff983b0328f7d603ae0ad8e456ab5d0b993634 100644 --- a/src/main.zig +++ b/src/main.zig @@ -893,13 +893,6 @@ const ArgsIterator = struct { } }; -/// Similar to `link.Framework` except it doesn't store yet unresolved -/// path to the framework. -const Framework = struct { - needed: bool = false, - weak: bool = false, -}; - const CliModule = struct { root_path: []const u8, root_src_path: []const u8, @@ -1117,12 +1110,12 @@ fn buildOutputType( .llvm_m_args = .empty, .sysroot = null, - .lib_directories = .empty, // populated by createModule() .lib_dir_args = .empty, // populated from CLI arg parsing + .lib_directories = .empty, // populated by createModule() + .framework_dir_args = .empty, // populated from CLI arg parsing + .framework_directories = .empty, // populated by createModule() .libc_installation = null, .want_native_include_dirs = false, - .frameworks = .empty, - .framework_dirs = .empty, .rpath_list = .empty, .each_lib_rpath = null, .libc_paths_file = EnvVar.ZIG_LIBC.get(environ_map), @@ -1275,13 +1268,25 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "--library-directory") or mem.eql(u8, arg, "-L")) { try create_module.lib_dir_args.append(arena, args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "-F")) { - try create_module.framework_dirs.append(arena, args_iter.nextOrFatal()); + try create_module.framework_dir_args.append(arena, args_iter.nextOrFatal()); } else if (mem.eql(u8, arg, "-framework")) { - try create_module.frameworks.put(arena, args_iter.nextOrFatal(), .{}); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = args_iter.nextOrFatal(), + .needed = false, + .weak = false, + } }); } else if (mem.eql(u8, arg, "-weak_framework")) { - try create_module.frameworks.put(arena, args_iter.nextOrFatal(), .{ .weak = true }); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = args_iter.nextOrFatal(), + .needed = false, + .weak = true, + } }); } else if (mem.eql(u8, arg, "-needed_framework")) { - try create_module.frameworks.put(arena, args_iter.nextOrFatal(), .{ .needed = true }); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = args_iter.nextOrFatal(), + .needed = true, + .weak = false, + } }); } else if (mem.eql(u8, arg, "-install_name")) { install_name = args_iter.nextOrFatal(); } else if (mem.cutPrefix(u8, arg, "--compress-debug-sections=")) |param| { @@ -1396,11 +1401,11 @@ fn buildOutputType( } else if (mem.eql(u8, arg, "-iframework")) { const path = args_iter.nextOrFatal(); try cssan.addIncludePath(arena, &cc_argv, .iframework, arg, path, false); - try create_module.framework_dirs.append(arena, path); // Forward to the backend as -F + try create_module.framework_dir_args.append(arena, path); // Forward to the backend as -F } else if (mem.eql(u8, arg, "-iframeworkwithsysroot")) { const path = args_iter.nextOrFatal(); try cssan.addIncludePath(arena, &cc_argv, .iframeworkwithsysroot, arg, path, false); - try create_module.framework_dirs.append(arena, path); // Forward to the backend as -F + try create_module.framework_dir_args.append(arena, path); // Forward to the backend as -F } else if (mem.eql(u8, arg, "--version")) { const next_arg = args_iter.nextOrFatal(); version = std.SemanticVersion.parse(next_arg) catch |err| { @@ -1828,7 +1833,7 @@ fn buildOutputType( } else if (mem.cutPrefix(u8, arg, "-L")) |rest| { try create_module.lib_dir_args.append(arena, rest); } else if (mem.cutPrefix(u8, arg, "-F")) |rest| { - try create_module.framework_dirs.append(arena, rest); + try create_module.framework_dir_args.append(arena, rest); } else if (mem.cutPrefix(u8, arg, "-l")) |name| { // We don't know whether this library is part of libc // or libc++ until we resolve the target, so we append @@ -2532,8 +2537,12 @@ fn buildOutputType( disable_c_depfile = true; try cc_argv.appendSlice(arena, it.other_args); }, - .framework_dir => try create_module.framework_dirs.append(arena, it.only_arg), - .framework => try create_module.frameworks.put(arena, it.only_arg, .{}), + .framework_dir => try create_module.framework_dir_args.append(arena, it.only_arg), + .framework => try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = it.only_arg, + .needed = false, + .weak = false, + } }), .nostdlibinc => create_module.want_native_include_dirs = false, .strip => mod_opts.strip = true, .exec_model => { @@ -2561,7 +2570,11 @@ fn buildOutputType( }, .name_done = false, } }), - .weak_framework => try create_module.frameworks.put(arena, it.only_arg, .{ .weak = true }), + .weak_framework => try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = it.only_arg, + .needed = false, + .weak = true, + } }), .headerpad_max_install_names => headerpad_max_install_names = true, .compress_debug_sections => { if (it.only_arg.len == 0) { @@ -2914,11 +2927,23 @@ fn buildOutputType( fatal("unable to parse minor subsystem version {q}: {t}", .{ minor, err }); }; } else if (mem.eql(u8, arg, "-framework")) { - try create_module.frameworks.put(arena, linker_args_it.nextOrFatal(), .{}); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = linker_args_it.nextOrFatal(), + .needed = false, + .weak = false, + } }); } else if (mem.eql(u8, arg, "-weak_framework")) { - try create_module.frameworks.put(arena, linker_args_it.nextOrFatal(), .{ .weak = true }); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = linker_args_it.nextOrFatal(), + .needed = false, + .weak = true, + } }); } else if (mem.eql(u8, arg, "-needed_framework")) { - try create_module.frameworks.put(arena, linker_args_it.nextOrFatal(), .{ .needed = true }); + try create_module.cli_link_inputs.append(arena, .{ .framework_query = .{ + .name = linker_args_it.nextOrFatal(), + .needed = true, + .weak = false, + } }); } else if (mem.eql(u8, arg, "-needed_library")) { try create_module.cli_link_inputs.append(arena, .{ .name_query = .{ .name = linker_args_it.nextOrFatal(), @@ -3400,59 +3425,6 @@ fn buildOutputType( } } - var resolved_frameworks = std.array_list.Managed(Compilation.Framework).init(arena); - - if (create_module.frameworks.keys().len > 0) { - var test_path = std.array_list.Managed(u8).init(gpa); - defer test_path.deinit(); - - var checked_paths = std.array_list.Managed(u8).init(gpa); - defer checked_paths.deinit(); - - var failed_frameworks = std.array_list.Managed(struct { - name: []const u8, - checked_paths: []const u8, - }).init(arena); - - framework: for (create_module.frameworks.keys(), create_module.frameworks.values()) |framework_name, info| { - checked_paths.clearRetainingCapacity(); - - for (create_module.framework_dirs.items) |framework_dir_path| { - if (try accessFrameworkPath( - io, - &test_path, - &checked_paths, - framework_dir_path, - framework_name, - )) { - const path = Path.initCwd(try arena.dupe(u8, test_path.items)); - try resolved_frameworks.append(.{ - .needed = info.needed, - .weak = info.weak, - .path = path, - }); - continue :framework; - } - } - - try failed_frameworks.append(.{ - .name = framework_name, - .checked_paths = try arena.dupe(u8, checked_paths.items), - }); - } - - if (failed_frameworks.items.len > 0) { - for (failed_frameworks.items) |f| { - const searched_paths = if (f.checked_paths.len == 0) " none" else f.checked_paths; - std.log.err("unable to find framework {q}. searched paths: {s}", .{ - f.name, searched_paths, - }); - } - process.exit(1); - } - } - // After this point, resolved_frameworks is used instead of frameworks. - if (create_module.resolved_options.output_mode == .Obj and target.ofmt == .coff) { const total_obj_count = create_module.c_source_files.items.len + @intFromBool(root_src_file != null) + @@ -3689,6 +3661,7 @@ fn buildOutputType( .emit_docs = emit_docs_resolved, .emit_implib = emit_implib_resolved, .lib_directories = create_module.lib_directories.items, + .framework_directories = create_module.framework_directories.items, .rpath_list = create_module.rpath_list.items, .symbol_wrap_set = symbol_wrap_set, .c_source_files = create_module.c_source_files.items, @@ -3697,8 +3670,6 @@ fn buildOutputType( .rc_includes = rc_includes, .mingw_unicode_entry_point = mingw_unicode_entry_point, .link_inputs = create_module.link_inputs.items, - .framework_dirs = create_module.framework_dirs.items, - .frameworks = resolved_frameworks.items, .windows_lib_names = create_module.windows_libs.keys(), .want_compiler_rt = if (zig_cc_explicitly_link_compiler_rt) true else want_compiler_rt, .want_ubsan_rt = want_ubsan_rt, @@ -4041,13 +4012,13 @@ const CreateModule = struct { /// CPU features. llvm_m_args: std.ArrayList([]const u8), sysroot: ?[]const u8, - lib_directories: std.ArrayList(Directory), lib_dir_args: std.ArrayList([]const u8), + lib_directories: std.ArrayList(Directory), + framework_dir_args: std.ArrayList([]const u8), + framework_directories: std.ArrayList(Directory), libc_installation: ?LibCInstallation, want_native_include_dirs: bool, - frameworks: std.array_hash_map.String(Framework), native_system_include_paths: []const []const u8, - framework_dirs: std.ArrayList([]const u8), rpath_list: std.ArrayList([]const u8), each_lib_rpath: ?bool, libc_paths_file: ?[]const u8, @@ -4158,7 +4129,7 @@ fn createModule( var unresolved_link_inputs: std.ArrayList(link.UnresolvedInput) = .empty; defer unresolved_link_inputs.deinit(gpa); try unresolved_link_inputs.ensureUnusedCapacity(gpa, create_module.cli_link_inputs.items.len); - var any_name_queries_remaining = false; + var any_named_library_queries = false; for (create_module.cli_link_inputs.items) |cli_link_input| switch (cli_link_input) { .name_query => |nq| { const lib_name = nq.name; @@ -4199,39 +4170,52 @@ fn createModule( } unresolved_link_inputs.appendAssumeCapacity(cli_link_input); - any_name_queries_remaining = true; + any_named_library_queries = true; + }, + .framework_query => { + unresolved_link_inputs.appendAssumeCapacity(cli_link_input); + any_named_library_queries = true; }, else => { unresolved_link_inputs.appendAssumeCapacity(cli_link_input); }, }; // After this point, unresolved_link_inputs is used instead of cli_link_inputs. - if (any_name_queries_remaining) create_module.want_native_include_dirs = true; + if (any_named_library_queries) create_module.want_native_include_dirs = true; // Resolve the library path arguments with respect to sysroot. try create_module.lib_directories.ensureUnusedCapacity(arena, create_module.lib_dir_args.items.len); - if (create_module.sysroot) |root| { - for (create_module.lib_dir_args.items) |lib_dir_arg| { - if (fs.path.isAbsolute(lib_dir_arg)) { - const stripped_dir = lib_dir_arg[fs.path.parsePath(lib_dir_arg).root.len..]; - const full_path = try fs.path.join(arena, &[_][]const u8{ root, stripped_dir }); - addLibDirectoryWarn(io, &create_module.lib_directories, full_path); - } else { - addLibDirectoryWarn(io, &create_module.lib_directories, lib_dir_arg); + for (create_module.lib_dir_args.items) |dir_arg| { + const path: []const u8 = path: { + if (fs.path.isAbsolute(dir_arg)) { + if (create_module.sysroot) |sysroot| { + // Change the path root to the given sysroot. + const stripped_dir = dir_arg[fs.path.parsePath(dir_arg).root.len..]; + break :path try fs.path.join(arena, &.{ sysroot, stripped_dir }); + } } - } - } else { - for (create_module.lib_dir_args.items) |lib_dir_arg| { - addLibDirectoryWarn(io, &create_module.lib_directories, lib_dir_arg); - } + break :path dir_arg; + }; + appendLibDirOrWarn(io, &create_module.lib_directories, path); } create_module.lib_dir_args = undefined; // From here we use lib_directories instead. - if (resolved_target.is_native_os and target.os.tag.isDarwin()) { - // If we want to link against frameworks, we need system headers. - if (create_module.frameworks.count() > 0) - create_module.want_native_include_dirs = true; + // Likewise for framework path arguments. + try create_module.framework_directories.ensureUnusedCapacity(arena, create_module.framework_dir_args.items.len); + for (create_module.framework_dir_args.items) |dir_arg| { + const path: []const u8 = path: { + if (fs.path.isAbsolute(dir_arg)) { + if (create_module.sysroot) |sysroot| { + // Change the path root to the given sysroot. + const stripped_dir = dir_arg[fs.path.parsePath(dir_arg).root.len..]; + break :path try fs.path.join(arena, &.{ sysroot, stripped_dir }); + } + } + break :path dir_arg; + }; + appendLibDirOrWarn(io, &create_module.framework_directories, path); } + create_module.framework_dir_args = undefined; // From here we use framework_directories instead. if (create_module.each_lib_rpath orelse resolved_target.is_native_os) { try create_module.rpath_list.ensureUnusedCapacity(arena, create_module.lib_directories.items.len); @@ -4253,11 +4237,17 @@ fn createModule( create_module.native_system_include_paths = try paths.include_dirs.toOwnedSlice(arena); - try create_module.framework_dirs.appendSlice(arena, paths.framework_dirs.items); try create_module.rpath_list.appendSlice(arena, paths.rpaths.items); try create_module.lib_directories.ensureUnusedCapacity(arena, paths.lib_dirs.items.len); - for (paths.lib_dirs.items) |path| addLibDirectoryWarn2(io, &create_module.lib_directories, path, true); + for (paths.lib_dirs.items) |path| { + appendLibDirOrWarnAllowMissing(io, &create_module.lib_directories, path); + } + + try create_module.framework_directories.ensureUnusedCapacity(arena, paths.framework_dirs.items.len); + for (paths.framework_dirs.items) |path| { + appendLibDirOrWarnAllowMissing(io, &create_module.framework_directories, path); + } } if (create_module.libc_paths_file) |paths_file| { @@ -4266,7 +4256,7 @@ fn createModule( } if (target.os.tag == .windows and (target.abi == .msvc or target.abi == .itanium) and - any_name_queries_remaining) + any_named_library_queries) { if (create_module.libc_installation == null) { create_module.libc_installation = LibCInstallation.findNative(arena, io, .{ @@ -4277,9 +4267,19 @@ fn createModule( fatal("unable to find native libc installation: {t}", .{err}); }; } + try create_module.lib_directories.ensureUnusedCapacity(arena, 2); - addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.msvc_lib_dir.?); - addLibDirectoryWarn(io, &create_module.lib_directories, create_module.libc_installation.?.kernel32_lib_dir.?); + + appendLibDirOrWarn( + io, + &create_module.lib_directories, + create_module.libc_installation.?.msvc_lib_dir.?, + ); + appendLibDirOrWarn( + io, + &create_module.lib_directories, + create_module.libc_installation.?.kernel32_lib_dir.?, + ); } // Destructively mutates but does not transfer ownership of `unresolved_link_inputs`. @@ -4291,6 +4291,7 @@ fn createModule( &unresolved_link_inputs, &create_module.link_inputs, create_module.lib_directories.items, + create_module.framework_directories.items, color, ) catch |err| fatal("failed to resolve link inputs: {t}", .{err}); @@ -6085,33 +6086,6 @@ const ClangSearchSanitizer = struct { }; }; -fn accessFrameworkPath( - io: Io, - test_path: *std.array_list.Managed(u8), - checked_paths: *std.array_list.Managed(u8), - framework_dir_path: []const u8, - framework_name: []const u8, -) !bool { - const sep = fs.path.sep_str; - - for (&[_][]const u8{ ".tbd", ".dylib", "" }) |ext| { - test_path.clearRetainingCapacity(); - try test_path.print("{s}" ++ sep ++ "{s}.framework" ++ sep ++ "{s}{s}", .{ - framework_dir_path, framework_name, framework_name, ext, - }); - try checked_paths.print("\n {s}", .{test_path.items}); - Io.Dir.cwd().access(io, test_path.items, .{}) catch |err| switch (err) { - error.FileNotFound => continue, - else => |e| fatal("unable to search for {s} framework {q}: {t}", .{ - ext, test_path.items, e, - }), - }; - return true; - } - - return false; -} - fn parseRcIncludes(arg: []const u8) std.zig.RcIncludes { return stringToEnum(std.zig.RcIncludes, arg) orelse fatal("unsupported rc includes type: {q}", .{arg}); @@ -6222,24 +6196,26 @@ fn anyObjectLinkInputs(link_inputs: []const link.UnresolvedInput) bool { return false; } -fn addLibDirectoryWarn(io: Io, lib_directories: *std.ArrayList(Directory), path: []const u8) void { - return addLibDirectoryWarn2(io, lib_directories, path, false); +fn appendLibDirOrWarn(io: Io, dirs: *std.ArrayList(Directory), path: []const u8) void { + if (Io.Dir.cwd().openDir(io, path, .{})) |handle| { + dirs.appendAssumeCapacity(.{ + .path = path, + .handle = handle, + }); + } else |err| { + warn("unable to open library directory {q}: {t}", .{ path, err }); + } } - -fn addLibDirectoryWarn2( - io: Io, - lib_directories: *std.ArrayList(Directory), - path: []const u8, - ignore_not_found: bool, -) void { - lib_directories.appendAssumeCapacity(.{ - .handle = Io.Dir.cwd().openDir(io, path, .{}) catch |err| { - if (err == error.FileNotFound and ignore_not_found) return; - warn("unable to open library directory {q}: {t}", .{ path, err }); - return; - }, - .path = path, - }); +fn appendLibDirOrWarnAllowMissing(io: Io, dirs: *std.ArrayList(Directory), path: []const u8) void { + if (Io.Dir.cwd().openDir(io, path, .{})) |handle| { + dirs.appendAssumeCapacity(.{ + .path = path, + .handle = handle, + }); + } else |err| switch (err) { + error.FileNotFound => {}, // ignore + else => |e| warn("unable to open library directory {q}: {t}", .{ path, e }), + } } const IoImpl = switch (build_options.io_mode) { diff --git a/src/target.zig b/src/target.zig index 81d39c22fa4d81129016bd58b04c97a6e3e7a1b6..6234c11a146b18c45b6b36ecf12138edebb99a5c 100644 --- a/src/target.zig +++ b/src/target.zig @@ -299,7 +299,7 @@ pub fn preferNewLinkerOverLld(target: *const std.Target) bool { /// Returns `true` if `ofmt` has two linker implementations, so `-fnew-linker` is meaningful. pub fn hasNewLinker(ofmt: std.Target.ObjectFormat) bool { return switch (ofmt) { - .elf => true, + .elf, .macho => true, else => false, }; } @@ -510,7 +510,7 @@ pub fn libcFullLinkFlags(target: *const std.Target) []const []const u8 { }, // On SerenityOS libc includes libm, libpthread, libdl, and libssp. .serenity => &.{"-lc"}, - else => &.{}, + else => if (target.os.tag.isDarwin()) &.{"-lSystem"} else &.{}, }; return result; }